Перейти к содержимому

05 Занятие 05: Установка и сопровождение / Планировщик заданий

Исходная страница в нативном Markdown: Установка и сопровождение / Планировщик заданий
Примечание

Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета. HTML source: pgpro_05_scheduler.html Archive: PGPRO-handouts-16.zip Страниц PDF: 24

Page 1: Установка и сопровождение / Планировщик заданий

Авторские права

© Postgres Professional, 2023–2026 Авторы: Алексей Береснев, Илья Баштанов, Павел Толмачев, Игорь Гнатюк Фото: Олег Бартунов (монастырь Пху и пик Бхрикути, Непал)

Использование материалов курса

Некоммерческое использование материалов курса (презентации, демонстрации) разрешается без ограничений. Коммерческое использование возможно только с письменного разрешения компании Postgres Professional. Запрещается внесение изменений в материалы курса.

Обратная связь

Отзывы, замечания и предложения направляйте по адресу: edu@postgrespro.ru

Отказ от ответственности

Компания Postgres Professional не несет никакой ответственности за любые повреждения и убытки, включая потерю дохода, нанесенные прямым или непрямым, специальным или случайным использованием материалов курса. Компания Postgres Professional не предоставляет каких-либо гарантий на материалы курса. Материалы курса предоставляются на основе принципа «как есть» и компания Postgres Professional не обязана предоставлять сопровождение, поддержку, обновления, расширения и изменения.

Page 2: Темы

  • Назначение
  • Архитектура
  • Периодические задания
  • Управление и наблюдение
  • Разовые задания

Page 3: Назначение

Расширение pgpro\_scheduler

  • планировщик периодических и разовых заданий мониторинг разграничение прав Задание — команды SQL
    • в отдельных транзакциях или все в одной

Комментарии преподавателя

Планировщик заданий требуется для периодического запуска заданий по расписанию, а также для запуска разовых отложенных заданий. В Postgres Pro планировщик реализован в виде расширения pgpro_scheduler.

С его помощью можно планировать регулярные задачи администрирования без доступа к файловой системе сервера и без использования внешнего программного обеспечения.

Задание планировщика состоит из команд SQL, которые могут быть выполнены как в одной транзакции, так и в разных.

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

https://postgrespro.ru/docs/enterprise/16/pgpro-scheduler

Page 4: Архитектура

Source slide page 4: Архитектура{#source-figure-004 caption=“Source slide page 4: Архитектура”}

  • …
  • ↑

Комментарии преподавателя

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

Основной процесс-диспетчер планировщика (один на весь кластер баз данных) запускается функцией schedule.enable, а для автоматического его запуска при старте сервера нужно установить параметр schedule.auto_enabled = true.

Диспетчерский процесс проверяет настройки и для каждой из указанных в параметре schedule.database баз данных запускает отдельный процесс-менеджер (для показанной на слайде конфигурации нужно задать значение ‘DB1,DB2’). Также сразу запускаются обработчики разовых заданий планировщика, их количество ограничено значением schedule.max_parallel_workers.

Максимальное количество обработчиков для планируемых заданий в каждой базе ограничено параметром schedule.max_workers.

Оба ограничения можно задавать на уровне базы данных командой ALTER DATABASE. Обработчики периодических и разовых заданий работают независимо.

При этом общее количество рабочих процессов сервера ограничено параметром max_worker_processes, его значение должно учитывать потребности и других задач, использующих фоновые процессы.

Если нет свободных процессов для выполнения, задание будет ожидать освобождения обработчика. По умолчанию время ожидания не ограничено, поэтому, планируя задание, разумно указывать ограничение времени, при превышении которого pgpro_scheduler отменит выполнение.

Page 5: Установка расширения pgpro_scheduler

Создадим базу данных:

=> CREATE DATABASE pgpro_scheduler;
CREATE DATABASE

Для работы расширения необходимо подключить библиотеку:

=> ALTER SYSTEM SET shared_preload_libraries = 'pgpro_scheduler';
ALTER SYSTEM

Общее ограничение на количество фоновых рабочих процессов в системе, значения по умолчанию достаточно для работы с одной базой:

=> SHOW max_worker_processes;
 max_worker_processes
----------------------
 8
(1 row)

Подключение библиотеки требует перезапуска СУБД.

student$ sudo systemctl restart postgrespro-ent-16.service
student$ psql

Установим расширение pgpro_scheduler в базе данных, в которой оно будет использоваться.

=> \c pgpro_scheduler
You are now connected to database "pgpro_scheduler" as user "student".
=> CREATE EXTENSION pgpro_scheduler;
CREATE EXTENSION

Объекты расширения находятся в схеме schedule. Например, представления для мониторинга разовых заданий:

=> \dv schedule.*
             List of relations
  Schema  |      Name      | Type |  Owner
----------+----------------+------+---------
 schedule | all_job_status | view | student
 schedule | all_jobs_log   | view | student
 schedule | job_status     | view | student
 schedule | jobs_log       | view | student
(4 rows)

Настройка

Укажем имена баз данных, для которых будут запускаться задания:

=> ALTER SYSTEM SET schedule.database = 'pgpro_scheduler';
ALTER SYSTEM

Применим изменение конфигурации.

=> SELECT pg_reload_conf();
 pg_reload_conf
----------------
 t
(1 row)

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

=> ALTER DATABASE pgpro_scheduler SET schedule.max_workers = 3;
ALTER DATABASE

Page 6: Page 6

Ограничение на число рабочих процессов, выполняющих разовые задания:

=> ALTER DATABASE pgpro_scheduler SET schedule.max_parallel_workers = 3;
ALTER DATABASE

Включим планировщик заданий:

=> SELECT schedule.enable();
NOTICE:  the number of allowed background workers could be insufficient for normal work
HINT:  With the current configuration, the pgpro_scheduler extension may start up to 8
background worker processes. You might need to increase "max_worker_processes" at least
to 10.
 enable
--------
 t
(1 row)

Состояние процессов планировщика заданий:

=> SELECT * FROM schedule.status();
 pid |    database     |       type
-----+-----------------+------------------
     | pgpro_scheduler | database manager
(1 row)

Информация об этих процессах доступна и в pg_stat_activity:

=> SELECT pid, datname, application_name, backend_type, query
FROM pg_stat_activity
WHERE application_name ~ '^pgp-s' \gx
(0 rows)

Page 7: Периодические задания

Команды SQL

  • выполняются в одной или в нескольких транзакциях
  • Расписание
    • в формате cron
    • массив дат
    • объект JSON
    • выражение SQL ­— время следующего запуска
    • диапазон времени
  • Дополнительные атрибуты
    • пользователь, длительность, время ожидания, обработчик ошибок

Комментарии преподавателя

Функция schedule.create_job позволяет запланировать периодическое задание. Параметры планирования задаются в виде одного объекта jsonb.

Атрибут commands задает одну или несколько команд для выполнения. Если задать команды в виде символьной строки, они выполнятся в одной транзакции. Если же задан массив строк, то каждому элементу массива будет соответствовать отдельная транзакция.

Расписание для задания задается атрибутами:

● cron — в формате crontab;

● dates — значение или массив значений типа timestamptz;

● rule — объектом jsonb.

Атрибутами start_date и end_date можно ограничить время, в которое задание может выполняться; вне диапазона работающее задание будет остановлено, а новое не запустится.

Время следующего запуска задания будет определяться динамически, если в атрибуте next_time_statement задать SQL-выражение. Оно вычисляется после каждого выполнения задания.

Также можно запланировать выполнение задания с правами другого пользователя (атрибут run_as), ограничить длительность выполнения и ожидания, подключить обработчик ошибок в виде SQL-команды.

https://postgrespro.ru/docs/enterprise/16/pgpro-scheduler#PGPRO-SCHED ULER-USAGE

Page 8: Управление и мониторинг

Управление

  • изменение атрибутов
  • активация
  • деактивация
  • удаление
  • Мониторинг
    • свойства задания
    • список заданий (всех, выполняющихся, выполненных)
    • список запланированных выполнений
    • очистка журнала

Комментарии преподавателя

Расширение pgpro_scheduler использует функции в схеме schedule для управления периодическими заданиями и их мониторинга:

● set_job_attribute() — изменение одного атрибута задания;

● set_job_attributes() — изменение нескольких атрибутов задания;

● deactivate_job — деактивировать задание;

● activate_job — активировать задание;

● drop_job — удалить задание;

● get_job — информация о задании;

● get_owned_cron — задания пользователя;

● get_cron — список заданий, выполняемых пользователем;

● get_active_jobs — список выполняющихся заданий;

● get_log — список всех завершенных заданий;

● get_user_log — список завершенных заданий пользователя;

● clean_log — удалить все записи о завершенных заданиях.

Имеется функция timetable, которая возвращает таблицу с описанием всех запланированных выполнений заданий (многократно повторяемых и разовых), попадающих в заданный диапазон времени.

Page 9: Периодические задания

Создадим таблицу:

=> CREATE TABLE t (
  bknd int DEFAULT pg_backend_pid(),
  txid bigint DEFAULT txid_current(),
  ttime timestamptz DEFAULT now()
);
CREATE TABLE

А теперь создадим периодическое задание, используя формат crontab. Формат использует пять полей для идентификации времени: минуты часа (0-59); часы (0-23); дни месяца (1-31); месяц (1-12 или трехбуквенное сокращение); дни недели (0 — воскресенье … 6 — суббота, или трехбуквенные сокращения). Если в поле после звездочки через косую черту указано число — это шаг выполнения. Например, */10 * * * * означает «раз в десять минут». Расширенный формат cron использует шесть полей с секундами. Например, */30 * * * * * означает «раз в тридцать секунд». Запланируем задание на выполнение раз в минуту:

=> SELECT schedule.create_job(
  '{ "commands": "INSERT INTO t DEFAULT VALUES",
     "cron": "*/1 * * * *" }'
);
 create_job
------------
          1
(1 row)

Получим информацию о запланированном задании:

=> SELECT id, commands, run_as, active
FROM schedule.get_cron();
 id |             commands             | run_as  | active
----+----------------------------------+---------+--------
  1 | {"INSERT INTO t DEFAULT VALUES"} | student | t
(1 row)

Функция get_job информирует о задании, возвращая значение типа cron_rec. Проверим поле rule, описывающее расписание, преобразовав расписание из представления JSONB в текстовые строки и урезав их по ширине:

=> SELECT substring(jsonb_each_text(rule)::text FOR 75) AS rule
FROM schedule.get_job(1);
                                    rule
-----------------------------------------------------------------------------
 (days,"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
 (hours,"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
 (wdays,"[0, 1, 2, 3, 4, 5, 6]")
 (months,"[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]")
 (crontab,"*/1 * * * *")
 (minutes,"[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
 (seconds,[0])
(7 rows)

Page 10: Page 10

Через минуту сработает первое задание. Подождем… Проверим, что было добавлено в таблицу t:

=> SELECT * FROM t;
  bknd  | txid |             ttime
--------+------+-------------------------------
 797284 |  971 | 2026-07-27 23:36:00.048241+03
(1 row)

Функция timetable информирует о выполненных и запланированных заданиях. Получим список заданий за 5 минут:

=> SELECT id, type, commands, scheduled_at, status
FROM schedule.timetable(
  now() - interval '3 minutes',
  now() + interval '2 minutes'
)
ORDER BY scheduled_at;
 id |    type    |             commands             |      scheduled_at      |  status
----+------------+----------------------------------+------------------------+-----------
  1 | periodical | {"INSERT INTO t DEFAULT VALUES"} | 2026-07-27 23:36:00+03 | done
  1 | periodical | {"INSERT INTO t DEFAULT VALUES"} | 2026-07-27 23:37:00+03 | submitted
  1 | periodical | {"INSERT INTO t DEFAULT VALUES"} | 2026-07-27 23:38:00+03 | submitted
(3 rows)

Отчет о выполненных заданиях планировщика:

=> SELECT cron, scheduled_at, started, status, message
FROM schedule.get_log();
 cron |      scheduled_at      |            started            | status | message
------+------------------------+-------------------------------+--------+---------
    1 | 2026-07-27 23:36:00+03 | 2026-07-27 23:36:00.036693+03 | done   |
(1 row)

Управление периодическими заданиями

До сих пор задание выполнялось в ноль секунд каждой минуты:

=> SELECT jsonb_array_elements(rule->'seconds') FROM schedule.get_job(1);
 jsonb_array_elements
----------------------
 0
(1 row)

Пусть задание выполняется чаще — раз в десять секунд. Используем расширенный формат cron из шести полей, где первое поле — секунды:

=> SELECT schedule.set_job_attribute(1, 'cron','*/10 * * * * *');
 set_job_attribute
-------------------
 t
(1 row)

Проверим в rule: задание должно быть запланировано для исполнения с периодичностью раз в десять секунд.

=> SELECT jsonb_array_elements(rule->'seconds') FROM schedule.get_job(1);
 jsonb_array_elements
----------------------
 0
 10
 20
 30
 40
 50
(6 rows)

Подождем немного… Проверим, что было добавлено в таблицу t:

Page 11: Page 11

=> SELECT * FROM t;
  bknd  | txid |             ttime
--------+------+-------------------------------
 797284 |  971 | 2026-07-27 23:36:00.048241+03
 797640 |  975 | 2026-07-27 23:36:10.016049+03
(2 rows)

Временно приостановим задание. Новые записи не будут попадать в таблицу.

=> SELECT schedule.deactivate_job(1);
 deactivate_job
----------------
 t
(1 row)

Проверим состояние задания — оно должно быть не активно:

=> SELECT id, commands, run_as, active FROM schedule.get_job(1);
 id |             commands             | run_as  | active
----+----------------------------------+---------+--------
  1 | {"INSERT INTO t DEFAULT VALUES"} | student | f
(1 row)

Подождем немного… Снова запустим задание и проверим состояние:

=> SELECT schedule.activate_job(1);
 activate_job
--------------
 t
(1 row)
=> SELECT id, commands, run_as, active FROM schedule.get_job(1);
 id |             commands             | run_as  | active
----+----------------------------------+---------+--------
  1 | {"INSERT INTO t DEFAULT VALUES"} | student | t
(1 row)

Подождем еще немного… Проверим записи в таблице t:

=> SELECT * FROM t;
  bknd  | txid |             ttime
--------+------+-------------------------------
 797284 |  971 | 2026-07-27 23:36:00.048241+03
 797640 |  975 | 2026-07-27 23:36:10.016049+03
 797887 |  980 | 2026-07-27 23:36:40.012964+03
 797890 |  983 | 2026-07-27 23:36:50.01538+03
(4 rows)

Выполненные задания отображаются в журнале:

=> SELECT cron, scheduled_at, started, status, message
FROM schedule.get_log() WHERE cron = 1;
 cron |      scheduled_at      |            started            | status | message
------+------------------------+-------------------------------+--------+---------
    1 | 2026-07-27 23:36:00+03 | 2026-07-27 23:36:00.036693+03 | done   |
    1 | 2026-07-27 23:36:10+03 | 2026-07-27 23:36:10.008657+03 | done   |
    1 | 2026-07-27 23:36:40+03 | 2026-07-27 23:36:40.007297+03 | done   |
    1 | 2026-07-27 23:36:50+03 | 2026-07-27 23:36:50.008664+03 | done   |
(4 rows)

Снова приостановим задание и очистим таблицу.

=> SELECT schedule.deactivate_job(1);

Page 12: Команды в отдельных транзакциях

 deactivate_job
----------------
 t
(1 row)
=> DELETE FROM t;
DELETE 4

В атрибуте commands значения могут задаваться в виде текста или массива. Если отдельные команды SQL заданы текстовой строкой через точку с запятой, то все команды задания будут выполнены в одной транзакции. Если команды SQL заданы JSON-массивом, то каждая из них будет выполнена в отдельной транзакции. Создадим задание для заполнения таблицы t раз в десять секунд. Каждая SQL-команда выполняется в отдельной транзакции. Значения now() для каждой транзакции — свои, как и идентификаторы транзакции.

=> SELECT schedule.create_job(
  '{ "commands": [
       "INSERT INTO t DEFAULT VALUES",
       "SELECT pg_sleep(5)",
       "INSERT INTO t DEFAULT VALUES"
     ],
     "cron": "*/10 * * * * *" }'
);
 create_job
------------
          2
(1 row)

Снова немного подождем…

=> SELECT schedule.drop_job(2);
 drop_job
----------
 t
(1 row)

Проверим, что было добавлено в таблицу:

=> SELECT * FROM t;
  bknd  | txid |             ttime
--------+------+-------------------------------
 798536 |  989 | 2026-07-27 23:37:00.01162+03
 798536 |  991 | 2026-07-27 23:37:05.024718+03
(2 rows)

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

Команды в одной транзакции

Даже если команды заданы массивом, можно задать атрибуту use_same_transaction значение true, и все команды будут выполнены в одной транзакции. Удалим все записи в таблице.

=> DELETE FROM t;
DELETE 2

В одной транзакции значения времени, которые возвращает функция now(), будут одинаковыми.

Page 13: Page 13

=> SELECT schedule.create_job(
  '{ "commands": [
       "INSERT INTO t DEFAULT VALUES",
       "SELECT pg_sleep(5)",
       "INSERT INTO t DEFAULT VALUES"
     ],
     "cron": "*/10 * * * * *",
     "use_same_transaction": true}'
);
 create_job
------------
          3
(1 row)

Подождем чуть-чуть… Удалим задание и проверим, что было добавлено в таблицу.

=> SELECT schedule.drop_job(3);
 drop_job
----------
 t
(1 row)
=> SELECT * FROM t;
  bknd  | txid |             ttime
--------+------+-------------------------------
 799236 |  997 | 2026-07-27 23:37:10.010298+03
 799236 |  997 | 2026-07-27 23:37:10.010298+03
(2 rows)

Все три оператора задания выполнились в одной транзакции. Снова очистим таблицу и запустим первое задание.

=> DELETE FROM t;
DELETE 2
=> SELECT schedule.activate_job(1);
 activate_job
--------------
 t
(1 row)

Page 14: Разовые задания

  • работает независимо
  • от периодических
  • Выполнить задание
    • немедленно
    • в определенный момент
    • после завершения других заданий
  • Назначить повторное выполнение
  • Отменить еще не выполняющееся задание

Комментарии преподавателя

Функция submit_job создает разовое задание. По умолчанию задание будет выполнено немедленно. Можно задать время запуска параметром run_after. Также можно в параметре depends_on передать массив идентификаторов других заданий — в этом случае данное задание будет выполнено сразу после их завершения.

Функция resubmit, вызванная в SQL-запросе задания, позволяет задать время следующего запуска этого задания.

Функция cancel_job отменяет задание. Если задание уже выполняется, оно не будет прервано, но повторно назначить его будет нельзя.

Получить список выполненных и запланированных заданий (как периодических, так и разовых) позволяет функция timetable.

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

Page 15: Разовые задания

Запланируем разовое задание, удаляющее через десять секунд таблицу t:

=> SELECT schedule.submit_job(
  'DROP TABLE t',
  run_after := now() + interval '10 seconds'
);
 submit_job
------------
          1
(1 row)

Таблица t пока на месте:

=> SELECT * FROM t;
 bknd | txid | ttime
------+------+-------
(0 rows)

Подождем немного…

=> SELECT * FROM t;
ERROR:  relation "t" does not exist
LINE 1: SELECT * FROM t;
                      ^

Таблица уже удалена. В таблице заданий выводится информация и о разовых заданиях:

=> SELECT type, commands, scheduled_at, status
FROM schedule.timetable(
  now() - interval '1 minutes',
  now()
)
WHERE id = 1
ORDER BY scheduled_at;
    type    |             commands             |         scheduled_at          | status
------------+----------------------------------+-------------------------------+--------
 periodical | {"INSERT INTO t DEFAULT VALUES"} | 2026-07-27 23:36:40+03        | done
 periodical | {"INSERT INTO t DEFAULT VALUES"} | 2026-07-27 23:36:50+03        | done
 periodical | {"INSERT INTO t DEFAULT VALUES"} | 2026-07-27 23:37:20+03        | done
 onetime    | {"DROP TABLE t"}                 | 2026-07-27 23:37:25.645247+03 | done
(4 rows)

Свойство onrollback периодического задания задает действия при сбое команды задания — будем восстанавливать таблицу, предполагая ее отсутствие:

=> SELECT schedule.set_job_attribute(1, 'onrollback',
  'CREATE TABLE t (
     bknd int DEFAULT pg_backend_pid(),
     txid bigint DEFAULT txid_current(),
     ttime timestamptz DEFAULT now()
   )'
);
 set_job_attribute
-------------------
 t
(1 row)

Проверим состояние задания:

=> SELECT id, commands, run_as, active
FROM schedule.get_job(1);

Page 16: Page 16

 id |             commands             | run_as  | active
----+----------------------------------+---------+--------
  1 | {"INSERT INTO t DEFAULT VALUES"} | student | t
(1 row)

Подождем еще немного… Теперь таблица восстановлена и пока пуста:

=> SELECT * FROM t;
 bknd | txid | ttime
------+------+-------
(0 rows)

Подождем еще… Теперь таблица уже не пуста:

=> SELECT * FROM t;
  bknd  | txid |             ttime
--------+------+-------------------------------
 801308 | 1020 | 2026-07-27 23:37:40.010017+03
(1 row)

Посмотрим список завершенных заданий:

=> SELECT scheduled_at, commands, started, status, message
FROM schedule.get_log()
WHERE cron = 1 \gx
-[ RECORD 1 ]+-------------------------------------------------
scheduled_at | 2026-07-27 23:36:00+03
commands     | {"INSERT INTO t DEFAULT VALUES"}
started      | 2026-07-27 23:36:00.036693+03
status       | done
message      |
-[ RECORD 2 ]+-------------------------------------------------
scheduled_at | 2026-07-27 23:36:10+03
commands     | {"INSERT INTO t DEFAULT VALUES"}
started      | 2026-07-27 23:36:10.008657+03
status       | done
message      |
-[ RECORD 3 ]+-------------------------------------------------
scheduled_at | 2026-07-27 23:36:40+03
commands     | {"INSERT INTO t DEFAULT VALUES"}
started      | 2026-07-27 23:36:40.007297+03
status       | done
message      |
-[ RECORD 4 ]+-------------------------------------------------
scheduled_at | 2026-07-27 23:36:50+03
commands     | {"INSERT INTO t DEFAULT VALUES"}
started      | 2026-07-27 23:36:50.008664+03
status       | done
message      |
-[ RECORD 5 ]+-------------------------------------------------
scheduled_at | 2026-07-27 23:37:20+03
commands     | {"INSERT INTO t DEFAULT VALUES"}
started      | 2026-07-27 23:37:20.002598+03
status       | done
message      |
-[ RECORD 6 ]+-------------------------------------------------
scheduled_at | 2026-07-27 23:37:30+03
commands     | {"INSERT INTO t DEFAULT VALUES"}
started      | 2026-07-27 23:37:30.009847+03
status       | error
message      | error in command #1: relation "t" does not exist
-[ RECORD 7 ]+-------------------------------------------------
scheduled_at | 2026-07-27 23:37:40+03
commands     | {"INSERT INTO t DEFAULT VALUES"}
started      | 2026-07-27 23:37:40.002633+03
status       | done
message      |

Удалим запланированное задание:

=> SELECT schedule.drop_job(1);

Page 17: Page 17

 drop_job
----------
 t
(1 row)

Page 18: Итоги

Расширение pgpro\_scheduler позволяет планировать задания

  • Задания выполняются либо периодически по расписанию, либо один раз в запланированное время
  • Задания принадлежат пользователям и выполняются от их имени
  • Каждое задание выполняется в отдельном процессе
  • Имеются средства управления заданиями и их мониторинга

Page 19: Практика

1. Подготовьте систему к работе с расширениями pgpro\_scheduler и pg\_stat\_statements. 2. Установите расширения pgpro\_scheduler и pg\_stat\_statements в базу данных и настройте их.

    1. Запланируйте задание, собирающее статистику по наиболее часто выполняемым запросам каждые 20 секунд, а также разовое задание, копирующее набранную статистику в файл.

Комментарии преподавателя

  1. Для установки расширений потребуется изменить значение параметра shared_preload_libraries и перезагрузить сервер.

  2. Подключив расширения с помощью CREATE EXTENSION, настройте число рабочих процессов по аналогии с демонстрацией и запустите планировщик.

  3. Для сбора статистики используйте расширение pg_stat_statements и таблицу с такой же структурой, как у представления pg_stat_statements, но с дополнительным полем, в которое будет записываться момент сбора статистики. Ограничьте время работы периодического задания одной минутой с помощью атрибута end_date в аргументе функции schedule.create_job.

Page 20: 1. Подготовка СУБД для работы с расширениями pgpro_scheduler и pg_stat_statements

student$ psql

Создадим базу данных pgpro_scheduler:

=> CREATE DATABASE pgpro_scheduler;
CREATE DATABASE

Подключим разделяемые библиотеки:

=> ALTER SYSTEM SET shared_preload_libraries = pg_stat_statements, pgpro_scheduler;
ALTER SYSTEM
=> ALTER SYSTEM SET track_io_timing = 'on';
ALTER SYSTEM

Общее ограничение на количество параллельных процессов:

=> SHOW max_worker_processes;
 max_worker_processes
----------------------
 8
(1 row)

Перезапустим СУБД.

student$ sudo systemctl restart postgrespro-ent-16.service
student$ psql pgpro_scheduler

2. Подключение расширений. Запуск планировщика

Установим расширения:

=> CREATE EXTENSION pgpro_scheduler;
CREATE EXTENSION
=> CREATE EXTENSION pg_stat_statements;
CREATE EXTENSION

База данных для нагрузочного тестирования:

=> ALTER SYSTEM SET schedule.database = 'pgpro_scheduler';
ALTER SYSTEM

Применим изменения конфигурации:

=> SELECT pg_reload_conf();
 pg_reload_conf
----------------
 t
(1 row)

Включим планировщик заданий:

=> SELECT schedule.enable();
 enable
--------
 t
(1 row)

Состояние процессов планировщика заданий:

=> SELECT * FROM schedule.status();

Page 21: 3. Планирование задач

 pid |    database     |       type
-----+-----------------+------------------
     | pgpro_scheduler | database manager
(1 row)

Создадим таблицу для сохранения выборок статистики:

=> CREATE TABLE stat_tab (LIKE pg_stat_statements);
CREATE TABLE
=> ALTER TABLE stat_tab
  ADD COLUMN time timestamp with time zone DEFAULT now();
ALTER TABLE

Запланируем копирование статистики в таблицу stat_tab раз в 20 секунд, причем периодическое задание должно работать одну минуту:

=> SELECT schedule.create_job(
  ('{
    "commands": "INSERT INTO stat_tab SELECT * FROM pg_stat_statements ORDER BY calls DESC LIMIT 3;",
    "cron": "*/20 * * * * *",
    "end_date": "'||(now()+'1 minute'::interval)||'"
  }')::jsonb
);
 create_job
------------
          1
(1 row)

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

student$ sudo rm -f /tmp/rep.stat
=> SELECT schedule.submit_job(
  'COPY stat_tab TO ''/tmp/rep.stat''',
  run_after => now() + interval '1 minutes'
);
 submit_job
------------
          1
(1 row)

Подождем минуту… Проверим выполненные задания:

=> SELECT cron, scheduled_at, started, status, message
FROM schedule.get_log();
 cron |      scheduled_at      |            started            | status | message
------+------------------------+-------------------------------+--------+---------
    1 | 2026-07-28 00:09:40+03 | 2026-07-28 00:09:40.00418+03  | done   |
    1 | 2026-07-28 00:10:00+03 | 2026-07-28 00:10:00.01965+03  | done   |
    1 | 2026-07-28 00:10:20+03 | 2026-07-28 00:10:20.019042+03 | done   |
(3 rows)

В таблице собраны сведения о наиболее частых запросах:

=> SELECT time, queryid, calls FROM stat_tab;
             time              |       queryid        | calls
-------------------------------+----------------------+-------
 2026-07-28 00:09:40.058619+03 |  3454082965944108383 |    30
 2026-07-28 00:09:40.058619+03 |  6968726711676132615 |     2
 2026-07-28 00:09:40.058619+03 | -6449100197244153197 |     1
 2026-07-28 00:10:00.028057+03 |  3454082965944108383 |    68
 2026-07-28 00:10:00.028057+03 | -6449100197244153197 |     2
 2026-07-28 00:10:00.028057+03 |  6968726711676132615 |     2
 2026-07-28 00:10:20.026808+03 |  3454082965944108383 |   108
 2026-07-28 00:10:20.026808+03 |  -425920193254469305 |     3
 2026-07-28 00:10:20.026808+03 |  6968726711676132615 |     3
(9 rows)

Page 22: Уборка

Разовое задание скопировало таблицу в файл:

student$ sudo ls -l /tmp/rep.stat
-rw-r--r-- 1 postgres postgres 4496 июл 28 00:10 /tmp/rep.stat

Удалим созданную базу данных и файл, сбросим настройки параметров конфигурации.

=> \c student
You are now connected to database "student" as user "student".
=> DROP DATABASE pgpro_scheduler WITH (FORCE);
DROP DATABASE
student$ sudo rm -f /tmp/rep.stat
=> ALTER SYSTEM RESET ALL;
ALTER SYSTEM
student$ sudo systemctl restart postgrespro-ent-16.service

Page 23: Практика PPEM

Эти задания используют планировщик PPEM, а не расширение pgpro_scheduler.

    1. Запланируйте выполнение очистки базы demo через одну-две минуты.
    1. Создайте задание для переиндексации таблиц в табличном пространстве pg_default базы demo и запланируйте его выполнение на регулярной основе каждый будний день в 20:00. 3. Убедитесь, что задание из п. 1 выполнено.
    1. Деактивируйте задание из п. 2.

Комментарии преподавателя

1, 2. Создавайте задания из панели «Базы данных».

3, 4. Используйте вкладку «Консоль заданий» в навигационной панели PPEM.

Page 24: 1. Однократное задание очистки базы данных

В навигационной панели выберите «Базы данных» и найдите базу demo экземпляра ent-16. В колонке «Действия» (крайняя справа) нажмите на пиктограмму «Очистить-Vacuum», и в открывшемся окне, в блоке «Планирование задачи», выберите «Отложенное по времени». Дайте новому заданию имя и назначьте время выполнения, после чего нажмите «Применить». В навигационной панели, во вкладке «Консоль заданий», выберите вложенную вкладку «Расписание» и убедитесь в наличии нового задания.

2. Переиндексация таблиц по расписанию

В навигационной панели откройте вкладку «Базы данных» и найдите базу demo. В колонке «Действия», в выпадающем меню (пиктограмма с тремя точками), выберите «Перестроить индексы-Reindex». В открывшемся окне выберите нужное табличное пространство, запланируйте задание по требуемому расписанию и нажмите «Применить».

3. Контроль списка заданий

Откройте «Консоль заданий» в навигационной панели, а затем вкладку «Задания». Проверьте, что созданное в п. 1 задание помечено статусом «Completed».

4. Деактивация задания

Во вкладке «Консоль заданий», в списке «Расписание» найдите задание, созданное в п. 2 и в колонке «Действия» нажмите пиктограмму «Деактивировать».