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(/*job 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(/*job id*/ 1, '1/30 * * * * *');

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

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

-- Выключает все расписания для данной задачи из набора выполняемых фоновых задач QSched:
SELECT qsched_job_disable(/*job 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(/*schedule Oid*/, /*Dynamic 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(/* schedule id */ 1);

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

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


SELECT next_fire_statement FROM qsched_my_jobs WHERE job_id = /*job_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(/*job id*/ 1);

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

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

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

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

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

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

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

С помощью системной настройки 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 и имя) не будет исполняться параллельно с экземпляром той же самой задачи. Это сделано для защиты от ненамеренной порчи данных, если, например, содержимое задачи модифицирует таблицы СУБД или делает какие-то другие модифицирующие действия.