pg_durable: устойчивое выполнение внутри PostgreSQL

Устойчивое выполнение внутри 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-вызовы.

Как это работает

  1. Опишите рабочий процесс на SQL с помощью компонуемых операторов, таких как ~> и |⇒.

  2. Запустите его через df.start() и получите обратно идентификатор экземпляра.

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

  4. Запрашивайте статус и результаты из 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-ы должны пройти следующие проверки перед слиянием:

  1. Проверка форматированияcargo fmt --check

  2. 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

© 2026 meganuke