Транзакции PostgreSQL, суперспособность распределённых систем

Транзакции PostgreSQL, суперспособность распределённых систем

В статье разбирается архитектурный принцип, который переворачивает привычное разделение хранилищ: когда метаданные рабочего процесса и данные приложения лежат в одной базе PostgreSQL, их можно обновлять в рамках одной транзакции. Это исключает частичные отказы и упрощает обработку граничных случаев.

Первая проблема, идемпотентность шагов. Обычно при восстановлении сбойного рабочего процесса система возобновляет работу с последней контрольной точки, но может повторно выполнить шаг, уже успевший записать результат. Классическое решение, вести отдельную таблицу применённых платежей и проверять её перед операцией. Но если шаг и его контрольная точка записаны в одной транзакции, обновление данных либо произойдёт вместе с записью контрольной точки, либо откатится полностью. Повторное выполнение безопасно и не требует дополнительной бухгалтерии.

Вторая проблема, атомарность при обновлении нескольких систем. Например, при оформлении заказа нужно обновить БД и запустить рабочий процесс доставки на складе одновременно, иначе возникают несогласованности. Стандартный паттерн, "transactional outbox": одна транзакция обновляет запись и пишет сообщение в таблицу outbox, затем фоновый процесс доставляет эти сообщения в целевую систему. При со-локализации этот процесс упрощается: достаточно user-defined function (UDF) в PostgreSQL, которая ставит в очередь рабочий процесс в той же транзакции, что и основное обновление. Затем рабочий процесс выполняется асинхронно, надёжность гарантирована.

Суть в том, что одна транзакция решает сразу две проблемы: либо всё фиксируется, либо ничего не фиксируется. Это исключает окно ошибки между обновлением и контрольной точкой, а значит, шаги больше не нуждаются в защитных механизмах на уровне приложения.

Ключевые факты

  • Со-локализация состояния рабочего процесса и данных приложения в одной PostgreSQL-базе превращает два хранилища в одно источник истины
  • Идемпотентность автоматически достигается, если шаг и его контрольная точка записываются в одной транзакции, откат затрагивает оба или ничего
  • Атомарность при многосистемных обновлениях достигается через транзакционный outbox либо через UDF, который ставит в очередь рабочий процесс в той же транзакции
  • Устраняется промежуток между обновлением данных и записью контрольной точки, сигнал надежности проверяется на уровне БД, а не приложения
  • Снижается операционная сложность: не нужны отдельная инфраструктура для опроса outbox-таблицы, примирение несогласованностей и специальные job на их разрешение

Почему это важно

В распределённых системах частичные отказы неизбежны: процесс может упасть после завершения шага, но до записи контрольной точки, либо обновить БД в одной системе, но не суметь известить другую. Классические решения (дополнительные таблицы, background worker, reconciliation job) добавляют хрупкость и операционный оверхед. Если же все изменения (данные и метаданные) происходят в одной транзакции, граничные случаи исчезают: транзакция либо коммитится полностью, либо откатывается. Это упрощает логику и снижает вероятность ошибок.

Кому это важно

Разработчики, проектирующие надёжные асинхронные рабочие процессы (workflow): обработка заказов, платежи, интеграции между сервисами. DevOps и SRE, которые поддерживают такие системы и хотят снизить операционный груз. Компании, использующие микросервисы и нуждающиеся в надежной доставке сообщений. Тем, кто выбирает между PostgreSQL и специализированными движками работ (Temporal, Airflow) с точки зрения сложности и надежности.

Как это применить

Если используется PostgreSQL, настроить рабочий процесс так, чтобы состояние хранилось в БД, а не во внешней системе. Для идемпотентности: писать контрольную точку шага в ту же транзакцию, что и обновление данных. Для атомарности: вместо отдельной outbox-таблицы и polling-процесса использовать UDF PostgreSQL, чтобы ставить задачи в очередь внутри транзакции. Затем отдельный worker деквирует и выполняет асинхронно. Примеры и документация доступны на сайте DBOS (github.com/dbos-inc, docs.dbos.dev).

Можно ли доверять

DBOS, компания, разрабатывающая инфраструктуру для durably-executed workflow на базе PostgreSQL. Объяснение опирается на хорошо известные паттерны: transactional outbox описан в Enterprise Integration Patterns и используется десятилетиями, идемпотентность через контрольные точки, стандарт в workflow-системах. Теория ACID-транзакций не новая, но применение её к со-локализации workflow и данных, ценный практический вывод. Код открыт (GitHub), примеры достаточно конкретны для воспроизведения.

Риски и подводные камни

Со-локализация работает хорошо, если все операции можно выполнить в одной БД транзакции, для очень больших распределённых систем это может быть узким местом. Нужна дисциплина в проектировании: UDF и логика шагов должны быть идемпотентными на уровне кода. Если обновление БД долгое, оно блокирует следующий шаг, нужно следить за времени отклика транзакции. Для систем с очень высокой нагрузкой на запись PostgreSQL может потребоваться репликация и failover-логика. Как и любой паттерн, это не серебряная пуля, а альтернатива с иными trade-off.

«В распределённых системах со-локализация, суперспособность. Когда метаданные рабочего процесса и данные приложения лежат в одной базе PostgreSQL, их можно обновлять в одной транзакции. Это означает, что частичные отказы больше невозможны, что делает гораздо проще построение рабочих процессов, правильно обрабатывающих все граничные случаи.»

— DBOS