Устойчивое выполнение внутри PostgreSQL
Долгоживущие, отказоустойчивые SQL-функции для команд, которые уже хранят состояние в Postgres и хотят прекратить склеивать воедино cron-задания, воркеры, очереди и таблицы статусов ради надёжной фоновой обработки. Опишите рабочий процесс на SQL, позвольте pg_durable сохранять контрольную точку (checkpoint) после каждого шага и возобновлять выполнение после сбоев, перезапусков или упавших шагов.
Устойчивое выполнение (durable execution) — уже стандартный отраслевой паттерн, и pg_durable реализует его прямо внутри Postgres без какой-либо дополнительной сервисной инфраструктуры. Это часть нашей миссии — приближать вычисления к данным.
*Попробуйте pg_durable прямо сейчас в https://aka.ms/AzureHorizonDB[Azure HorizonDB]* — новом облачном сервисе PostgreSQL от Microsoft, спроектированном для высокой производительности и https://aka.ms/horizondb_pg_durable[построенном с pg_durable внутри].
Подходит ли это мне?
Для кого это
-
Бэкенд- и дата-инженеры, которые хотят, чтобы рабочие процессы жили рядом с теми данными, с которыми работают.
-
DBA и SRE, автоматизирующие runbook-ы, которые должны переживать перезапуски и поддаваться аудиту средствами SQL.
-
Команды, строящие data- или AI-пайплайны, которым нужно устойчивое выполнение на уровне строки, документа или батча.
Основная идея
Функция pg_durable — это граф SQL-шагов, которые PostgreSQL выполняет и контрольно сохраняет по мере продвижения. Если база данных падает, перезапускается или какой-то шаг завершается ошибкой, выполнение возобновляется с последней устойчивой контрольной точки — вместо того чтобы восстанавливать состояние вручную.
Рабочие нагрузки, для которых это полезно
-
Пайплайны векторных эмбеддингов: разбить на чанки, вызвать embedding API и выполнить upsert в
pgvector. -
Пайплайны приёма данных: стейджинг, дедупликация, трансформация и публикация крупных батчей.
-
Плановое обслуживание: обнаружить bloat, уведомить, дождаться подтверждения, затем выполнить следующее действие.
-
Fan-out агрегация: выполнять независимые запросы параллельно, а затем объединять результаты.
-
Рабочие процессы с внешними API: обогащение, классификация и webhook-подобные вызовы из SQL.
Что вы, скорее всего, делаете сегодня вместо этого
-
pg_cronплюс таблица заданий, колонки статусов, счётчики повторов и polling-воркер. -
Внешний оркестратор — Airflow, Temporal, Step Functions или Argo, обращающийся обратно в Postgres.
-
Очередь плюс воркеры плюс отдельная таблица состояния для координации повторных попыток и частичного завершения.
-
Процедура
plpgsql, которая работает до тех пор, пока сбой или долгая транзакция не вынудит начать заново.
Какие проблемы это решает
-
Перезапуск посреди длинного задания означает повторное выполнение уже успешно завершённой работы.
-
Одна упавшая строка или один неудачный вызов API превращается в ручную очистку с неопределённым воспроизведением.
-
Долгие транзакции удерживают блокировки, разрастают WAL и делают пакетные задания хрупкими при больших объёмах.
-
Параллельная работа на уровне app-tier создаёт больше мест для ошибок частичного отказа и рассинхронизации.
-
Логика рабочего процесса расползается по SQL, воркерам, очередям, дашбордам и таблицам статусов.
Что меняется в вашей архитектуре
-
Определение рабочего процесса переходит в SQL и запускается через
df.start(…). -
Состояние повторных попыток, отслеживание прогресса и контрольное сохранение переходят в Postgres вместо самодельного кода на уровне приложения.
-
Часть воркеров на уровне приложения, потребителей очередей или планировщик-связующее может исчезнуть полностью.
-
Операционная видимость обеспечивается через таблицы Postgres, например
df.instances, с той же моделью авторизации и резервного копирования, что и у ваших данных.
Когда не стоит использовать
-
Задание уже представляет собой один
INSERT … SELECTили один обычный SQL-запрос. -
Вам нужна синхронная обработка запросов с субмиллисекундными задержками, а не устойчивое фоновое выполнение.
-
Вы не можете устанавливать расширения или запускать фоновый воркер в вашей среде Postgres.
-
Рабочий процесс в основном живёт за пределами Postgres и охватывает много разнородных систем.
-
Вам нужна произвольная логика приложения, которая не укладывается в SQL-шаги, ветвления, циклы или HTTP-вызовы.
Как это работает
-
Опишите рабочий процесс на SQL с помощью компонуемых операторов, таких как
~>и|⇒. -
Запустите его через
df.start()и получите обратно идентификатор экземпляра. -
Позвольте среде выполнения исполнять каждый шаг устойчиво, сохраняя контрольные точки между шагами.
-
Запрашивайте статус и результаты из PostgreSQL пока рабочий процесс выполняется или после его завершения.
Ограничения
Модель намеренно ориентирована на SQL. Если шагу требуется произвольный код, SDK без HTTP-поддержки или насыщенное управление потоком исполнения в памяти, возможно, придётся обернуть эту логику в SQL-функцию, вынести её за HTTP-эндпоинт для df.http() или использовать для этой части системы универсальный оркестратор.
Возможности
-
Устойчивость — Состояние функций сохраняется в PostgreSQL. Переживает сбои, перезапуски и переключения на резервный узел (failover).
-
SQL-нативность — Функции описываются на SQL с помощью компонуемых операторов.
-
Осведомлённость о базе данных — Примитивы первого класса для планирования, условий и параллельного выполнения.
-
Нулевая инфраструктура — Работает как расширение PostgreSQL. Не нужен Redis, Temporal или внешние сервисы.
Быстрый пример
-- Устойчивая функция, которая обрабатывает данные пошагово
SELECT df.start(
'SELECT id FROM documents WHERE processed = false LIMIT 100' |=> 'batch'
~> 'UPDATE documents SET processed = true WHERE id = ANY($batch)'
);
Пакеты
Теговые релизы публикуют Debian-пакеты для PostgreSQL 17 и 18 на архитектуре amd64 из ассетов GitHub-релиза. Пакеты называются pg-durable-postgresql-<PG major>_<pg_durable version>-1_<arch>.deb и устанавливают библиотеку расширения, control-файл и SQL-файлы обновления в соответствующие каталоги установки PostgreSQL.
Теговые релизы также публикуют готовый к запуску Docker-образ (linux/amd64) для PostgreSQL 17 и 18 в GitHub Container Registry: ghcr.io/microsoft/pg_durable. Образ устанавливает выпущенный Debian-пакет поверх официального образа postgres. Каждый релиз публикует неизменяемые теги X.Y.Z-pg<major> и vX.Y.Z-pg<major> (например, 0.2.2-pg17, 0.2.2-pg18); наивысший стабильный релиз дополнительно обновляет плавающие теги pg<major>, а мажорная версия по умолчанию (pg17) также обновляет latest. Мажорная версия PG является частью каждого тега, поэтому несколько версий PostgreSQL могут публиковаться рядом друг с другом. Просмотреть все опубликованные образы и теги можно по адресу https://github.com/microsoft/pg_durable/pkgs/container/pg_durable.
Предупреждение: Опубликованный Docker-образ предназначен исключительно для оценки и изучения pg_durable — не используйте его в продакшене. Он включает экземпляры с правами суперпользователя для удобной демонстрации «из коробки». Его политика HTTP-исходящего трафика соответствует той, с которой был собран выпущенный Debian-пакет (
http-allow-azure-domains— только домены Azure). Образы для нескольких архитектур (linux/arm64) пока не публикуются; они появятся после того, как будут готовы Debian-пакеты для arm64.
Запустите опубликованный образ — PostgreSQL 17 и 18 могут работать одновременно на разных портах хоста:
# PostgreSQL 17 (тег `latest` также указывает на последний релиз PG17)
docker run -d --name pg_durable_pg17 \
-p 5432:5432 \
-e POSTGRES_PASSWORD=secret \
ghcr.io/microsoft/pg_durable:pg17
# PostgreSQL 18 (запуск рядом с PG17 на другом порту хоста)
docker run -d --name pg_durable_pg18 \
-p 5433:5432 \
-e POSTGRES_PASSWORD=secret \
ghcr.io/microsoft/pg_durable:pg18
# Подключение через psql (PG17 на 5432, PG18 на 5433)
psql "postgresql://postgres:secret@localhost:5432/postgres"
psql "postgresql://postgres:secret@localhost:5433/postgres"
Расширение предварительно загружается и создаётся в базе данных postgres при первой инициализации. POSTGRES_DB игнорируется — pg_durable всегда устанавливается в postgres, чтобы расширение и фоновый воркер никогда не работали с разными базами данных. Для воспроизводимых развёртываний закрепляйте неизменяемый тег X.Y.Z-pg<major> (например, 0.2.2-pg17), а не плавающие теги pg<major>/latest; неизменяемые теги никогда не перезаписываются после публикации.
После установки пакета добавьте pg_durable в shared_preload_libraries, перезапустите PostgreSQL и создайте расширение в настроенной базе данных pg_durable:
CREATE EXTENSION pg_durable;
База данных pg_durable по умолчанию — postgres; настройку фонового воркера и привилегий см. в User Guide.
Каждый релиз также публикует архивы исходного кода для сборки из источников и файл SHA256SUMS для проверки загруженных ассетов.
Установка для разработки
Предварительные требования
-
PostgreSQL 17 или 18
-
Rust (nightly)
-
cargo-pgrx 0.16.1
GitHub Codespace
Пресборка главной ветки устанавливает PostgreSQL 17, собирает pg_durable и подготавливает локальный кластер в ~/.pgrx с готовым расширением. PostgreSQL при этом не запускается, поэтому запустите его перед началом работы.
# Запустить PostgreSQL
./scripts/pg-start.sh
# Подключиться
~/.pgrx/17.*/pgrx-install/bin/psql -h localhost -p 28817 -d postgres
На ветке без готовой пресборки запустите pg-start.sh — при первом запуске он соберёт и установит расширение (ожидайте несколько минут):
./scripts/pg-start.sh
Другие окружения
Локальная машина и Dev Container
VS Code Dev Container (.devcontainer/) поставляет предустановленные Rust, cargo-pgrx и PostgreSQL 17. На чистой локальной машине сначала установите toolchain, следуя шагам из .devcontainer/onCreateCommand.sh.
# Собрать, инициализировать PostgreSQL и установить расширение
# Занимает некоторое время — можно заняться чем-нибудь другим
./scripts/pg-start.sh
# Подключиться к локальному экземпляру pgrx PostgreSQL
~/.pgrx/17.*/pgrx-install/bin/psql -h localhost -p 28817 -d postgres
pg-start.sh инициализирует новые локальные каталоги данных с суперпользователем postgres, а также создаёт соответствующую роль суперпользователя для текущего пользователя ОС, чтобы стандартное локальное использование psql продолжало работать. Используйте -U postgres, если хотите явно указать канонический bootstrap-роль.
Docker
Чтобы запустить заранее собранный опубликованный образ, см. раздел Пакеты. Для локальной разработки и тестирования соберите и запустите из исходного кода:
# Сборка и тестирование (Dockerfile из исходников — компилирует расширение)
./scripts/test-e2e-docker.sh --rebuild
# Опционально: развернуть в ACR (для кастомного образа PG17 с pg_durable)
./scripts/deploy-acr.sh
Опубликованный образ GHCR устанавливает выпущенный
.debповерх официального образаpostgres; исходныйDockerfile, используемый здесь, компилирует расширение и предназначен для CI и локальной разработки. Это разные артефакты.
Многопользовательская настройка
CREATE EXTENSION pg_durable не выдаёт никаких привилегий роли PUBLIC. После установки расширения администратор должен явно предоставить доступ прикладным ролям. Защита на уровне строк (Row-level security, RLS) гарантирует, что каждый пользователь может видеть и управлять только своими экземплярами и узлами устойчивых функций.
Выдача привилегий прикладной роли:
-- Выдать конкретным ролям после CREATE EXTENSION
SELECT df.grant_usage('app_role');
Можно также создать промежуточную роль и предоставить членство в ней прикладным ролям:
-- Создать общую роль для доступа к pg_durable
CREATE ROLE pg_durable_user NOLOGIN;
SELECT df.grant_usage('pg_durable_user');
-- Предоставить членство прикладным ролям
GRANT pg_durable_user TO app_backend, etl_service;
См. раздел User Guide — Privilege Grants с полным списком отдельных грантов, отзывом доступа и усилением безопасности обновлённых установок.
Примечание:
GRANT EXECUTE ON ALL FUNCTIONSприменяется только к функциям, существующим на момент выполнения гранта. После обновления pg_durable черезALTER EXTENSION pg_durable UPDATEповторно выполнитеdf.grant_usage('role')(или переиздайте ручные гранты), чтобы новые функции стали доступны.
Ключевые моменты:
-
Роль фонового воркера (GUC
pg_durable.worker_role, по умолчанию:postgres) должна быть суперпользователем — она обходит RLS для управления экземплярами всех пользователей. -
Пользователи получают
SELECT+INSERTнаdf.instances/df.nodes, а такжеUPDATE (status, updated_at)на уровне столбцов дляdf.cancel(). -
Identity-колонка (
submitted_by) не может изменяться пользователями. -
df.varsиспользует разграничение по пользователям — у каждого пользователя есть своё пространство переменных через колонкуownerи RLS. Суперпользователи обходят RLS, но DSL-функции всё равно ограничивают область видимости вызывающим пользователем через явные фильтры. Не храните секреты в открытом виде.
Непрерывная интеграция
Все pull request-ы должны пройти следующие проверки перед слиянием:
-
Проверка форматирования —
cargo fmt --check -
Clippy и тесты —
cargo clippy, юнит-тесты (cargo pgrx test pg17), тесты pg_regress и E2E-тесты.
Рабочий процесс CI описан в .github/workflows/ci.yml. Он использует pgrx для загрузки PostgreSQL и управления им.
Тестирование
pg_durable имеет два набора тестов:
Тесты pg_regress (стандартные регрессионные тесты PostgreSQL)
Быстрые, детерминированные тесты базовой функциональности DSL с использованием стандартного фреймворка тестирования PostgreSQL. SQL тестов находится в sql/, ожидаемый вывод — в expected/, PGXS настраивается в корневом Makefile.
make test-regress # полный сброс + запуск
make installcheck # только запуск (PostgreSQL должен уже работать)
E2E-тесты (комплексные сценарные тесты)
Сложные локальные интеграционные тесты с PostgreSQL через pgrx:
./scripts/test-e2e-local.sh # Все локальные SQL E2E-тесты, включая специальные фазы перезапуска/конфигурации
./scripts/test-e2e-local.sh 04_parallel # Конкретный тест
./scripts/test-e2e-local.sh --default-build-phases # Только группа фаз default-build
Подробности см. в tests/e2e/.
Документация
-
User Guide — Полное руководство по использованию с примерами.
-
MVP Guide — Детали реализации и внутреннее устройство.
-
Examples — Соглашения о примерах и рекомендации по smoke-check.
Архитектура
pg_durable — это расширение PostgreSQL (построено с помощью pgrx) — всё работает внутри сервера PostgreSQL, никаких внешних сервисов. Расширение предоставляет SQL DSL для построения графов функций и регистрирует фоновый воркер, который выполняет их устойчиво поверх двух низкоуровневых библиотек Rust:
-
duroxide — фреймворк устойчивых задач, предоставляющий среду выполнения оркестрации (детерминированное воспроизведение, контрольные точки, суб-оркестрации, таймеры).
-
duroxide-pg — PostgreSQL-бэкенд хранения состояния для duroxide. Сохраняет состояние среды выполнения (экземпляры, историю, рабочие очереди) в специальной схеме
duroxide.*, которой владеет расширение.
┌────────────────────────────────────────────────────────────────────┐
│ PostgreSQL │
│ │
│ ┌──────────────────────────────────────────────────────────────┐ │
│ │ pg_durable extension (pgrx) │ │
│ │ │ │
│ │ SQL DSL 'sql' |=> 'name' ~> 'sql2' │ │
│ │ df.if() | df.join() | df.loop() │ │
│ │ │ │
│ │ Background worker (hosts the duroxide runtime in-process) │ │
│ │ ┌────────────────────────────────────────────────────────┐ │ │
│ │ │ duroxide (orchestration runtime) │ │ │
│ │ │ ┌──────────────────────────────────────────────────┐ │ │ │
│ │ │ │ duroxide-pg (PostgreSQL state provider) │ │ │ │
│ │ │ └──────────────────────────────────────────────────┘ │ │ │
│ │ └────────────────────────────────────────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ │
│ Schemas │
│ df.* DSL graphs (nodes, instances, vars) │
│ duroxide.* runtime state (owned by duroxide-pg) │
└────────────────────────────────────────────────────────────────────┘
Если вы предпочитаете создавать устойчивые функции на Rust, Python или Node, при этом сохраняя состояние в PostgreSQL, вы можете использовать duroxide и duroxide-pg напрямую из своего языка — pg_durable — это то, что вы построили бы поверх этой пары, если бы предпочли описывать логику на SQL.
Статус
Preview — Проект в настоящее время находится в стадии предварительного просмотра.
Поддержка
Используйте GitHub Issues для отчётов об ошибках и запросов функций. Не сообщайте об уязвимостях безопасности через публичные GitHub Issues — следуйте инструкциям в SECURITY.md.
Кодекс поведения
Этот проект принял Microsoft Open Source Code of Conduct. Подробнее см. в FAQ по кодексу поведения или свяжитесь с opencode@microsoft.com по вопросам и комментариям.
Безопасность
Microsoft серьёзно относится к безопасности своих программных продуктов и сервисов. Пожалуйста, не сообщайте об уязвимостях безопасности через публичные GitHub Issues. Инструкции по сообщению об уязвимостях см. в SECURITY.md.
Конфиденциальность и телеметрия
pg_durable не отправляет телеметрию в Microsoft.
Товарные знаки
Этот проект может содержать товарные знаки или логотипы проектов, продуктов или сервисов. Авторизованное использование товарных знаков или логотипов Microsoft регулируется Руководством по товарным знакам и бренду Microsoft и должно ему соответствовать. Использование товарных знаков или логотипов Microsoft в изменённых версиях этого проекта не должно вводить в заблуждение или подразумевать спонсорство Microsoft. На использование товарных знаков или логотипов третьих сторон распространяются политики соответствующих третьих сторон.
Лицензия
PostgreSQL License