世界を動かす技術を、日本語で。

Postgresのトランザクションは分散システムのスーパー・パワーです

2026年7月3日原文(dbos.dev)

概要

  • Postgres を活用したワークフロー管理の新提案
  • アプリケーションデータとワークフローステートの 同居 による利点の説明
  • トランザクション で実現する冪等性と原子性の簡素化
  • Transactional Outbox パターンの課題と解決策
  • DBOSによる Postgresワークフローエンジン の紹介

Postgresでワークフロー管理を統合する利点

  • アプリケーションデータワークフローステート を同じPostgresデータベースに格納する提案
  • 一見、関心事の分離が必要に思えるが、 分散システム ではデータの共存が大きな強み
  • ワークフローメタデータとアプリケーションデータを 同一トランザクション で更新可能
    • 部分的な失敗が発生しないため、エッジケースの対応が容易
  • トランザクションで冪等性や原子性の課題を シンプルに解決 可能

トランザクションで実現する冪等性

  • 分散システム における大きな課題は冪等性の確保
  • ワークフローは各ステップの チェックポイント を記録し、途中中断時は直前のステップから再開
    • しかし、処理完了後にチェックポイント記録前に中断すると、再実行時に 重複処理 が発生
  • 従来はアプリケーション側で bookkeepingテーブル を用意し、二重処理を防止
    • 例:applied_paymentsテーブルで支払いの重複を管理
  • ワークフロー状態とアプリケーションデータの同居 により、複雑なbookkeepingが不要
    • ワークフローエンジンが提供するトランザクションで、データ更新とチェックポイント記録を 同時にコミット
  • トランザクションがコミットされれば、 処理とチェックポイントが確実に記録
    • コミット前に失敗した場合は全てロールバック
  • アプリケーション側で冪等性ロジックや管理テーブルが不要となり、 exactly-once実行保証

トランザクション・ワークフローアウトボックスによる原子性

  • 複数システム 間での原子性確保も分散システムの難題
    • 例:注文データベース更新と外部通知の同時実行
  • 従来は Transactional Outboxパターン を採用
    • アウトボックステーブルにメッセージを書き込み、別プロセスで通知
    • 一つのトランザクションでデータ更新とアウトボックス書き込みを実現
  • この方式は 運用負荷 が高く、ポーリングやリトライ、監視が必要
    • ワークフローエンジンが別システムの場合、同期ズレや追加インフラが発生
  • Postgres UDF を活用し、アプリケーションデータ更新とワークフローキューへの登録を 同一トランザクション で実行
    • enqueue_workflow UDFでワークフロー行を作成
    • 更新とキューイングの 原子性 を保証
    • ワーカーが非同期でワークフローを実行し、信頼性を担保

Postgresワークフローエンジンの今後

  • DBOS は、Postgresベースの耐障害性ワークフロー実行を シンプルかつ高性能 に実現することを目指す
  • スケーラブルかつ信頼性の高いシステム構築に興味がある方への案内
    • Quickstart: https://docs.dbos.dev/quickstart
    • GitHub: https://github.com/dbos-inc
    • Discordコミュニティ: https://discord.gg/eMUHrvbu67

Hackerたちの意見

おめでとう、ミューテックスを発見したね。本当に分散システムなのか、それとも中央データベースを持ったサービスの集まりなのか?

分散と非中央集権が同じ意味だとは思わないな。ハブアンドスポークの鉄道システムは中央集権的だけど、複数の列車が同時に走っているなら分散システムでもある。分散システムは何らかの形で調整が必要で、単一の中央DBはその方法の一つだと思う。*: 編集:ここでのより良い例は、単一の中央ディスパッチャーを持つ鉄道システムで、中央集権的だけど分散しているかもしれない。

本当に分散システムなのか、それとも中央データベースを持つサービスの集まりなのか?Zookeeperを使うたびにこの疑問が浮かぶんだ。Apache Kafkaがその問題の代表例で、HBaseも近い位置にいるね。

UDFの最後のポイントがよくわからない。異なるシステム間で状態を原子的に更新する必要があるのか、そうでないのか。将来的にランダムなタイミングで2つ目を更新するためにシステムに行を追加するのは、キューにジョブを入れるのとあまり変わらない気がする。

重要なのは、UDFのエンキューがデータベースの更新とトランザクションとして結びついていることだ。例えば、データベースの更新が新しい注文の挿入だとしよう。これにより、新しい注文が挿入された場合、注文を処理するためのジョブもエンキューされることが保証される。新しい注文が挿入されても、その処理ジョブがエンキューされないことはあり得ない。だから、耐久性のあるワークフロー/キューシステムが、エンキューされた処理ジョブが実際に実行されることを保証する役割を果たす。

君の直感は正しいと思うよ。これはメッセージキューを再発明してるように聞こえる。将来的にこれを試す人は、順序、コミット、パーティショニング、デッドレターキュー、リプレイ可能性、電話しないで、私が電話するから、など、Kafkaのようなものが持っている痛い教訓を学ぶかもしれないね。

私の理解では、ワークフローの進行単位とデータベースのコミット単位を1対1で合わせているってことだね。つまり、ワークフローの各ステップがデータベースのコミット単位になる。だから、アウトボックスパターンが簡素化されるんだ。でもその代わり、データベース自体がワークフローに密接に結びつくから、後で分離するのがアーキテクチャ的に難しくなる。とはいえ、実際にはデータベースを分離する必要はほとんどないんだけどね。ほとんどのサービスでは、メッセージブローカーやワークフローエンジンを入れ替えることはあるけど、データベースはほぼそのままだから。この理解が合ってるかは自信ないな。

そう、コアデザインはデータベース上にワークフローシステムを構築することなんだ。要するに、ほとんどのワークフローシステムが使う中央オーケストレーターをPostgresデータベースに置き換えるってこと。この前のブログ記事で詳しく説明してるよ:https://www.dbos.dev/blog/postgres-is-all-you-need-for-durab...(HNのディスカッションもね:https://news.ycombinator.com/item?id=48313530)

クライアントのメール送信のために、外部サービスとのインタラクションに対してトランザクションの原子性を利用したフェイルセーフアプローチを採用してる。正式なキューを使っても同じように動作して、今日の保証を得ることができるけど、当時はそんなインフラにお金をかけるほどの規模じゃなかったからね。内部では、保留状態から計算された状態にデータを変換するために複雑なロジックを実行するジョブがあって、DBの原子性に依存してデータが成功裏に移行することを保証してる。そのタスクはすごく頑丈なんだけど、二次的な永続ストレージが関与する場合、トランザクションの保証は何らかの形で妥協しなきゃいけない。メール送信の例では、クライアントがすべての通知を受け取ることを保証する方が、通知が正確に一度だけ送信されることを保証するより重要だと思ってる。だから、メール送信が成功したことを確認してから、そのメッセージを保留リストから削除するトランザクションを閉じるってメカニズムを使ってる。太陽フレアとかの影響で潜在的な損失が出る可能性は常にあるけど、こういうシステムを設計する際の鍵は、システムがどう失敗するかを理解し、その結果を受け入れて、できるだけサイクルやロジックの距離を縮めることだと思う。ロジックは、不可逆的なアクションが起こる前にできるだけ準備作業をして、不可逆的なアクションは好みに応じて順序を決めて、安全かつ迅速に安価に処理するべきだね。

うちには、メインアプリケーションのデータベースにあるインハウスのpubsubソリューションがあって、記事に書いてある通りの感じだよ。そして、それが許す原子性は本当に素晴らしい!

数年前、ある面接でこの点で退席したことがあるんだ。技術的な質問の一つが「データベースとメッセージキューがあったら、どうやって両方を更新するか、またはどちらも更新しないか(つまり、トランザクショナルに)」ってやつだった。数分考えた後、「無理だし、君も無理だよ」と返したんだ。それから、レプリケート状態マシンや書き込み先行ログ、イベントソーシング(その時の呼び方は何でもいいけど)を使って、最終的な整合性に頼るのが唯一の実用的な解決策だって提案した。そしたら、彼がアウトボックスパターンについて聞いたから、説明してもらった。確かに、この記事に似てるなと思ったよ。データベースDとメッセージキューQをトランザクションで扱う秘訣は、Dを二つの部分(ステートとアウトボックス)に分けて、代わりにそれら(S,O)を通じてトランザクションを行い、DとQの間にトランザクションがあるふりをすることなんだ。

メッセージキューを同じデータベースに入れればいいじゃん。

Hacker Newsで議論の続きを見る