Skip to Content
Primo RPA OrchestratorСистемным администраторамСекционирование таблиц с журналом Робота и Оркестратора для PostgreSQL

Секционирование существующих таблиц с журналом Робота и Оркестратора для PostgreSQL

Общая информация о механизме секционирования приведена на официальном сайте PostgreSQL.

1. Секционирование по месяцам

Для организации помесячного секционирования существующую таблицу необходимо подключить к секционированной таблице как раздел с историческими данными.

1.1. Журнал Робота (таблица Logs)

Создаётся новая головная секционированная таблица, структурно идентичная исходной таблице Logs, но с другим именем — например, Logs2:

CREATE TABLE public."Logs2" ( "Id" uuid NOT NULL, "OrchTimestampUtc" timestamp without time zone NOT NULL, "Event" integer, "EntityId" text COLLATE pg_catalog."default", "UserId" text COLLATE pg_catalog."default", "TimestampUtc" timestamp without time zone, "OperationKey" uuid, "Signature" text COLLATE pg_catalog."default", "WorkerAdminName" text COLLATE pg_catalog."default", "Text" text COLLATE pg_catalog."default", "EventType" integer, "IP" text COLLATE pg_catalog."default", "TenantId" text COLLATE pg_catalog."default", "AssignmentId" integer, "RobotId" integer, "RobotKey" text COLLATE pg_catalog."default", "ProjectId" integer, "WorkerId" integer, "Type" integer, "ParsedDateEvent" timestamp without time zone, "ParsedType" integer, "ParsedElementName" text COLLATE pg_catalog."default", "ParsedElementId" text COLLATE pg_catalog."default", "ParsedElementClass" text COLLATE pg_catalog."default", "ParsedMessage" text COLLATE pg_catalog."default", "ScreenFilePath" text COLLATE pg_catalog."default", "OrchScreenFilePath" text COLLATE pg_catalog."default", "OrchScreenFilePathThumb" text COLLATE pg_catalog."default", "Node" text COLLATE pg_catalog."default", "ExtDataJson" text COLLATE pg_catalog."default", "RobotName" text COLLATE pg_catalog."default", "ProjectName" text COLLATE pg_catalog."default", "WorkerName" text COLLATE pg_catalog."default", "RdpUserName" text COLLATE pg_catalog."default", "LogType" integer NOT NULL, "ParsedElementNum" text COLLATE pg_catalog."default" ) partition by range ("OrchTimestampUtc");

Далее создаётся секция для будущих данных. В примере предполагается, что начиная с 16 февраля 2026 года новых записей ещё нет, и секции будут помесячными. При развёртывании необходимо подставить актуальные даты.

create table "Logs_20260216" partition of "Logs2" for values from ('2026-02-16') to ('2026-03-01');

Для подготовки существующей таблицы Logs к присоединению добавляется ограничение на поле OrchTimestampUtc и выполняется его валидация на всех данных:

begin; set local statement_timeout to '1s'; alter table "Logs" add constraint "Logs_partbound_check" check ("OrchTimestampUtc" < '2026-02-16' and "OrchTimestampUtc" is not null) not valid; commit; alter table "Logs" validate constraint "Logs_partbound_check";

Если validate constraint завершается ошибкой, необходимо проанализировать содержимое таблицы, устранить несоответствия (например, удалить строки с недопустимыми датами) и повторить операцию. Важно: всю миграцию следует завершить до наступления граничной даты '2026-02-16'. В противном случае ограничение можно удалить и начать процедуру заново:

alter table "Logs" drop constraint "Logs_partbound_check";

После успешной валидации выполняется переименование и подключение архивной таблицы как секции:

begin; set statement_timeout to '1s'; alter table "Logs" rename to "Logs_archive"; alter table "Logs2" rename to "Logs"; alter table "Logs" attach partition "Logs_archive" for values from (MINVALUE) to ('2026-02-16'); commit;

До наступления '2026-02-16' все новые данные будут продолжать записываться в секцию Logs_archive (бывшую таблицу Logs). После этой даты запись пойдёт в Logs_20260216. Все запросы к таблице Logs остаются прозрачными для приложений.

Для корректной работы на вновь созданных секциях необходимо воссоздать индексы. Если секций несколько, индексы создаются на каждой:

CREATE INDEX "IX_Logs_20260216_EntityId" ON public."Logs_20260216" USING btree ("EntityId" COLLATE pg_catalog."default" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_20260216_Event" ON public."Logs_20260216" USING btree ("Event" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX IF NOT EXISTS "IX_Logs_20260216_Monitoring" ON public."Logs_20260216" USING btree ("RobotId" DESC NULLS LAST, "OrchTimestampUtc" DESC NULLS LAST, "LogType" ASC NULLS LAST, "Type" DESC NULLS LAST, "OperationKey" ASC NULLS LAST) INCLUDE("RdpUserName") TABLESPACE pg_default; CREATE INDEX IF NOT EXISTS "IX_Logs_20260216_Monitoring_1" ON public."Logs_20260216" USING btree ("Type" DESC NULLS LAST, "LogType" ASC NULLS LAST, "OperationKey" ASC NULLS LAST, "OrchTimestampUtc" DESC NULLS LAST) TABLESPACE pg_default; CREATE INDEX IF NOT EXISTS "IX_Logs_20260216_Monitoring_2" ON public."Logs_20260216" USING btree ("OrchTimestampUtc" DESC NULLS LAST, "RobotId" DESC NULLS LAST, "LogType" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX IF NOT EXISTS "IX_Logs_20260216_Monitoring_3" ON public."Logs_20260216" USING btree ("LogType" ASC NULLS LAST, "OrchTimestampUtc" DESC NULLS LAST, "ProjectId" DESC NULLS LAST, "OperationKey" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_20260216_OperationKey" ON public."Logs_20260216" USING btree ("OperationKey" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_20260216_ProjectId" ON public."Logs_20260216" USING btree ("ProjectId" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_20260216_RobotId" ON public."Logs_20260216" USING btree ("RobotId" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_20260216_RobotId_OperationKey_TimestampUtc" ON public."Logs_20260216" USING btree ("RobotId" ASC NULLS LAST, "OperationKey" ASC NULLS LAST, "TimestampUtc" DESC NULLS LAST) INCLUDE("Type", "LogType") TABLESPACE pg_default; CREATE INDEX "IX_Logs_20260216_RobotKey" ON public."Logs_20260216" USING btree ("RobotKey" COLLATE pg_catalog."default" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_20260216_UserId" ON public."Logs_20260216" USING btree ("UserId" COLLATE pg_catalog."default" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_20260216_WorkerId" ON public."Logs_20260216" USING btree ("WorkerId" ASC NULLS LAST) TABLESPACE pg_default;

1.2. Журнал Оркестратора (таблица OrchEvents)

Аналогичные операции выполняются для таблицы OrchEvents. Создаётся секционированная головная таблица:

CREATE TABLE public."OrchEvents2" ( "Id" uuid NOT NULL, "OrchTimestampUtc" timestamp without time zone NOT NULL, "Event" integer NOT NULL, "EntityId" text COLLATE pg_catalog."default", "UserId" text COLLATE pg_catalog."default", "OperationKey" uuid, "Signature" text COLLATE pg_catalog."default", "WorkerAdminName" text COLLATE pg_catalog."default", "Text" text COLLATE pg_catalog."default", "EventType" integer, "IP" text COLLATE pg_catalog."default", "TenantId" text COLLATE pg_catalog."default", "AssignmentId" integer, "LogType" integer NOT NULL DEFAULT 1, "ProjectId" integer, "ProjectName" text COLLATE pg_catalog."default", "NodeId" integer ) partition by range ("OrchTimestampUtc");

Создаётся будущая секция:

create table "OrchEvents_20260216" partition of "OrchEvents2" for values from ('2026-02-16') to ('2026-03-01');

Подготавливаем существующую таблицу с данными OrchEvents – добавляем в неё ограничение на поле OrchTimestampUtc и проверяем это ограничение на данных таблицы:

Добавляется и проверяется ограничение на архивной таблице:

begin; set local statement_timeout to '1s'; alter table "OrchEvents" add constraint "OrchEvents_partbound_check" check ("OrchTimestampUtc" < '2026-02-16' and "OrchTimestampUtc" is not null) not valid; commit; alter table "OrchEvents" validate constraint "OrchEvents_partbound_check";

Переименование таблицы и подключение секции:

begin; set statement_timeout to '1s'; alter table "OrchEvents" rename to "OrchEvents_archive"; alter table "OrchEvents2" rename to "OrchEvents"; alter table "OrchEvents" attach partition "OrchEvents_archive" for values from (MINVALUE) to ('2026-02-16'); commit;

Создание индексов на секции будущих данных OrchEvents:

CREATE INDEX "IX_OrchEvents_20260216_AllOrchTimestampUtc" ON public."OrchEvents_20260216" USING btree ("TenantId" COLLATE pg_catalog."default" ASC NULLS LAST, "Event" ASC NULLS LAST, "UserId" COLLATE pg_catalog."default" ASC NULLS LAST, "EventType" ASC NULLS LAST, "OrchTimestampUtc" DESC NULLS LAST) INCLUDE("EntityId", "OperationKey", "Signature", "WorkerAdminName", "Text", "IP", "AssignmentId", "LogType") TABLESPACE pg_default; CREATE INDEX "IX_OrchEvents_20260216_EntityId_OrchTimestampUtc" ON public."OrchEvents_20260216" USING btree ("EntityId" COLLATE pg_catalog."default" ASC NULLS LAST, "OrchTimestampUtc" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_OrchEvents_20260216_Event_OrchTimestampUtc" ON public."OrchEvents_20260216" USING btree ("Event" ASC NULLS LAST, "OrchTimestampUtc" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_OrchEvents_20260216_UserId_OrchTimestampUtc" ON public."OrchEvents_20260216" USING btree ("UserId" COLLATE pg_catalog."default" ASC NULLS LAST, "OrchTimestampUtc" ASC NULLS LAST) TABLESPACE pg_default;

При необходимости можно заранее создать несколько будущих секций. Последующее автоматическое создание и удаление секций реализуется с помощью планировщика.

2. Автоматическое создание новых секций

Для автоматического обслуживания используются расширение pg_cron и хранимая процедура.

2.1. Установка и настройка pg_cron

Установочный пакет копируется в доступную директорию и устанавливается (на примере дистрибутивов на базе RHEL 8, PostgreSQL 13):

# cd /srv/samba/shared/install/postgresql-13

Устанавливаем пакет:

# dnf -y install 'pg_cron_13-1.3.0-1.rhel8.x86_64.rpm'

В файл postgresql.conf вносятся изменения:

  • В параметр shared_preload_libraries добавляется (или дописывается) pg_cron:

    # vim /var/lib/pgsql/13/data/postgresql.conf shared_preload_libraries = 'pg_cron'

    alt

  • В секции настроек планировщика включается фильтрация секций (если ещё не включена):

    enable_partition_pruning = on

    alt

  • Указывается целевая база данных для cron-заданий:

    cron.database_name = 'ltoolslogs'

    alt

  • При необходимости задаётся часовой пояс, отличный от GMT, например:

    cron.timezone = 'Asia/Omsk'

    alt

Для беспарольного подключения локальных задач редактируется pg_hba.conf:

# vim /var/lib/pgsql/13/data/pg_hba.conf host all all 127.0.0.1/32 trust host all all ::1/128 trust

alt

После сохранения изменений служба PostgreSQL перезапускается:

# systemctl restart postgresql-13

Расширение активируется в БД ltoolslogs. Можно через какой-то менеджер для работы с PostgreSQL, например, pgAdmin4. Или из командной строки:

# sudo -i -u postgres psql -d ltoolslogs -c 'CREATE EXTENSION pg_cron'

Факт создания схемы cron и служебных объектов проверяется визуально.

alt

2.2. Процедура автоматического создания секций

В БД ltoolslogs создаётся процедура, формирующая секции на следующий месяц для таблиц Logs и OrchEvents:

CREATE OR REPLACE PROCEDURE public.create_logs_part( ) LANGUAGE 'plpgsql' AS $BODY$ DECLARE y integer; m integer; d1 timestamp; d2 timestamp; part varchar(100); part_first varchar(100); part_last varchar(100); BEGIN y := date_part('year', CURRENT_DATE); m := date_part('month', CURRENT_DATE) + 1; IF m > 12 THEN y := y + 1; m := 1; END IF; d1 = make_date(y, m, 1); IF m = 12 THEN d2 = make_date(y + 1, 1, 1); ELSE d2 = make_date(y, m + 1, 1); END IF; RAISE INFO 'Start checking and creating sections for [Logs] and [OrchEvents] from [%] to [%]...', d1, d2; part := format('%s%s', y, CASE WHEN m > 9 THEN format('%s', m) ELSE format('0%s', m) END); part_first := to_char(d1, 'yyyy-mm-dd'); part_last := to_char(d2, 'yyyy-mm-dd'); RAISE INFO 'Check if exists partition [Logs_%].', part; if NOT EXISTS ( SELECT FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = format('Logs_%s', part)) then RAISE INFO 'Creating [Logs_%] as partition of [Logs] from [%] to [%].', part, part_first, part_last; EXECUTE format('create table "Logs_%s" partition of "Logs" for values from (''%s'') to (''%s'')', part, part_first, part_last); EXECUTE format('CREATE INDEX "IX_Logs_%1$s_EntityId" ON "Logs_%1$s" USING btree ("EntityId" COLLATE pg_catalog."default" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_%1$s_Event" ON public."Logs_%1$s" USING btree ("Event" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_%1$s_Monitoring" ON public."Logs_%1$s" USING btree ("RobotId" DESC NULLS LAST, "OrchTimestampUtc" DESC NULLS LAST, "LogType" ASC NULLS LAST, "Type" DESC NULLS LAST, "OperationKey" ASC NULLS LAST) INCLUDE("RdpUserName") TABLESPACE pg_default; CREATE INDEX "IX_Logs_%1$s_Monitoring_1" ON public."Logs_%1$s" USING btree ("Type" DESC NULLS LAST, "LogType" ASC NULLS LAST, "OperationKey" ASC NULLS LAST, "OrchTimestampUtc" DESC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_%1$s_Monitoring_2" ON public."Logs_%1$s" USING btree ("OrchTimestampUtc" DESC NULLS LAST, "RobotId" DESC NULLS LAST, "LogType" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_%1$s_Monitoring_3" ON public."Logs_%1$s" USING btree ("LogType" ASC NULLS LAST, "OrchTimestampUtc" DESC NULLS LAST, "ProjectId" DESC NULLS LAST, "OperationKey" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_%1$s_OperationKey" ON "Logs_%1$s" USING btree ("OperationKey" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_%1$s_ProjectId" ON "Logs_%1$s" USING btree ("ProjectId" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_%1$s_RobotId" ON "Logs_%1$s" USING btree ("RobotId" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_%1$s_RobotId_OperationKey_TimestampUtc" ON "Logs_%1$s" USING btree ("RobotId" ASC NULLS LAST, "OperationKey" ASC NULLS LAST, "TimestampUtc" DESC NULLS LAST) INCLUDE("Type", "LogType") TABLESPACE pg_default; CREATE INDEX "IX_Logs_%1$s_RobotKey" ON "Logs_%1$s" USING btree ("RobotKey" COLLATE pg_catalog."default" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_%1$s_UserId" ON "Logs_%1$s" USING btree ("UserId" COLLATE pg_catalog."default" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_Logs_%1$s_WorkerId" ON "Logs_%1$s" USING btree ("WorkerId" ASC NULLS LAST) TABLESPACE pg_default;', part); else RAISE INFO 'No new sections for [Logs] from [%] to [%] was created.', part_first, part_last; end if; RAISE INFO 'Check if exists partition [OrchEvents_%]...', part; if NOT EXISTS ( SELECT FROM pg_catalog.pg_class c JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relname = format('OrchEvents_%s', part)) then RAISE INFO 'Creating section [OrchEvents_%] for table [OrchEvents] from [%] to [%].', part, part_first, part_last; EXECUTE format('create table "OrchEvents_%s" partition of "OrchEvents" for values from (''%s'') to (''%s'')', part, part_first, part_last); EXECUTE format('CREATE INDEX "IX_OrchEvents_%1$s_AllOrchTimestampUtc" ON "OrchEvents_%1$s" USING btree ("TenantId" COLLATE pg_catalog."default" ASC NULLS LAST, "Event" ASC NULLS LAST, "UserId" COLLATE pg_catalog."default" ASC NULLS LAST, "EventType" ASC NULLS LAST, "OrchTimestampUtc" DESC NULLS LAST) INCLUDE("EntityId", "OperationKey", "Signature", "WorkerAdminName", "Text", "IP", "AssignmentId", "LogType") TABLESPACE pg_default; CREATE INDEX "IX_OrchEvents_%1$s_EntityId_OrchTimestampUtc" ON "OrchEvents_%1$s" USING btree ("EntityId" COLLATE pg_catalog."default" ASC NULLS LAST, "OrchTimestampUtc" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_OrchEvents_%1$s_Event_OrchTimestampUtc" ON "OrchEvents_%1$s" USING btree ("Event" ASC NULLS LAST, "OrchTimestampUtc" ASC NULLS LAST) TABLESPACE pg_default; CREATE INDEX "IX_OrchEvents_%1$s_UserId_OrchTimestampUtc" ON "OrchEvents_%1$s" USING btree ("UserId" COLLATE pg_catalog."default" ASC NULLS LAST, "OrchTimestampUtc" ASC NULLS LAST) TABLESPACE pg_default;', part); else RAISE INFO 'No new sections for [OrchEvents] from [%] to [%] was created.', part_first, part_last; end if; END; $BODY$;

2.3. Планирование задания

Создаётся задание pg_cron, которое будет еженедельно (в примере — каждое воскресенье в 01:00) вызывать процедуру. При необходимости расписание корректируется.

select cron.schedule('monthly-add-next-sections', '0 1 * * 0', 'CALL public.create_logs_part();');

Для изменения задания достаточно удалить соответствующую запись из таблицы cron."job" и создать новую.

alt

При необходимости процедуру можно выполнить вручную, минуя планировщик:

CALL public.create_logs_part();

alt

Процедура всегда предварительно проверяет наличие секций на следующий месяц и создаёт их только при отсутствии.

3. Результат

После выполнения описанных действий таблицы Logs и OrchEvents становятся секционированными. Планировщик PostgreSQL использует фильтрацию секций (partition pruning) и индексы отдельных разделов, что позволяет значительно повысить производительность работы Оркестратора.

alt