Встроенная оркестрация в PostgreSQL: расширение pg_durable для надежного фонового выполнения SQL-функций
Фоновые задачи в веб-приложениях — от импорта данных до генерации векторных встраиваний — традиционно требуют сложной обвязки: планировщиков cron, очередей сообщений и оркестраторов вроде Temporal. Это усложняет сопровождение и создает риск рассинхронизации состояния СУБД. Microsoft представила pg_durable — Rust-расширение для PostgreSQL, переносящее паттерн надежного выполнения процессов (durable execution) прямо в базу данных, устраняя необходимость во внешней оркестрации и сближая вычисления с данными.
Для кого создан инструмент и какие проблемы он решает
Расширение pg_durable ориентировано на три основные группы специалистов:
- Разработчики бэкенда и инженеры данных, которым необходимо гарантировать надежность пайплайнов обработки данных рядом с их источником.
- Администраторы баз данных и SRE-инженеры, автоматизирующие регламентные задачи, которые должны переносить перезапуски СУБД и сохранять логи в SQL.
- Разработчики ИИ-систем, которым нужна надежная векторизация текстов или изображений по каждой строке с вызовом внешних API и сохранением результатов через pgvector.
В отличие от стандартных PL/pgSQL процедур, которые при сбоях откатывают всю транзакцию и блокируют ресурсы СУБД, pg_durable использует концепцию контрольных точек (checkpoints) и воспроизведения (replay). Задача разбивается на шаги. В случае сбоя или перезапуска выполнение возобновляется с последней контрольной точки, исключая ручное восстановление состояния СУБД или повторный запуск уже завершенных шагов.
Архитектура и устройство расширения
С технической точки зрения pg_durable — это расширение СУБД, разработанное на Rust с использованием фреймворка pgrx (библиотеки для интеграции Rust и C-API PostgreSQL). Расширение выполняется в рамках адресного пространства PostgreSQL силами встроенного фонового процесса (Background Worker).
Архитектура pg_durable опирается на две Rust-библиотеки:
- duroxide — ядро оркестрации, реализующее конечный автомат выполнения графа задач, детерминированное повторное воспроизведение истории, работу с таймерами и запуск под-оркестраций.
- duroxide-pg — PostgreSQL-провайдер состояния для duroxide. Он сохраняет метаданные процессов, очереди задач и историю в схеме
duroxide.*СУБД.
Пользовательское взаимодействие строится на базе SQL DSL (языка описания процессов), функции которого расположены в схеме df.*.
Использование SQL DSL и композиция шагов
Для описания многошаговых процессов используются специальные SQL-операторы:
~>(последовательное выполнение) — связывает шаги в цепочку, где выходные данные предыдущего шага передаются следующему.|=>(пакетная обработка и разветвление) — делит выборку на батчи для параллельной или пакетной обработки (паттерн fan-out).
Пример запуска надежной фоновой задачи обработки документов:
SELECT df.start(
'SELECT id FROM documents WHERE processed = false LIMIT 100' |=> 'batch'
~> 'UPDATE documents SET processed = true WHERE id = ANY($batch)'
);
Функция df.start(...) принимает строку с графом выполнения. Сначала выбирается пакет из 100 документов, id которых помещаются в переменную batch. Затем массив передается следующему шагу обновления статусов. При сбое СУБД pg_durable восстановит контекст из duroxide.* и возобновит работу с шага обновления, не повторяя выборку.
Руководство по развертыванию и настройке
Для запуска pg_durable в тестовой или промышленной среде необходимо установить расширение и настроить конфигурацию PostgreSQL.
Шаг 1. Установка пакетов или запуск в Docker
Для Debian (amd64) доступны готовые пакеты для PostgreSQL 17 и 18. Пакеты имеют названия вида pg-durable-postgresql-<версия_PG>_<версия_расширения>-1_amd64.deb и устанавливают библиотеки расширения в каталоги PostgreSQL.
Для тестирования подготовлен официальный Docker-образ с предустановленным расширением pg_durable поверх официальных образов PostgreSQL 17 и 18:
# Запуск для PostgreSQL 17
docker run -d --name pg_durable_pg17 -p 5432:5432 -e POSTGRES_PASSWORD=secret ghcr.io/microsoft/pg_durable:pg17
# Запуск для PostgreSQL 18
docker run -d --name pg_durable_pg18 -p 5433:5432 -e POSTGRES_PASSWORD=secret ghcr.io/microsoft/pg_durable:pg18
Подключение к запущенной СУБД выполняется через консольный клиент psql:
psql "postgresql://postgres:secret@localhost:5432/postgres"
!WARNINGВажное предупреждение о безопасности: Docker-образ предназначен только для локального ознакомления и отладки. Он поставляется с настройками, которые разрешают суперпользователю беспрепятственный доступ к инстансам оркестрации. Кроме того, исходящие HTTP-запросы (egress policy) ограничены правилом
http-allow-azure-domains(только к доменам Azure). Этот образ запрещено использовать в продакшене.
Шаг 2. Конфигурация shared_preload_libraries и активация
Поскольку pg_durable использует фоновые воркеры, код расширения должен быть предварительно загружен ядром PostgreSQL при запуске.
- Добавьте в файл
postgresql.confимя расширения:shared_preload_libraries = 'pg_durable' - Перезапустите службу PostgreSQL.
- Подключитесь к базе данных
postgres(расширение устанавливается только туда, чтобы избежать конфликтов воркера) и выполните команду:CREATE EXTENSION pg_durable;
Настройка прав доступа и безопасность
Команда CREATE EXTENSION не открывает доступ к функциям оркестрации по умолчанию. Права должны выдаваться администратором БД. Безопасность внутри pg_durable опирается на Row-Level Security (RLS). Это гарантирует, что пользователи могут видеть и изменять (например, отменять через df.cancel()) только свои задачи.
Администратор может настроить права доступа двумя способами.
Способ 1. Назначение прав роли приложения
Вы можете предоставить права на работу с процессами конкретной роли приложения:
SELECT df.grant_usage('app_role');
Способ 2. Использование разделяемой роли доступа (рекомендуемый подход)
Рекомендуется создать выделенную роль без права входа (NOLOGIN), делегировать ей права pg_durable, а затем включить в нее учетные записи служб:
CREATE ROLE pg_durable_user NOLOGIN;
SELECT df.grant_usage('pg_durable_user');
GRANT pg_durable_user TO app_backend, etl_service;
!IMPORTANTВажные особенности безопасности:
- Фоновый воркер работает под учетной записью, указанной в конфигурационном параметре GUC
pg_durable.worker_role(по умолчанию этоpostgres). Эта роль должна быть суперпользователем, чтобы обходить правила RLS при управлении задачами всех пользователей.- Переменные в
df.varsизолируются для каждого пользователя с помощью RLS. Настоятельно рекомендуется не сохранять секретные данные (пароли, ключи API) в открытом виде внутри этих переменных.- Права доступа, выданные через
df.grant_usage, распространяются только на существующие функции. После обновления версии расширения (например, черезALTER EXTENSION pg_durable UPDATE) администратор должен выполнить командуdf.grant_usageповторно.
Тестирование и ограничения
Для проверки совместимости с pg_durable предусмотрено несколько тестовых сценариев. В системе должны быть установлены компилятор Rust (nightly) и cargo-pgrx версии 0.16.1.
1. Тесты pg_regress
Это тесты для проверки корректности работы функций SQL DSL. Тестовый SQL расположен в каталоге sql/, а ожидаемые результаты — в expected/:
make test-regress # Сброс состояния и полный запуск тестов
make installcheck # Запуск тестов на уже работающем экземпляре
2. Сценарии сквозного тестирования (E2E)
Сквозные сценарии проверяют сложные ситуации, такие как аварийный останов СУБД во время выполнения процесса:
./scripts/test-e2e-local.sh # Запуск всех локальных интеграционных сценариев
./scripts/test-e2e-local.sh 04_parallel # Запуск конкретной группы тестов
3. Пайплайн CI/CD и ограничения
Все изменения проходят автоматические проверки, описанные в .github/workflows/ci.yml. Вы можете запустить их локально:
cargo fmt --check # Проверка форматирования кода
cargo clippy # Статический анализ кода
Инструмент не следует использовать, если:
- Задача простая. Если операция решается одним запросом (например,
INSERT INTO ... SELECT), накладные расходы будут избыточными. - Требуется субмиллисекундный ответ. pg_durable спроектирован под надежную фоновую обработку.
- Управляемые облачные базы данных. Если облачный провайдер запрещает изменять
shared_preload_librariesили запускать фоновые процессы суперпользователя. - Сложная логика вне СУБД. Если процесс состоит из множества внешних вызовов, эффективнее использовать Temporal. В этом случае библиотеки
duroxideиduroxide-pgможно подключить напрямую в коде приложения.

