Планировщик заданий QSched

QSched — встроенный планировщик заданий для QHB.

Описание

Пользовательские задания выполняются набором фоновых процессов, которые уникальны для каждой базы данных. Основой для работы планировщика является набор системных таблиц с префиксом pg_catalog.qsched_..., который доступен для просмотра администратору базы данных и пользователю-владельцу базы данных.

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



Использование

Присвоение права работы с QSched

Администратор/владелец базы данных вправе наделить любого пользователя правом работы с планировщиком:

-- GRANT CONNECT ON DATABASE ... TO ...;
GRANT SCHED ON DATABASE ... TO ...;

Запуск планировщика во время работы СУБД

SELECT start_qsched_master();
-- Из процедуры будет возвращен PID планировщика для данной базы данных.

Запуск планировщика при старте СУБД

Для запуска планировщика при старте СУБД в файл qhb.conf необходимо добавить параметр with_qsched с указанием, для каких баз данных и от имени каких пользователей производить запуск. Важно, чтобы как база данных, так и пользователь существовали, а так же чтобы у пользователя были необходимые права для работы с базой данных, в противном случае старт СУБД будет прерван.

# Добавление автоматического запуска Qsched для баз данных dbname1 и db_name2
echo with_qsched = 'db_owner1:db_name1, db_owner2:db_name2' >> $PGDATA/qhb.conf;

# Добавление автоматического запуска Qsched только для базы данных dbname1
echo with_qsched = 'db_owner1:db_name1' >> $PGDATA/qhb.conf;

Проверка статуса планировщика

SELECT status_qsched_master();
-- Из процедуры будет возвращено значение типа boolean планировщика, которое
-- указывает на то, работает ли "мастер" (true) или нет (false) для данной базы данных.

Остановка планировщика

SELECT stop_qsched_master();
-- Из процедуры будет возвращен идентификатор завершенного процесса PID планировщика
-- для данной базы данных.

Создание задачи

Задачи создаются и планируются посредством SQL-интерфейса, предусмотренного в планировщике QSched:

-- Общая форма для примера:
SELECT qsched_job_create_{proc,block,query}('job name', 'job payload');
-- Из процедуры будет возвращен JOB_ID:Oid.

-- Пример немедленного выполнения (для отладки):
SELECT qsched_job_exec(/* id задачи */ 1);

-- Конкретная функция для создания задачи с содержимым в виде запроса:
SELECT qsched_job_create_query('job name', 'SELECT 123;');
-- Из процедуры будет возвращен JOB_ID:Oid.

-- Конкретная функция для создания задачи с содержимым в виде plpgsql-блока (в
-- данном примере — бесконечный цикл):
SELECT qsched_job_create_block('job name', 'BEGIN LOOP PERFORM pg_sleep(1); END LOOP; END;')
-- из процедуры будет возвращен JOB_ID:Oid.

-- Конкретная функция для создания задачи с содержимым в виде хранимой процедуры
-- (существование процедуры 'proc_name' проверяется во время выполнения):
SELECT qsched_job_create_proc('job name', 'proc_name');
-- Из процедуры будет возвращен JOB_ID:Oid.

Примечания по содержимому выполняемых заданий

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

SELECT qsched_job_create_proc('job name','my_schema.my_proc');

Для создания задачи с переменным набором аргументов, но общей целевой функцией полезно использовать синтаксис с plpgsql-блоками:

-- Пример использования анонимного блока с целью фиксирования значений аргументов
-- (аргументы также могут рассчитываться внутри анонимного блока):
SELECT qsched_job_create_block('job name', $$BEGIN PERFORM my_proc('my_arg1', 'my_arg2'); END;$$);

Создание расписания

Задача планируется одним или несколькими периодическими расписаниями с cron-подобным синтаксисом:

-- Запланирует для выполнения каждые 30 секунд:
SELECT qsched_schedule_create(/* id задачи */ 1, '1/30 * * * * *');

-- Включает все расписания данной задачи в набор выполняемых фоновых задач QSched:
SELECT qsched_job_enable(/* id задачи */ 1);

Выключение расписаний/задач

-- Выключает все расписания для данной задачи из набора выполняемых фоновых задач
-- QSched:
SELECT qsched_job_disable(/* id задачи */ 1);

Включение и выключение конкретных расписаний

Наряду с функциями включения и отключения целостных задач qsched_job_{enable,disable} существуют функции, которые работают с гранулярностью расписания. Так, передав идентификатор расписания в функции qsched_schedule_{enable,disable}, можно активировать или отключить конкретное расписание конкретной задачи.

Узнать идентификаторы расписаний можно в системном представлении qsched_my_jobs.


Просмотр логов планировщика

Для просмотра доступны логи по задачам, которые либо были созданы текущим пользователем, либо планировались для выполнения от его имени:

-- Покажет представление, основанное на системной таблице pg_catalog.qsched_job_run_status:
SELECT * FROM qsched_my_log;

Просмотр общей таблицы задач и расписаний для пользователя

Обратившись к представлению qsched_my_jobs, пользователь может увидеть задачи и расписания, которые создавал сам, либо те, которые запланированы исполняться от его имени.

-- Выведет полную сводку по "вашим" задачам и расписаниям:
SELECT * FROM qsched_my_jobs;

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

Допустим, мы создали задачу и установили ей расписание в формате crontab:

SELECT qsched_job_create_query('job name', 'SELECT 123');
SELECT qsched_schedule_create(1, '* * * * * *');

SELECT qsched_job_enable(1);
COMMIT;

В данном случае мы активировали задачу, и, согласно расписанию, она будет выполняться каждую секунду. Но, например, мы хотим, чтобы следующее время выполнения задачи рассчитывалось динамически, после очередного успешного выполнения. Для этого SQL-интерфейс QSched предоставляет функцию qsched_schedule_dyn_override, которая для заданного расписания позволяет определить команду SQL для расчета времени следующего выполнения:

-- Общий синтаксис:
SELECT qsched_schedule_dyn_override(/* Oid расписания */, /*динамический SQL*/);

-- Пример:
SELECT qsched_schedule_dyn_override(1, $$CURRENT_TIMESTAMP + '1 day'$$);

Результат выполнения данной команды SQL всегда будет интерпретирован как время, а точнее TIMESTAMP WITHOUT TIME ZONE.

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


Отмена динамического вычисления времени следующего старта задачи

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


SELECT qsched_schedule_cancel_override(/* id расписания */ 1);

Здесь для примера указан идентификатор 1, но это может быть любое значение типа OID. Тогда ваша задача будет следовать расписанию для определения времени запуска.

Убедиться в правильности изменений можно обратившись к представлению qsched_my_jobs, задав желаемый идентификатор задачи:


SELECT next_fire_statement FROM qsched_my_jobs WHERE job_id = /* id задачи */ 1;

Выполнение произвольного SQL для задачи, завершившейся ошибкой

Допустим, мы создали задачу и ее идентификатор 123. Задача может завершиться неудачей при любом исполнении, и мы хотим настроить выражение SQL, которое бы выполнялось процессом QSched при таком неудачном завершении:

-- Вторым аргументом указано выражение SQL Fallback:
SELECT qsched_job_set_fallback_sql(123, 'UPDATE err_log_table SET failure = 1;');

Теперь каждый раз, когда данная задача будет завершаться неудачно, исполнитель процесса QSched будет пытаться обновить записи в таблице err_log_table с выставлением поля failure в 1. (Данная операция дана в качестве примера, можно использовать произвольное выражение SQL.)

Если и задача завершилась неудачно, и Fallback-выражение выполнилось с ошибкой, в системных таблицах QSched данный факт отмечается и доступен для просмотра в представлении qsched_my_log:

-- Столбец double_fault означает двойную ошибку — и содержимого задачи, и
-- Fallback-выражения.
SELECT double_fault FROM qsched_my_log;

Отмена SQL-выражения Fallback для конкретной задачи

Если вы хотите, чтобы Fallback-выражение больше не выполнялось при неудачном завершении какой-либо задачи, необходимо выполнить запрос с использованием функции qsched_job_unset_fallback_sql:


SELECT qsched_job_unset_fallback_sql(/* id задачи */ 1);

Удаление и отключение ненужных задач

В данной версии поддерживается полное удаление задачи (исключая историю исполнения) с помощью функции qsched_job_delete:

-- Удаление Задачи, ее расписания, логов и историю исполнения:
SELECT qsched_job_delete(1 /* oid задачи */);

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

-- Сделает все расписания для данной задачи неактивными, включая запланированные
-- с помощю qsched_job_exec немедленные запуски:
SELECT qsched_job_disable(1 /* oid задачи */);

Активировать все расписания для данной задачи обратно можно с помощью функции qsched_job_enable(/* oid задачи */).



Конфигурация автоматической очистки логов планировщика

Автоматическая очистка логов планировщика конфигурируется при помощи следующих параметров: | Имя | Описание | Обязателен | :-- | :-- | :--
| qsched_log_rotation_max_lifetime | Максимальное время жизни записи | Да, при отсутствии qsched_log_rotation_max_entries | qsched_log_rotation_max_entries | Максимальное количество сохраняемых записей | Да, при отсутствии qsched_log_rotation_max_lifetime | qsched_log_rotation_period | Период запуска очистки записей | Да | qsched_log_rotation_strategy | Стратегия очистки | Нет | qsched_log_rotation_adjustment_period | Период подсчета записей для автоматической стратегии | Нет

Параметры очистки автоматически перезагружаются при подаче сигнала SIGHUP или при вызове pg_reload_conf(), таким образом возможно изменить параметры очистки логов или включить/выключить ее во время работы СУБД.

Очистка может производиться по следующим стратегиям:

  • Delete (значение 0), удаляет устаревшие записи при помощи DELETE (по-умолчанию).
  • Truncate (значение 1), сохраняет актуальные записи, очищает таблицу при помощи TRUNCATE, загружает сохраненные записи обратно. Потенциально быстрее, чем Delete, когда удаляется больше записей, чем сохраняется.
  • Автоматически (значение 2), периодически считает количество сохраняемых/удаляемых записей, и если нужно сохранить больше половины записей, то используется Delete, в противном случае - Truncate. Delete/Truncate используются до следующего подсчета записей. Периодичность подсчета записей определяется параметром qsched_log_rotation_adjustment_period. Например, если указать этот параметр равным двум, то пересчет записей (а так же потенциальная смена стратегии очистки) будет производиться каждые два запуска очистки. Если же указать ноль, то пересчет будет производиться при каждом запуске.

Примечание
Поскольку TRUNCATE системных таблиц требует разрешения на модификацию системных таблиц, для работ стратегий 1 и 2 требуется дополнительный параметр конфигурации allow_system_table_mods = true.

Для запуска автоматической очистки логов планировщика в файл qhb.conf необходимо добавить параметры qsched_log_rotation_max_lifetime и/или qsched_log_rotation_max_entries, а так же qsched_log_rotation_period:

# Максимальное время жизни записи - 30 минут
echo qsched_log_rotation_max_lifetime = '30m' >> $PGDATA/qhb.conf;
# Максимальное количество сохраняемых записей - 500
echo qsched_log_rotation_max_entries = '500' >> $PGDATA/qhb.conf;
# Запускать очистку раз в 5 минут
echo qsched_log_rotation_period = '5m' >> $PGDATA/qhb.conf;

Проверка статуса автоматической очистки

Узнать, включена ли автоматическая очистка логов:

SELECT qsched_log_rotation_status();

Выводит true при включенной очистке и false при выключенной.

Узнать текущую стратегию очистки логов:

SELECT qsched_log_rotation_static_strategy();

Выводит текущую стратегию, 0 для Delete, 1 для Truncate, 2 для Delete. -1 выводится при отключенной очистке.

Узнать текущую стратегию очистки логов при использовании автоматической стратегии:

SELECT qsched_log_rotation_auto_strategy();

Выводит текущую стратегию, 0 для Delete, 1 для Truncate. -1 выводится в случаях, когда очистка отключена, когда автоматическая стратегия не используется, а так же до первого запуска очистки, поскольку на тот момент стратегия еще ни разу не была выработана.

Конфигурация автоматической очистки логов планировщика

Автоматическая очистка логов планировщика конфигурируется при помощи следующих параметров: | Имя | Описание | Обязателен | :-- | :-- | :--
| qsched_log_rotation_max_lifetime | Максимальное время жизни записи | Да, при отсутствии qsched_log_rotation_max_entries | qsched_log_rotation_max_entries | Максимальное количество сохраняемых записей | Да, при отсутствии qsched_log_rotation_max_lifetime | qsched_log_rotation_period | Период запуска очистки записей | Да | qsched_log_rotation_strategy | Стратегия очистки | Нет | qsched_log_rotation_adjustment_period | Период подсчета записей для автоматической стратегии | Нет

Параметры очистки автоматически перезагружаются при подаче сигнала SIGHUP или при вызове pg_reload_conf(), таким образом возможно изменить параметры очистки логов или включить/выключить ее во время работы СУБД.

Очистка может производиться по следующим стратегиям:

  • Delete (значение 0), удаляет устаревшие записи при помощи DELETE (по-умолчанию).
  • Truncate (значение 1), сохраняет актуальные записи, очищает таблицу при помощи TRUNCATE, загружает сохраненные записи обратно. Потенциально быстрее, чем Delete, когда удаляется больше записей, чем сохраняется.
  • Автоматически (значение 2), периодически считает количество сохраняемых/удаляемых записей, и если нужно сохранить больше половины записей, то используется Delete, в противном случае - Truncate. Delete/Truncate используются до следующего подсчета записей. Периодичность подсчета записей определяется параметром qsched_log_rotation_adjustment_period. Например, если указать этот параметр равным двум, то пересчет записей (а так же потенциальная смена стратегии очистки) будет производиться каждые два запуска очистки. Если же указать ноль, то пересчет будет производиться при каждом запуске.

Примечание
Поскольку TRUNCATE системных таблиц требует разрешения на модификацию системных таблиц, для работ стратегий 1 и 2 требуется дополнительный параметр конфигурации allow_system_table_mods = true.

Для запуска автоматической очистки логов планировщика в файл qhb.conf необходимо добавить параметры qsched_log_rotation_max_lifetime и/или qsched_log_rotation_max_entries, а так же qsched_log_rotation_period:

# Максимальное время жизни записи - 30 минут
echo qsched_log_rotation_max_lifetime = '30m' >> $PGDATA/qhb.conf;
# Максимальное количество сохраняемых записей - 500
echo qsched_log_rotation_max_entries = '500' >> $PGDATA/qhb.conf;
# Запускать очистку раз в 5 минут
echo qsched_log_rotation_period = '5m' >> $PGDATA/qhb.conf;

Проверка статуса автоматической очистки

Узнать, включена ли автоматическая очистка логов:

SELECT qsched_log_rotation_status();

Выводит true при включенной очистке и false при выключенной.

Узнать текущую стратегию очистки логов:

SELECT qsched_log_rotation_static_strategy();

Выводит текущую стратегию, 0 для Delete, 1 для Truncate, 2 для Delete. -1 выводится при отключенной очистке.

Узнать текущую стратегию очистки логов при использовании автоматической стратегии:

SELECT qsched_log_rotation_auto_strategy();

Выводит текущую стратегию, 0 для Delete, 1 для Truncate. -1 выводится в случаях, когда очистка отключена, когда автоматическая стратегия не используется, а так же до первого запуска очистки, поскольку на тот момент стратегия еще ни разу не была выработана.

Число допустимых параллельных процессов

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

-- Увеличить допустимое число параллельных процессов до 4:
ALTER DATABASE SET qsched_parallel_slaves TO 4;

-- После этого следует перезапустить координатор процессов:
SELECT start_qsched_master();

-- Но перед этим следует завершить текущий координатор.
-- Узнать PID можно, обратившись к дереву процессов либо к представлению pg_stat_activity.
\! kill -TERM <pid_of_qsched_master>

SHOW qsched_parallel_slaves;

Данное ограничение действует по отношению к конкретной базе данных и не распространяется на процессы-координаторы QSched Master.



Ограничения QSched

В QSched введено стандартное для многих Enterprise-планировщиков ограничение: хотя многие задачи могут исполняться параллельно, данная конкретная задача (имеющая OID и имя) не будет исполняться параллельно с экземпляром той же самой задачи. Это сделано для защиты от ненамеренной порчи данных, если, например, содержимое задачи модифицирует таблицы СУБД или делает какие-то другие модифицирующие действия.