F.31. pg_cron — планировщик заданий на основе cron, работающий внутри базы данных#

F.31. pg_cron — планировщик заданий на основе cron, работающий внутри базы данных

F.31. pg_cron — планировщик заданий на основе cron, работающий внутри базы данных #

1.6.7:

F.31.1. Обзор? #

pg_cron — это простой планировщик заданий на основе cron для Tantor BE, который работает внутри базы данных как расширение.

F.31.2. Как работает pg_cron #

Расширение создает фоновый рабочий процесс, который отслеживает задания в таблице cron.job.

CREATE TABLE cron.job (
    jobid bigint primary key default pg_catalog.nextval('cron.jobid_seq'),
    schedule text not null,
    command text not null,
    nodename text not null default 'localhost',
    nodeport int not null default pg_catalog.inet_server_port(),
    database text not null default pg_catalog.current_database(),
    username text not null default current_user
);

Основываясь на вашей конфигурации, для выполнения задания расширение устанавливает соединение с Postgres или запускает рабочий процесс базы данных.

pg_cron может выполнять несколько заданий параллельно, но только один экземпляр каждого конкретного задания одновременно. Если второй экземпляр запускается до завершения первого, он ставится в очередь и начинается сразу после завершения первого.

F.31.3. Синтаксис Cron #

Код в pg_cron, который обрабатывает разбор и планирование, взят непосредственно из исходного кода cron, написанного Полом Викси, поэтому поддерживаются те же параметры.

 ┌───────────── min (0 - 59)
 │ ┌────────────── hour (0 - 23)
 │ │ ┌─────────────── day of month (1 - 31) or last day of the month ($)
 │ │ │ ┌──────────────── month (1 - 12)
 │ │ │ │ ┌───────────────── day of week (0 - 6) (0 to 6 are Sunday to
 │ │ │ │ │                  Saturday, or use names; 7 is also Sunday)
 │ │ │ │ │
 │ │ │ │ │
 * * * * *

Легкий способ создать расписание cron: crontab.guru.

pg_cron также позволяет вам: - использовать $ для обозначения последнего дня месяца; - использовать [1-59] секунд для планирования задания на основе интервала. Обратите внимание, что секунды нельзя использовать с другими единицами времени.

Примеры расписаний cron:

'10 seconds'  # every 10 seconds
* * * * *     # every minute
*/5 * * * *   # every 5 minutes
0 * * * *     # every hour
0 0 * * *     # daily at 12AM
0 0 * * 1-5   # 12AM every weekday
0 1 * * 0     # 1AM every Sunday
0 13 2 6 *    # 1PM on the 2nd of June

F.31.4. Управление и создание заданий #

Задания Cron можно управлять напрямую, взаимодействуя с таблицей cron.job, если у вас есть необходимые разрешения. Однако рекомендуется использовать функции cron:

Примечание

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

F.31.4.1. Создание задания cron #

F.31.4.1.1. cron.schedule сигнатуры #
-- create job, return jobid
CREATE OR REPLACE FUNCTION cron.schedule(schedule text, command text)
RETURNS bigint;

-- create named job, return jobid
CREATE OR REPLACE FUNCTION cron.schedule(job_name text, schedule text, command text)
RETURNS bigint
F.31.4.1.2. Примеры #
F.31.4.1.2.1. Создайте задание cron #
-- Delete old data on Saturday at 3:30am (GMT)
SELECT cron.schedule(
       '30 3 * * 6', 
       $$DELETE FROM events WHERE event_time < now() - interval '1 week'$$
);
-- returns cron id
F.31.4.1.2.2. Создать именованное задание cron #
-- Vacuum every day at 10:00am (GMT)
SELECT cron.schedule(
       'nightly-vacuum', 
       '0 10 * * *', 
       'VACUUM'
);
-- returns cron id
F.31.4.1.2.3. Создайте задание, которое выполняется каждые 30 секунд #
-- run SELECT 1 every 30 seconds
SELECT cron.schedule(
       'run_every_30_seconds', 
       '30 seconds', 
       'SELECT 1'
);
-- returns cron id
F.31.4.1.2.4. Создайте задание, которое вызывает хранимую процедуру каждые 5 секунд #
-- Call a stored procedure every 5 seconds
SELECT cron.schedule(
       'process-updates',
       '5 seconds',
       'CALL process_updates()'
);
-- returns cron id
F.31.4.1.2.5. Создайте задание, которое обрабатывает заработную плату в 12:00 последнего дня каждого месяца #
-- Process payroll at 12:00 of the last day of each month
SELECT cron.schedule(
       'process-payroll',
       '0 12 $ * *',
       'CALL process_payroll()'
);
-- returns cron id

F.31.4.2. Создание задания cron в другой базе данных #

F.31.4.2.1. cron.schedule_in_database сигнатура #
-- create job, return jobid
CREATE OR REPLACE FUNCTION cron.schedule_in_database(
       job_name text, 
       schedule text, 
       command text, 
       database text, 
       username text DEFAULT NULL::text, 
       active boolean DEFAULT true
)
RETURNS bigint
F.31.4.2.2. Пример #
F.31.4.2.2.1. Создать задание cron в другой базе данных #
-- Delete old data on Saturday at 3:30am (GMT)
SELECT cron.schedule_in_database(
       'delete_old_data', 
       '30 3 * * 6', 
       $$DELETE FROM events WHERE event_time < now() - interval '1 week'$$,
       'some_other_database'
);
-- returns cron id

F.31.4.3. Удаление задания cron #

F.31.4.3.1. cron.unschedule сигнатуры #
-- remove job by name, return true if job was removed
CREATE OR REPLACE FUNCTION cron.unschedule(job_name text)
RETURNS boolean

-- remove job by id, return true if job was removed
CREATE OR REPLACE FUNCTION cron.unschedule(job_id bigint)
RETURNS boolean
F.31.4.3.2. Примеры #
F.31.4.3.2.1. Удалить именованное задание cron #
-- delete job by name
SELECT cron.unschedule('nightly-vacuum');
-- returns true if job was removed
F.31.4.3.2.2. Удалить задание cron по идентификатору #
-- delete job by id
SELECT cron.unschedule(42);
-- returns true if job was removed

F.31.4.4. Изменение задания cron #

F.31.4.4.1. cron.alter_job сигнатура #
CREATE OR REPLACE FUNCTION cron.alter_job(
       job_id bigint, 
       schedule text DEFAULT NULL::text, 
       command text DEFAULT NULL::text, 
       database text DEFAULT NULL::text, 
       username text DEFAULT NULL::text, 
       active boolean DEFAULT NULL::boolean
)
RETURNS void
F.31.4.4.2. Примеры #
F.31.4.4.2.1. Изменить расписание задания #
-- change job's schedule
SELECT cron.alter_job(42, '0 10 * * *');
-- returns void
F.31.4.4.2.2. Изменить расписание задания, команду и имя пользователя #
-- change job's command
SELECT cron.alter_job(
       42,
       '0 10 * * *',
       'VACUUM',
       username := 'some_other_user'
);
-- returns void
F.31.4.4.2.3. Деактивировать задание #
-- deactivate job
SELECT cron.alter_job(42, active := false);
-- returns void

F.31.5. Настройка pg_cron #

Чтобы запустить фоновый рабочий процесс pg_cron, необходимо добавить pg_cron в shared_preload_libraries в postgresql.conf. Обратите внимание, что pg_cron не выполняет никакие задания, пока сервер находится в режиме горячего резерва, но он автоматически запускается при повышении роли сервера.

## add to postgresql.conf

## required to load pg_cron background worker on start-up
shared_preload_libraries = 'pg_cron'

По умолчанию фоновый рабочий процесс pg_cron ожидает, что его метаданные будут созданы в базе данных Tantor BE. Однако вы можете изменить это, установив параметр конфигурации cron.database_name в файле postgresql.conf.

## add to postgresql.conf

## optionally, specify the database in which the pg_cron background worker should run (defaults to postgres)
cron.database_name = 'postgres'

pg_cron может быть установлен только в одну базу данных в кластере. Если необходимо запускать задания в нескольких базах данных, используйте cron.schedule_in_database().

Ранее pg_cron мог использовать только время по Гринвичу, но теперь вы можете адаптировать ваше время, установив cron.timezone в postgresql.conf.

## add to postgresql.conf

## optionally, specify the timezone in which the pg_cron background worker should run (defaults to GMT). E.g:
cron.timezone = 'PRC'

После перезапуска Tantor BE вы можете создать функции и метаданные таблиц pg_cron с помощью CREATE EXTENSION pg_cron.

-- run as superuser:
CREATE EXTENSION pg_cron;

-- optionally, grant usage to regular users:
GRANT USAGE ON SCHEMA cron TO marco;

F.31.5.1. Обеспечение возможности запуска заданий pg_cron #

Предупреждение

По умолчанию, pg_cron использует libpq для открытия нового подключения к локальной базе данных, что должно быть разрешено в pg_hba.conf. Возможно, потребуется включить аутентификацию trust для подключений с localhost для пользователя, под которым выполняется cron-задание, или вы можете добавить пароль в файл .pgpass, который libpq будет использовать при открытии подключения.

Вы также можете использовать каталог сокетов домена Unix в качестве имени хоста и включить аутентификацию trust для локальных подключений в pg_hba.conf, что обычно безопасно:

## Connect via a unix domain socket:
cron.host = '/tmp'

## Can also be an empty string to look for the default directory:
cron.host = ''

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

## Schedule jobs via background workers instead of localhost connections
cron.use_background_workers = on
## Increase the number of available background workers from the default of 8
max_worker_processes = 20

Для обеспечения безопасности задания выполняются в базе данных, в которой вызывается функция cron.schedule с теми же разрешениями, что и у текущего пользователя. Кроме того, пользователи могут видеть только свои собственные задания в таблице cron.job.

-- View active jobs
select * from cron.job;

F.31.5.2. Параметры расширения #

Расширение pg_cron поддерживает следующие параметры конфигурации:

Параметр Значение по умолчанию Описание
cron.database_name postgres База данных, в которой должен выполняться фоновый рабочий процесс pg_cron.
cron.enable_superuser_jobs on Разрешить планирование заданий от имени суперпользователей.
cron.host localhost Имя хоста для подключения к postgres.
cron.launch_active_jobs on Когда выключено, отключает все активные задания без необходимости перезапуска сервера
cron.log_min_messages WARNING log_min_messages для фонового рабочего процесса запуска (launcher bgworker).
cron.log_run on Записывать все детали выполнения в таблицу cron.job_run_details.
cron.log_statement on Протоколировать все операторы cron перед выполнением.
cron.max_running_jobs 32 Максимальное количество заданий, которые могут выполняться одновременно.
cron.timezone GMT Часовой пояс, в котором должен работать фоновый процесс pg_cron.
cron.use_background_workers off Использовать фоновые рабочие процессы вместо клиентских подключений.
F.31.5.2.1. Изменение настроек #

Чтобы просмотреть настройки конфигурации, выполните:

SELECT * FROM pg_settings WHERE name LIKE 'cron.%';

Параметр можно изменить в файле postgresql.conf или с помощью приведённой ниже команды:

ALTER SYSTEM SET cron.<parameter> TO '<value>';

cron.log_min_messages и cron.launch_active_jobs имеют контекст установки параметра sighup. Они могут быть применены путем выполнения SELECT pg_reload_conf();.

Все остальные параметры имеют контекст postmaster и вступают в силу только после перезапуска сервера.

F.31.6. Мониторинг заданий #

F.31.6.1. Рассмотрение таблицы cron.job_run_details #

Вы можете просмотреть активность заданий в таблице cron.job_run_details:

select * from cron.job_run_details order by start_time desc limit 5;
┌───────┬───────┬─────────┬──────────┬──────────┬───────────────────┬───────────┬──────────────────┬───────────────────────────────┬───────────────────────────────┐
│ jobid │ runid │ job_pid │ database │ username │      command      │  status   │  return_message  │          start_time           │           end_time            │
├───────┼───────┼─────────┼──────────┼──────────┼───────────────────┼───────────┼──────────────────┼───────────────────────────────┼───────────────────────────────┤
│    11 │  4328 │    2610 │ postgres │ marco    │ select pg_sleep(3)│ running   │ NULL             │ 2023-02-07 09:30:00.098164+01 │ NULL                          │
│    10 │  4327 │    2609 │ postgres │ marco    │ select process()  │ succeeded │ SELECT 1         │ 2023-02-07 09:29:00.015168+01 │ 2023-02-07 09:29:00.832308+01 │
│    10 │  4321 │    2603 │ postgres │ marco    │ select process()  │ succeeded │ SELECT 1         │ 2023-02-07 09:28:00.011965+01 │ 2023-02-07 09:28:01.420901+01 │
│    10 │  4320 │    2602 │ postgres │ marco    │ select process()  │ failed    │ server restarted │ 2023-02-07 09:27:00.011833+01 │ 2023-02-07 09:27:00.72121+01  │
│     9 │  4320 │    2602 │ postgres │ marco    │ select do_stuff() │ failed    │ job canceled     │ 2023-02-07 09:26:00.011833+01 │ 2023-02-07 09:26:00.22121+01  │
└───────┴───────┴─────────┴──────────┴──────────┴───────────────────┴───────────┴──────────────────┴───────────────────────────────┴───────────────────────────────┘
(10 rows)

Записи в таблице не очищаются автоматически, но каждый пользователь, который может планировать задания cron, также имеет разрешение удалять свои собственные записи cron.job_run_details.

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

-- Delete old cron.job_run_details records of the current user every day at noon
SELECT  cron.schedule('delete-job-run-details', '0 12 * * *', $$DELETE FROM cron.job_run_details WHERE end_time < now() - interval '7 days'$$);

Если вы не хотите использовать cron.job_run_details вообще, то вы можете добавить cron.log_run = off в postgresql.conf.

F.31.6.2. Другие параметры журналирования cron #

Если параметр cron.log_statement настроен, задания будут журналироваться перед выполнением. Параметр cron.log_min_messages управляет минимальным уровнем сообщений, которые будут записываться.