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

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

---

Индекс LLMS: [llms.txt](/ru/llms.txt)

---

<!-- Generated by scripts/generate_skeleton.py. -->

> [!NOTE]
> Эта страница извлечена из официального PDF в нативный Markdown. Текст слайдов, код и комментарии сохранены в порядке чтения; исходные PDF и HTML остаются эталоном макета.
> **HTML source:** [pgpro_05_scheduler.html](https://edu.postgrespro.ru/16/pgpro-16/pgpro_05_scheduler.html)
> **Archive:** [PGPRO-handouts-16.zip](https://edu.postgrespro.ru/16/PGPRO-handouts-16.zip)
> **Страниц PDF:** 24

## Page 1: Установка и сопровождение / Планировщик заданий {#source-page-001}

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

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

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

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

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

## Page 2: Темы {#source-page-002}

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

## Page 3: Назначение {#source-page-003}

**Расширение pgpro\\\_scheduler**
- планировщик периодических и разовых заданий мониторинг разграничение прав Задание — команды SQL
  - в отдельных транзакциях или все в одной

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

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

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

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

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

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

## Page 4: Архитектура {#source-page-004}

![Source slide page 4: Архитектура](images/page-004.png){#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 {#source-page-005}

Создадим базу данных:
```sql
=> CREATE DATABASE pgpro_scheduler;
```
```sql
CREATE DATABASE
```
Для работы расширения необходимо подключить библиотеку:
```sql
=> ALTER SYSTEM SET shared_preload_libraries = 'pgpro_scheduler';
```
```sql
ALTER SYSTEM
```
Общее ограничение на количество фоновых рабочих процессов в системе, значения по умолчанию достаточно для работы с одной базой:
```text
=> SHOW max_worker_processes;
```
```text
 max_worker_processes
----------------------
 8
(1 row)
```
Подключение библиотеки требует перезапуска СУБД.
```sh
student$ sudo systemctl restart postgrespro-ent-16.service
```
```sh
student$ psql
```
Установим расширение pgpro\_scheduler в базе данных, в которой оно будет использоваться.
```sql
=> \c pgpro_scheduler
```
```text
You are now connected to database "pgpro_scheduler" as user "student".
```
```sql
=> CREATE EXTENSION pgpro_scheduler;
```
```sql
CREATE EXTENSION
```
Объекты расширения находятся в схеме schedule. Например, представления для мониторинга разовых заданий:
```text
=> \dv schedule.*
```
```text
             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)
```
### Настройка
Укажем имена баз данных, для которых будут запускаться задания:
```sql
=> ALTER SYSTEM SET schedule.database = 'pgpro_scheduler';
```
```sql
ALTER SYSTEM
```
Применим изменение конфигурации.
```sql
=> SELECT pg_reload_conf();
```
```text
 pg_reload_conf
----------------
 t
(1 row)
```
Установим ограничения на уровне базы данных.
Максимальное число рабочих процессов для периодических заданий:
```sql
=> ALTER DATABASE pgpro_scheduler SET schedule.max_workers = 3;
```
```sql
ALTER DATABASE
```

## Page 6: Page 6 {#source-page-006}

Ограничение на число рабочих процессов, выполняющих разовые задания:
```sql
=> ALTER DATABASE pgpro_scheduler SET schedule.max_parallel_workers = 3;
```
```sql
ALTER DATABASE
```
Включим планировщик заданий:
```sql
=> SELECT schedule.enable();
```
```text
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)
```
Состояние процессов планировщика заданий:
```sql
=> SELECT * FROM schedule.status();
```
```text
 pid |    database     |       type
-----+-----------------+------------------
     | pgpro_scheduler | database manager
(1 row)
```
Информация об этих процессах доступна и в pg\_stat\_activity:
```sql
=> SELECT pid, datname, application_name, backend_type, query
FROM pg_stat_activity
WHERE application_name ~ '^pgp-s' \gx
```
```text
(0 rows)
```

## Page 7: Периодические задания {#source-page-007}

**Команды 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: Управление и мониторинг {#source-page-008}

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

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

Расширение 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: Периодические задания {#source-page-009}

Создадим таблицу:
```sql
=> CREATE TABLE t (
  bknd int DEFAULT pg_backend_pid(),
  txid bigint DEFAULT txid_current(),
  ttime timestamptz DEFAULT now()
);
```
```sql
CREATE TABLE
```
А теперь создадим периодическое задание, используя формат crontab. Формат использует пять полей для идентификации времени:
минуты часа (0-59); часы (0-23); дни месяца (1-31); месяц (1-12 или трехбуквенное сокращение); дни недели (0 — воскресенье ... 6 — суббота, или трехбуквенные сокращения).
Если в поле после звездочки через косую черту указано число — это шаг выполнения. Например,
\*/10 \* \* \* \*
означает «раз в десять минут».
Расширенный формат cron использует шесть полей с секундами. Например,
\*/30 \* \* \* \* \*
означает «раз в тридцать секунд».
Запланируем задание на выполнение раз в минуту:
```sql
=> SELECT schedule.create_job(
  '{ "commands": "INSERT INTO t DEFAULT VALUES",
     "cron": "*/1 * * * *" }'
);
```
```text
 create_job
------------
          1
(1 row)
```
Получим информацию о запланированном задании:
```sql
=> SELECT id, commands, run_as, active
FROM schedule.get_cron();
```
```sql
 id |             commands             | run_as  | active
----+----------------------------------+---------+--------
  1 | {"INSERT INTO t DEFAULT VALUES"} | student | t
(1 row)
```
Функция get\_job информирует о задании, возвращая значение типа cron\_rec.
Проверим поле rule, описывающее расписание, преобразовав расписание из представления JSONB в текстовые строки и урезав их по ширине:
```sql
=> SELECT substring(jsonb_each_text(rule)::text FOR 75) AS rule
FROM schedule.get_job(1);
```
```text
                                    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 {#source-page-010}

Через минуту сработает первое задание. Подождем...
Проверим, что было добавлено в таблицу t:
```sql
=> SELECT * FROM t;
```
```text
  bknd  | txid |             ttime
--------+------+-------------------------------
 797284 |  971 | 2026-07-27 23:36:00.048241+03
(1 row)
```
Функция timetable информирует о выполненных и запланированных заданиях. Получим список заданий за 5 минут:
```sql
=> SELECT id, type, commands, scheduled_at, status
FROM schedule.timetable(
  now() - interval '3 minutes',
  now() + interval '2 minutes'
)
ORDER BY scheduled_at;
```
```sql
 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)
```
Отчет о выполненных заданиях планировщика:
```sql
=> SELECT cron, scheduled_at, started, status, message
FROM schedule.get_log();
```
```text
 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)
```
### Управление периодическими заданиями
До сих пор задание выполнялось в ноль секунд каждой минуты:
```sql
=> SELECT jsonb_array_elements(rule->'seconds') FROM schedule.get_job(1);
```
```text
 jsonb_array_elements
----------------------
 0
(1 row)
```
Пусть задание выполняется чаще — раз в десять секунд.
Используем расширенный формат cron из шести полей, где первое поле — секунды:
```sql
=> SELECT schedule.set_job_attribute(1, 'cron','*/10 * * * * *');
```
```text
 set_job_attribute
-------------------
 t
(1 row)
```
Проверим в rule: задание должно быть запланировано для исполнения с периодичностью раз в десять секунд.
```sql
=> SELECT jsonb_array_elements(rule->'seconds') FROM schedule.get_job(1);
```
```text
 jsonb_array_elements
----------------------
 0
 10
 20
 30
 40
 50
(6 rows)
```
Подождем немного...
Проверим, что было добавлено в таблицу t:

## Page 11: Page 11 {#source-page-011}

```sql
=> SELECT * FROM t;
```
```text
  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)
```
Временно приостановим задание. Новые записи не будут попадать в таблицу.
```sql
=> SELECT schedule.deactivate_job(1);
```
```text
 deactivate_job
----------------
 t
(1 row)
```
Проверим состояние задания — оно должно быть не активно:
```sql
=> SELECT id, commands, run_as, active FROM schedule.get_job(1);
```
```sql
 id |             commands             | run_as  | active
----+----------------------------------+---------+--------
  1 | {"INSERT INTO t DEFAULT VALUES"} | student | f
(1 row)
```
Подождем немного...
Снова запустим задание и проверим состояние:
```sql
=> SELECT schedule.activate_job(1);
```
```text
 activate_job
--------------
 t
(1 row)
```
```sql
=> SELECT id, commands, run_as, active FROM schedule.get_job(1);
```
```sql
 id |             commands             | run_as  | active
----+----------------------------------+---------+--------
  1 | {"INSERT INTO t DEFAULT VALUES"} | student | t
(1 row)
```
Подождем еще немного...
Проверим записи в таблице t:
```sql
=> SELECT * FROM t;
```
```text
  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)
```
Выполненные задания отображаются в журнале:
```sql
=> SELECT cron, scheduled_at, started, status, message
FROM schedule.get_log() WHERE cron = 1;
```
```text
 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)
```
Снова приостановим задание и очистим таблицу.
```sql
=> SELECT schedule.deactivate_job(1);
```

## Page 12: Команды в отдельных транзакциях {#source-page-012}

```text
 deactivate_job
----------------
 t
(1 row)
```
```sql
=> DELETE FROM t;
```
```sql
DELETE 4
```
В атрибуте commands значения могут задаваться в виде текста или массива.
Если отдельные команды SQL заданы текстовой строкой через точку с запятой, то все команды задания будут выполнены в одной транзакции.
Если команды SQL заданы JSON-массивом, то каждая из них будет выполнена в отдельной транзакции.
Создадим задание для заполнения таблицы t раз в десять секунд.
Каждая SQL-команда выполняется в отдельной транзакции. Значения now() для каждой транзакции — свои, как и идентификаторы транзакции.
```sql
=> SELECT schedule.create_job(
  '{ "commands": [
       "INSERT INTO t DEFAULT VALUES",
       "SELECT pg_sleep(5)",
       "INSERT INTO t DEFAULT VALUES"
     ],
     "cron": "*/10 * * * * *" }'
);
```
```text
 create_job
------------
          2
(1 row)
```
Снова немного подождем...
```sql
=> SELECT schedule.drop_job(2);
```
```text
 drop_job
----------
 t
(1 row)
```
Проверим, что было добавлено в таблицу:
```sql
=> SELECT * FROM t;
```
```text
  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, и все команды будут выполнены в одной транзакции.
Удалим все записи в таблице.
```sql
=> DELETE FROM t;
```
```sql
DELETE 2
```
В одной транзакции значения времени, которые возвращает функция now(), будут одинаковыми.

## Page 13: Page 13 {#source-page-013}

```sql
=> 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}'
);
```
```text
 create_job
------------
          3
(1 row)
```
Подождем чуть-чуть...
Удалим задание и проверим, что было добавлено в таблицу.
```sql
=> SELECT schedule.drop_job(3);
```
```text
 drop_job
----------
 t
(1 row)
```
```sql
=> SELECT * FROM t;
```
```text
  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)
```
Все три оператора задания выполнились в одной транзакции.
Снова очистим таблицу и запустим первое задание.
```sql
=> DELETE FROM t;
```
```sql
DELETE 2
```
```sql
=> SELECT schedule.activate_job(1);
```
```text
 activate_job
--------------
 t
(1 row)
```

## Page 14: Разовые задания {#source-page-014}

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

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

Функция submit\_job создает разовое задание. По умолчанию задание будет выполнено немедленно. Можно задать время запуска параметром run\_after. Также можно в параметре depends\_on передать массив идентификаторов других заданий — в этом случае данное задание будет выполнено сразу после их завершения.

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

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

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

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

## Page 15: Разовые задания {#source-page-015}

Запланируем разовое задание, удаляющее через десять секунд таблицу t:
```sql
=> SELECT schedule.submit_job(
  'DROP TABLE t',
  run_after := now() + interval '10 seconds'
);
```
```text
 submit_job
------------
          1
(1 row)
```
Таблица t пока на месте:
```sql
=> SELECT * FROM t;
```
```text
 bknd | txid | ttime
------+------+-------
(0 rows)
```
Подождем немного...
```sql
=> SELECT * FROM t;
```
```sql
ERROR:  relation "t" does not exist
LINE 1: SELECT * FROM t;
                      ^
```
Таблица уже удалена.
В таблице заданий выводится информация и о разовых заданиях:
```sql
=> SELECT type, commands, scheduled_at, status
FROM schedule.timetable(
  now() - interval '1 minutes',
  now()
)
WHERE id = 1
ORDER BY scheduled_at;
```
```sql
    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 периодического задания задает действия при сбое команды задания — будем восстанавливать таблицу, предполагая ее отсутствие:
```sql
=> 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()
   )'
);
```
```text
 set_job_attribute
-------------------
 t
(1 row)
```
Проверим состояние задания:
```sql
=> SELECT id, commands, run_as, active
FROM schedule.get_job(1);
```

## Page 16: Page 16 {#source-page-016}

```sql
 id |             commands             | run_as  | active
----+----------------------------------+---------+--------
  1 | {"INSERT INTO t DEFAULT VALUES"} | student | t
(1 row)
```
Подождем еще немного...
Теперь таблица восстановлена и пока пуста:
```sql
=> SELECT * FROM t;
```
```text
 bknd | txid | ttime
------+------+-------
(0 rows)
```
Подождем еще...
Теперь таблица уже не пуста:
```sql
=> SELECT * FROM t;
```
```text
  bknd  | txid |             ttime
--------+------+-------------------------------
 801308 | 1020 | 2026-07-27 23:37:40.010017+03
(1 row)
```
Посмотрим список завершенных заданий:
```sql
=> SELECT scheduled_at, commands, started, status, message
FROM schedule.get_log()
WHERE cron = 1 \gx
```
```sql
-[ 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      |
```
Удалим запланированное задание:
```sql
=> SELECT schedule.drop_job(1);
```

## Page 17: Page 17 {#source-page-017}

```text
 drop_job
----------
 t
(1 row)
```

## Page 18: Итоги {#source-page-018}

**Расширение pgpro\\\_scheduler позволяет планировать задания**
- Задания выполняются либо периодически по расписанию, либо один раз в запланированное время
- Задания принадлежат пользователям и выполняются от их имени
- Каждое задание выполняется в отдельном процессе
- Имеются средства управления заданиями и их мониторинга

## Page 19: Практика {#source-page-019}

**1. Подготовьте систему к работе с расширениями pgpro\\\_scheduler и pg\\\_stat\\\_statements. 2. Установите расширения pgpro\\\_scheduler и pg\\\_stat\\\_statements в базу данных и настройте их.**
- 3. Запланируйте задание, собирающее статистику по наиболее часто выполняемым запросам каждые 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 {#source-page-020}

```sh
student$ psql
```
Создадим базу данных pgpro\_scheduler:
```sql
=> CREATE DATABASE pgpro_scheduler;
```
```sql
CREATE DATABASE
```
Подключим разделяемые библиотеки:
```sql
=> ALTER SYSTEM SET shared_preload_libraries = pg_stat_statements, pgpro_scheduler;
```
```sql
ALTER SYSTEM
```
```sql
=> ALTER SYSTEM SET track_io_timing = 'on';
```
```sql
ALTER SYSTEM
```
Общее ограничение на количество параллельных процессов:
```text
=> SHOW max_worker_processes;
```
```text
 max_worker_processes
----------------------
 8
(1 row)
```
Перезапустим СУБД.
```sh
student$ sudo systemctl restart postgrespro-ent-16.service
```
```sh
student$ psql pgpro_scheduler
```
### 2. Подключение расширений. Запуск планировщика
Установим расширения:
```sql
=> CREATE EXTENSION pgpro_scheduler;
```
```sql
CREATE EXTENSION
```
```sql
=> CREATE EXTENSION pg_stat_statements;
```
```sql
CREATE EXTENSION
```
База данных для нагрузочного тестирования:
```sql
=> ALTER SYSTEM SET schedule.database = 'pgpro_scheduler';
```
```sql
ALTER SYSTEM
```
Применим изменения конфигурации:
```sql
=> SELECT pg_reload_conf();
```
```text
 pg_reload_conf
----------------
 t
(1 row)
```
Включим планировщик заданий:
```sql
=> SELECT schedule.enable();
```
```text
 enable
--------
 t
(1 row)
```
Состояние процессов планировщика заданий:
```sql
=> SELECT * FROM schedule.status();
```

## Page 21: 3. Планирование задач {#source-page-021}

```text
 pid |    database     |       type
-----+-----------------+------------------
     | pgpro_scheduler | database manager
(1 row)
```
Создадим таблицу для сохранения выборок статистики:
```sql
=> CREATE TABLE stat_tab (LIKE pg_stat_statements);
```
```sql
CREATE TABLE
```
```sql
=> ALTER TABLE stat_tab
  ADD COLUMN time timestamp with time zone DEFAULT now();
```
```sql
ALTER TABLE
```
Запланируем копирование статистики в таблицу stat\_tab раз в 20 секунд, причем периодическое задание должно работать одну минуту:
```sql
=> 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
);
```
```text
 create_job
------------
          1
(1 row)
```
Через минуту содержимое таблицы нужно скопировать в файл с помощью разового задания.
```sh
student$ sudo rm -f /tmp/rep.stat
```
```sql
=> SELECT schedule.submit_job(
  'COPY stat_tab TO ''/tmp/rep.stat''',
  run_after => now() + interval '1 minutes'
);
```
```text
 submit_job
------------
          1
(1 row)
```
Подождем минуту...
Проверим выполненные задания:
```sql
=> SELECT cron, scheduled_at, started, status, message
FROM schedule.get_log();
```
```text
 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)
```
В таблице собраны сведения о наиболее частых запросах:
```sql
=> SELECT time, queryid, calls FROM stat_tab;
```
```text
             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: Уборка {#source-page-022}

Разовое задание скопировало таблицу в файл:
```sh
student$ sudo ls -l /tmp/rep.stat
```
```text
-rw-r--r-- 1 postgres postgres 4496 июл 28 00:10 /tmp/rep.stat
```
Удалим созданную базу данных и файл, сбросим настройки параметров конфигурации.
```sql
=> \c student
```
```text
You are now connected to database "student" as user "student".
```
```sql
=> DROP DATABASE pgpro_scheduler WITH (FORCE);
```
```sql
DROP DATABASE
```
```sh
student$ sudo rm -f /tmp/rep.stat
```
```sql
=> ALTER SYSTEM RESET ALL;
```
```sql
ALTER SYSTEM
```
```sh
student$ sudo systemctl restart postgrespro-ent-16.service
```

## Page 23: Практика PPEM {#source-page-023}

Эти задания используют планировщик PPEM, а не расширение pgpro\_scheduler.
- 1. Запланируйте выполнение очистки базы demo через одну-две минуты.
- 2. Создайте задание для переиндексации таблиц в табличном пространстве pg\_default базы demo и запланируйте его выполнение на регулярной основе каждый будний день в 20:00. 3. Убедитесь, что задание из п. 1 выполнено.
- 4. Деактивируйте задание из п. 2.

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

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

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

## Page 24: 1. Однократное задание очистки базы данных {#source-page-024}

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