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

耐久性のあるワークフローにはPostgresを使おう

概要

  • Durable workflows は、プログラムの進捗をデータベースに定期的に保存し、障害時に直前の状態から復旧可能
  • 多くのシステムでは 外部オーケストレーター が使われるが、これは複雑化の要因
  • Postgres などのデータベース自体をオーケストレーターとして利用することで、シンプルかつ効率的な設計が実現可能
  • 可用性・スケーラビリティ・観測性・セキュリティ の課題はPostgresの既存機能で十分に対応可能
  • Postgresを活用した durable workflows の利点と具体的な設計案を紹介

Durable Workflowsの基本と課題

  • Durable workflows は、プログラムの進行状況を定期的に データベースにチェックポイント として保存する設計
  • プログラムがクラッシュしても、 最後のチェックポイント から再開できるため、信頼性向上
  • 例えるなら、 ビデオゲームのセーブ機能 と同様の仕組み
  • 一般的には、 TemporalAirflowAWS Step Functions のような 外部オーケストレーター が中心となる構成
  • ワークフローの各ステップの進行・チェックポイント管理・障害時のリカバリを オーケストレーター が担当

外部オーケストレーションの複雑さ

  • 外部オーケストレーター は、設計と運用の両面で 複雑化 の原因
  • Durable workflowsの本質は データベースによる状態管理 にあるため、 専用オーケストレーターは不要 という主張
  • Postgres などのデータベースを直接活用することで、 設計の単純化と効率化 が可能

Postgresを用いたDurable Workflowsの設計

  • アプリケーションサーバー が直接 Postgres と通信し、ワークフローを実行
  • クライアントは、 Postgresのworkflowsテーブル にワークフローを登録
  • サーバーはテーブルを ポーリング してワークフローを取得・実行
  • 各ステップの出力を Postgresにチェックポイント として保存
  • サーバー障害時も、 他サーバーがチェックポイントから復旧 できる設計
  • ロック機構一意制約 を活用し、ワークフローの重複実行を防止

Postgres活用によるメリット

  • 中央オーケストレーター不要、アプリケーションサーバー間で Postgresを介した協調 が可能
  • スケーラビリティ :サーバー追加で水平方向の拡張が容易、Postgres自体の性能が上限
  • 可用性 :Postgresのレプリケーションや自動フェイルオーバー機能を活用
  • 観測性 :ワークフローやステップの状態が Postgresテーブル に蓄積されるため、 SQLクエリ で柔軟に可視化・分析
    • 例:直近1か月でエラーとなったワークフローの抽出など
  • 信頼性・セキュリティ :外部オーケストレーターを排除し、 Postgresのみが単一障害点 となる設計
    • 既存の アクセス制御・監査 機能をそのまま利用可能
    • 新たなクリティカルインフラの導入不要

Postgres Orchestrationの今後

  • Postgres を用いたdurable workflowsは、 シンプルで高信頼 なシステム構築を実現
  • DBOS など、Postgresベースのdurable executionに特化したプロダクトも登場
  • さらなる情報や実例は以下を参照
    • Quickstart: https://docs.dbos.dev/quickstart
    • GitHub: https://github.com/dbos-inc
    • Discordコミュニティ: https://discord.gg/eMUHrvbu67

まとめ

  • Durable workflowsは データベース中心設計 で本質的にシンプル化可能
  • Postgres の既存機能を活用することで、 大規模・高可用性・高観測性・高セキュリティ を実現
  • 新たなオーケストレーターの導入よりも、 既存インフラの活用 が合理的

Hackerたちの意見

DBOSとTemporalを使ってる人の体験談が気になるな。過去にTemporalを使ったことがあるけど、すごく良かった。ただ、リクエストペイロードやイベントサイズに制限があって、それが解決策を作るときにちょっと不便だったかな。良いエンジニアリングプラクティスを強制するけど、CSVファイルが2MBを超えると特別なロジックを書くのが面倒になることもある。S3にアップロードしてリンクを渡して、ワークフローでダウンロードするっていうのはね。DBOSの経験はどう?運用の複雑さや機能の整合性、他に何か気になることはある?

大きなペイロードの問題を解決するために外部ストレージのアプローチがリリースされたよ。100%好きってわけじゃないけど(後付け感があるし、内蔵されてるわけじゃない)、今は初期リリースだから、今のところはこれで解決したと考えてもいいかも。

AI生成ワークフローや動画ファイルの処理にDBOSを使ってるよ。Celeryからの移行には時間がかかったけど、うちの場合はやる価値があった。

大規模なオンプレミスのTemporalを運用してるんだけど、捨てアカウントだから多分バレるだろうな。1年以上プロダクションで運用してきたけど、個人的には設計が悪くて、遅いし、インフラ的に重すぎると思う。もし200以上のイベントやワークフローを扱って、数百を同時に一日中動かす必要があるなら、インフラに何百万もかかるし、結局すごく使いづらいよ。自分でベンチマークを試してみて、数字はひどいから。営業チームもひどくて、必死だし。開発者の視点から見ると、SDKは結構いいけどね。Nexusにハマらないようにして、営業チームから電話が来たら、法務も同席させることを忘れずに。

Temporalはちょっと複雑すぎると思ったけど、君が言ったように、良いエンジニアリングプラクティスを強制するのが一番のポイントだね。それから彼らのクラウドサービスを試してみたけど、価格に驚いたよ。何かをプロダクションに持っていく前に、1,000ドルの無料クレジットを使い切っちゃった。ローカルでTemporalを動かすのも面倒だったしね。結局、彼らのアーキテクチャからインスピレーションを得て、自分でPostgresでやるのが一番だと思う。

DBOSは使ったことないけど、今の仕事でTemporalを使ってて、前の仕事でも使ってたから、今は約1.5年の経験があるよ。家では、あまり時間に敏感じゃないホームオートメーションのタスクを処理するために使ってる。ワークフローのレイテンシはそんなに悪くないけど、家の動作イベントでトリガーされるものには使わないかな。非アクティブの後に何かをオフにするためのタイムアウトの話なら別だけど。自分のVPCやK8sクラスターの中で薄いREST APIを前に置いて、イベント駆動のトリガーを助けるのが好きなんだ。これでTemporalの認証やワークフローのステータス確認を気にしなくて済むから、できるだけロジックフリーに保てる。ざっくり言うと、DBトリガーがあって、そのトリガーが直接アクションを起こすか、イベントをキューに入れる。ハンドラーは必要なイベント詳細を持って薄いREST APIを呼び出す。REST APIはこれがワークフローを開始するか、既存のものにシグナルを送るか、無視するかを決める(このパターンは状況によって変わるけど、SignalWithStartは自分にとっては一般的だし、イベントがワークフローを開始する価値がない場合は無視することもある)。親子ワークフローの機能は、単一のオブジェクトのライフサイクルに対して異なる自己完結型の動作を調整する必要があるときに非常に価値があるし、外部要因がオブジェクトの軌道を変えたときにキャンセル可能なのもいい。長い話を短くすると、すごくパワフルで扱いやすいと思うし、ライフサイクルロジックをAPIから引き離すのに本当に役立ってる。ロジックを簡単そうな場所に投げるだけじゃなくて、後で隠れた罠になるのを避けるのに役立つよね。

DBOSはRustをサポートしてないから、https://github.com/tensorzero/durable でかなりミニマルなRust版を実装したよ。結構安定してて拡張性もあるけど、SQLの実装には注意が必要だね。ここにいる読者には興味深いと思うよ。

Conductor OSSはこれをうまくやってるよ。https://docs.conductor-oss.org/devguide/ai/index.html https://github.com/agentspan-ai/agentspan これは基本的にConductorのエージェントSDKレイヤーで、あなたのlanggraph、openAI、vercel、ADKエージェントを耐久性のあるものに変えて、コード変更なしでオーケストレーションを追加できるんだ。

うちのプロダクションではキューにRedisを使ってるけど、PostgresやMySQLをキューとして使ってるユーザーも見かけたよ。

Armin RonacherのabsurdはPostgres用の耐久性ワークフローの実装だよ。https://lucumr.pocoo.org/2025/11/3/absurd-workflows/ https://github.com/earendil-works/absurd https://earendil-works.github.io/absurd/ 使ったことはないけど、他の選択肢と比較する価値はあると思う。

もし大量のスループットが必要ないなら、absurd(と私たちのRust派生のdurable)は、クライアントサイドを非常にシンプルに保つ素晴らしい選択肢だと思うよ。軽量だから、コーディングエージェントが全体を頭に入れておいて、必要に応じて状態を調べるためにクエリを実行することも簡単だしね。

TB単位のデータにスケールするまで、必要なのはPostgresだけだよ。うちはPostgreSQLを耐久性のあるワークフローエンジン、ベクトル検索、時系列データ、BM25検索、OLTP/OLAPエンジン、キューとして使ってる。https://lobu.ai にとっては基本的に唯一の依存関係だね。主な利点は、すべてのデータを一箇所に集中させることで、複数のシステム間でデータをコピーする心配がなくなること。何かがボトルネックになると、最終的には特定の目的のツールに移行してスケールアウトできるよ。正直なところ、LISTEN/NOTIFYはPGの中で一番脆弱な部分だと思うけど、スケールアウトするまでのスタートとしては大丈夫だよ。

リスン/通知はPG 18と19でかなり良くなりそうだね。

ログのことが言及されてないね。ほとんどのアプリには同意するけど、OLAPのやつ(メトリクス、ログ、トレース)は、容量や読み取りのアクティビティのためにVictoriaMetricsみたいな別のストアに置いておくべきだと思う。

同じ考えだよ。特にOLAPや時系列のために、何か特別な拡張機能使ってる?(パーティションテーブル+関連の拡張はうまくいくけど、他に何か使ってるか気になるな。)

でも、その壁にぶつかると、違うパターンやシステムを使うように人を納得させるのが難しいんだよね。「数千行になるだけ」と言ってたテーブルが、突然数TBになって、パフォーマンスやDB管理のタスクが本当に難しくなったときに、みんな困惑してるのを見てきたよ。ほぼ毎日、「それをリレーショナルデータとして扱う必要があるの?」って聞かなきゃいけないスケールで働いてるんだ。「リレーショナルに見えないけど」って。

最初からパブリック以外の「データベース」を使った方がいいよ。間にジョインはなしで。後でPostgresのインスタンスを分けるのが楽になるから。今のマージされたバージョンとは違った使い方になるし、最適化もしやすいし、時間も稼げる。時間があれば大丈夫だよ。

逆に、TB単位のデータをスケールし始めるスタートアップは、TBのデータが必要になることはないんだよね。まだ人が欲しがる製品がないのに、スケールするのにエネルギーを使いすぎちゃう。

ちなみに、あなたのアプリでGoogleサインインしようとすると、「このアプリはあなたのGoogleアカウントの機密情報へのアクセスを要求しています。開発者(rekakc*@gmail.com)がこのアプリをGoogleで確認するまで、使用しない方がいいです。」ってメッセージが出るよ。

僕の夢は、データストレージ、状態遷移、妥当な状態制約、そして妥当な状態間を遷移するロジックを分けるんじゃなくて、これらをアプリの状態の核に統一できることなんだ。正直、Postgresはすでに多くの機能を持ってるけど、アプリや製品レベルで、アプリが遷移できる証明可能な正しい状態のセットを提供する明確なストーリーが見えないんだ。クライアントに情報を提供する形で自動的に公開できるような(このユーザーはこの投稿を「いいね」できるけど、編集はできない)。僕にはカラーペトリネットの形に見えるけど、データベースのように明確な成功の境界を持つシンプルなアプリ状態のパラダイムはまだ見えてないな。

これやってみたけど、千行のストアドプロシージャは本当に悪夢だね。

コンセプトは完全に理解できるし、同意するよ。これはワークフローシステムでの耐久性を構築する素晴らしい方法だね。とはいえ、ゲーマー脳としてはこれを「スケールでのセーブスカミング」と呼びたい気分。つまり、多くの人がこのアプローチが機能することを知ってるけど、抽象的なCSのことに結びつけてないかもしれない。ロバスト性を構築するために使える別の戦略は、冪等性のある操作でワークフローを構築することだね。これはワークフローの状態がバックアップするには大きすぎる場合に役立つよ。代わりに、最初からジョブを実行して、進捗が出るまでずっとノーオプのままなんだ。

Estuaryでは、Postgresでスケールアウト可能な耐久アクター/FSMを構築するための社内Rustクレートを持ってるよ。これがコントロールプレーンのすべての非同期アクティビティを支えてるんだ。細かくスケジュールされたアクションや、データフローのトポロジーを通じた複雑な変更伝播、信頼性のあるアラートやメール配信など、今は毎秒数百から数千の状態遷移を処理してる。これは素晴らしいパターンで、たったの3つのソースファイルでできてる。フィボナッチ数列を計算する例があるよ(すごく非効率的で、たくさんのサブタスクとメッセージパッシングがあるけど)[2] [1] https://github.com/estuary/flow/tree/master/crates/automatio... [2] https://github.com/estuary/flow/blob/master/crates/automatio...

PostgresとDBOSが大好きだよ。最近、https://github.com/earendil-works/absurdにも手を出し始めたんだけど、これもPostgresでDBOSよりもさらにシンプルなんだ。比較もすごく面白いよ: https://earendil-works.github.io/absurd/comparison/ でも運用上の理由から、耐久性のあるワークフローにはSQLiteを使い始めた。DBOSやabsurd PGからSQLiteにデータベースの概念を移植するのは、最近は驚くほど簡単だよ。小さなポーリングループでnotify/listenの代わりにやるのも、小規模なワークロードには問題ない感じ。

この「無料」のworkflow_error.sqlについて掘り下げてみたいな。1024バイトのワークフロージョブ記述子を前提に、記事にある10,000ジョブ/秒の安定状態を考えるよ。可能性その1: テーブルにインデックスが1つあって、それがcreated_atのタイムスタンプだとする。このクエリは、10,000ジョブ/秒 * 60秒 * 60分 * 24時間 * 31日 * 1024バイト/ジョブ = 25,543GBをスキャンしなきゃいけない。KVストアならちょうどそのくらいスキャンすることになるね。可能性その2: プライマリキーが(state, timestamp)に洗練されていると仮定する。失敗率が1%だとすると、今度は「たった」255GBをスキャンして返すことになる。キー・バリュー・ストアもそのくらいスキャンするだろうね。(これが多分正しい物理設計だと思う)。可能性その3: プライマリキーが(timestamp)で、stateにセカンダリインデックスがあるとする。インデックス結合を行うことになると思うけど、一方の結合側が25,543GBで、もう一方が運用している月数分の255GBの未ソートバケットになるんじゃないかな。KVストアではそれを表現できないだろうね。さて、他にどんなアドホッククエリを1ヶ月の振り返りで効率的にサポートするつもりなの?それと、PGに25TBをスキャンさせながら10K TPSで10MB/秒を挿入するように指示したらどうなるの?バキュームの設定はどうなってるの?