Фоновые процессы#

Фоновые процессы#

asda

PostgreSQL состоит из набора взаимодействующих процессов.За основными процессами закреплены собственные имена и ониимеют особый смысл для сервера. Например, при остановке экземпляра нужно записать в журнал сообщение о завершении контрольной точки, поэтому процесс walwriter надо останавливать после checkpointer, и т. п. Обычно (хотя и не всегда) такие процессы работают постоянно от старта сервера до его останова.

Но вместе с тем часто возникает необходимость временно запустить процесс, который выполнит необходимую работу и завершится. Такую возможность предоставляет механизм фоновых процессов (background workers):

https://postgrespro.ru/docs/postgresql/16/bgworker

Как и все остальные, фоновые процессы порождаются и контролируются процессом postmaster. Фоновые процессы имеютвсе возможности, такие как доступ к разделяемой памяти сервера и соединение с базами данных по внутреннему протоколу SPI (рассматривается в теме «Языки программирования»).

Фоновые процессы должны быть написаны на языке C, пример можно найти в исходном коде (src/test/modules/worker_spi).

Заметим, что процесс автоочистки (autovacuum launcher) тоже динамически порождает рабочие процессы (autovacuum worker),но исторически делает это по-своему, не используя общий механизм фоновых процессов.

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

asda

Фоновые процессы используются в PostgreSQL для различных целей.

  1. Параллельное выполнение запросов.

2. Параллельное выполнение служебных команд (CREATE INDEX и REINDEX (в т. ч. CONCURRENTLY) для B-деревьев, VACUUM). В этих случаях имеется ведущий процесс, инициирующий запуск рабочих процессов и получающий от них информацию.

  1. Логическая репликация (для процессов на стороне сервера-подписчика).

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

ПРАКТИКА#

Использование фоновых процессов

Вот простой наглядный пример использования фоновых процессов для распараллеливания запросов. Если требуется посчитать количество строк в большой таблице, ведущий процесс запускает несколько рабочих процессов (в данном случае 2):

EXPLAIN (analyze, costs off, timing off)
SELECT count(*) FROM mail_messages;

                                                                        QUERY PLAN
-----------------------------------------------------------------------------------
 Finalize Aggregate (actual rows=1 loops=1)
   ->  Gather (actual rows=3 loops=1)
                 Workers Planned: 2
                 Workers Launched: 2
                 ->  Partial Aggregate (actual rows=1 loops=3)
                           ->  Parallel Seq Scan on mail_messages (actual rows=118708 loops=3)
 Planning Time: 7.664 ms
 Execution Time: 254.754 ms
(8 rows)

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

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

Прикладные задачи#

asda

Кроме штатного использования, фоновые процессы можно приспособить и для решения пользовательских задач.

Например, можно организовать планировщик заданий внутри СУБД.

В отличие от, например, cron, такой планировщик не будет зависетьот используемой операционной системы. (Планировщик внутри СУБД реализован в Postgres Pro Enterprise и рассматривается в курсе PGPRO).

Можно выполнять асинхронную обработку событий.

Можно распараллеливать сложную длинную обработку больших объемов данных.

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

И так далее.

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

Расширение pg_background#

asda

Есть и другое решение, которое позволяет прикладным разработчикам воспользоваться фоновыми процессами — стороннее расширение pg_background:

https://github.com/vibhorkum/pg_background

Фактически это небольшая обертка над низкоуровневым API фоновых процессов, позволяющая запускать в фоновом режиме произвольные команды SQL.

Разумеется, таким образом можно вызывать хранимые функции и процедуры, написанные на любых серверных языках программирования (например, PL/pgSQL).

ПРАКТИКА#

Расширение pg_background

Расширение уже собрано и доступно для установки в виртуальной машине курса:

CREATE EXTENSION pg_background;

CREATE EXTENSION

Расширение предоставляет всего три функции.

Функция pg_background_launch запускает фоновый процесс, выполняющий одну SQL-команду.

Например, выполним в фоне простой запрос. Для удобства он будет работать 10 секунд:

SELECT pg_background_launch(
        $$ SELECT 2+2 FROM (SELECT pg_sleep(10)) $$
);

 pg_background_launch
----------------------
                                34704
(1 row)

Пока запрос выполняется, мы можем увидеть процесс в pg_stat_activity:

SELECT query, backend_type, wait_event_type, wait_event
FROM pg_stat_activity WHERE pid = 34704 \gx

-[ RECORD 1 ]---+----------------------------------------
query           |  SELECT 2+2 FROM (SELECT pg_sleep(10))
backend_type    | pg_background
wait_event_type | Timeout
wait_event      | PgSleep

Обратите внимание на ожидание.

Функция pg_background_result выводит результат выполнения фоновой команды (при необходимости дожидаясь ее окончания).

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

SELECT * FROM pg_background_result(34704) AS (result integer);

 result
--------
          4
(1 row)

Функция pg_background_detach отключает текущий процесс от ожидания результатов фонового процесса. Из-за особенности текущей реализации между вызовами pg_background_launch и p**g_background_detach** должна быть небольшая задержка.

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

Запустим процесс, возвращающий много информации:

SELECT pg_background_launch(
        $$ SELECT * FROM generate_series(1,1_000_000) $$
);

 pg_background_launch
----------------------
                                34880
(1 row)
SELECT query, backend_type, wait_event_type, wait_event
FROM pg_stat_activity WHERE pid = 34880 \gx

-[ RECORD 1 ]---+---------------------------------------------
query           |  SELECT * FROM generate_series(1,1_000_000)
backend_type    | pg_background
wait_event_type | IPC
wait_event      | MessageQueuePutMessage

Обратите внимание на ожидание.

Отключимся от процесса:

SELECT * FROM pg_background_detach(34880);

 pg_background_detach
----------------------

(1 row)
SELECT query, backend_type, wait_event_type, wait_event
FROM pg_stat_activity WHERE pid = 34880 \gx

-[ RECORD 1 ]---+---------------------------------------------
query           |  SELECT * FROM generate_series(1,1_000_000)
backend_type    | pg_background
wait_event_type |
wait_event      |

Больше фоновый процесс ничего не ждет (и, возможно, уже отработал).

Заметим, что в случае dblink сложностей с переполнением буфера не возникает, потому что используется не межпроцессное взаимодействие, а устанавливается обычное соединение по клиент-серверному протоколу.

Интересно, что изначально в состав расширения pg_background планировалось включить четвертую функция pg_background_run(pid, query), которая должна была передавать новое задание уже запущенному процессу, избегая затрат на создание очередного фонового процесса. Однако пока что это не реализовано.