pg_durable - PostgreSQL向けの耐久性SQL関数
(github.com/microsoft)- PostgreSQL内部で リトライ、スケジューリング、並列fan-out、条件分岐 を小さなSQL DSLだけで処理する durable function 拡張
- コンテナや外部サービスなしで Postgresとバックグラウンドワーカー だけで動作
- すべての段階がPostgreSQLに状態をチェックポイントとして記録し、クラッシュ・再起動・接続断でも中断地点から再開
- キュー管理、状態追跡、クラッシュ復旧、段階調整、リトライを自前で実装する必要なく、SQLだけ書けばオーケストレーションエンジンが処理
- 直接実装すると 300行以上のボイラープレート が必要な作業を単一のDSL呼び出しで置き換え、PostgreSQL 17 でオープンソースとしてすぐ利用可能
概要と中核価値
- Postgresに組み込まれた クラッシュ耐性のある(crash-proof) durable function で、リトライ・スケジューリング・並列fan-out・条件分岐を小さなSQL DSLでオーケストレーション
- 追加インフラなしで Postgres + バックグラウンドワーカー だけで動作し、別途コンテナや外部サービスは不要
- キュー管理・状態追跡・クラッシュ復旧・段階調整・リトライをすべて担う オーケストレーションエンジン として機能し、利用者はSQLだけを書けばよい
pg_durableなしで実装する場合
- 3つの集計を並列実行したあとダッシュボードを更新しつつ、リトライとクラッシュ復旧まで組み込むには 300行以上のボイラープレート が必要
- 自前で構築すべき項目: キュー設定と構成、ワーカー管理とポーリング、メッセージ処理と状態追跡、エラー処理とリトライ、手動の段階調整
- 例示コードには
job_queue,job_results,job_state,workflow_steps,step_variables,scheduled_jobsなど多数の状態テーブルと、ポーリングワーカー、ワークフロー進行、クラッシュ復旧、並列実行コーディネーター、変数受け渡し、スケジューリング、クリーンアップ関数が含まれる - スケジューリングの
next_run計算には 外部cronパーサーライブラリ が追加で必要
pg_durableで実装する場合
- 同じ並列集計 + ダッシュボード更新を単一の
df.start()呼び出しで表現し、&演算子でfan-out したあと~>でjoin を処理- 例: 3つのクエリが並列に分岐したあと
refresh dashboard段階に合流して結果を生成 - ライブ実行例では3段階の並列実行後にjoinし、dashboard readyまで 1.9秒で耐久的に完了
- 例: 3つのクエリが並列に分岐したあと
- キュー管理、状態追跡、クラッシュ復旧、段階調整、リトライをすべて pg_durableが処理
主な特徴
-
Durable by default
- すべての段階がPostgreSQLに状態を チェックポイント として記録し、クラッシュ・再起動・接続断でもワークフローが存続
- 中断した地点から 正確にそのまま再開
-
Automatic retries
- flakyな作業のための リトライロジックを内蔵 し、段階が失敗するとその段階だけを再試行して、残りのワークフローは継続
- 手動のエラー処理コードは不要
-
Full observability in SQL
- すべてのワークフロー状態が Postgresテーブル に保存され、実行履歴の照会・段階出力の確認・失敗のデバッグを標準SQLで実施可能
- 外部ダッシュボードは不要
-
Parallel execution
&演算子またはdf.join()で独立作業をfan-outし、集計・API呼び出し・ETL段階を 自動調整とともに同時実行
作成できるパターン
-
ETL Pipelines
- cleanup → transform → load を順次保証つきで接続し、各段階が前段階を待って、失敗時にはパイプラインをきれいに停止 (
~> sequence,|=> variables)
- cleanup → transform → load を順次保証つきで接続し、各段階が前段階を待って、失敗時にはパイプラインをきれいに停止 (
-
Parallel Aggregation
- ユーザー数集計 + 売上合計 + 在庫確認を同時に実行し、複数クエリへfan-outしたあと全体完了を待機 (
&,df.join())
- ユーザー数集計 + 売上合計 + 在庫確認を同時に実行し、複数クエリへfan-outしたあと全体完了を待機 (
-
Order Processing
- 注文IDをキャプチャして検証・処理・完了段階へ渡し、段階間で変数が自動的に流れる (
|=> capture,$var substitution,df.sleep())
- 注文IDをキャプチャして検証・処理・完了段階へ渡し、段階間で変数が自動的に流れる (
-
Scheduled Jobs
- cronスケジュールでAPIポーリング・レコードアーカイブ・データ同期を行い、ループが永続実行され再起動後も存続 (
@> loop,df.wait_for_schedule())
- cronスケジュールでAPIポーリング・レコードアーカイブ・データ同期を行い、ループが永続実行され再起動後も存続 (
-
Conditional Branching
- 待機中の作業・行数・フラグを確認して処理またはスキップへ分岐し、分岐ロジックがアプリケーションではなくSQLに置かれる (
df.if(),?> conditional)
- 待機中の作業・行数・フラグを確認して処理またはスキップへ分岐し、分岐ロジックがアプリケーションではなくSQLに置かれる (
-
Multi-step Validation
- データ取得 → スキーマ検証 → ビジネスルール確認 → 承認/拒否 の流れで、各段階がチェックポイント化されるため失敗しても進捗を失わない
-
Database Maintenance
- autovacuumを妨げる要因・テーブルbloat・wraparoundリスクを検出してレビュー用に公開し、承認待ち後に 再起動後も耐久的に修正 (
?> conditional,df.wait_for_signal(),@> loop)
- autovacuumを妨げる要因・テーブルbloat・wraparoundリスクを検出してレビュー用に公開し、承認待ち後に 再起動後も耐久的に修正 (
-
Azure Functions & HTTP
df.http()でAzure Functionsや許可されたHTTPSエンドポイントをSQLから直接呼び出し、ドキュメント分割・行拡張・レコード分類をインライン処理
-
Human-in-the-Loop Approval
- 日常的な作業は自動承認し、高リスク作業(高額請求書、破壊的作業)は 人の承認シグナルが来るまで一時停止 (
df.wait_for_signal(),df.if())
- 日常的な作業は自動承認し、高リスク作業(高額請求書、破壊的作業)は 人の承認シグナルが来るまで一時停止 (
AIベースの作成支援
- ワークフローを 平文の英語で説明するとCopilotが正しい durable-function SQL を生成 し、構文を学ばなくてもやりたい内容だけ記述すればよい
- リポジトリには再利用可能なエージェントスキル
pg-durable-sqlが含まれ、GitHub Copilotや他のエージェントに演算子・変数置換・ループ・並列joinなど正しいSQL生成方法を学習させる
オープンソース提供
- ウェイトリストや囲い込みなし で完全オープンソースとして提供され、リポジトリをクローン・ビルドして自分のPostgreSQLですぐ実行可能
- ノートPC・サーバー・クラウドのどこでも durable orchestration を適用可能
Azure HorizonDB マネージドオプション
- Azure HorizonDB はMicrosoftの新しいPostgreSQLクラウドサービスで、pg_durableを内蔵 しており、作成した durable function をそのまま維持しつつ、エンタープライズ向けの拡張性・セキュリティ・AIを追加
- 最大3×高速な性能、ストレージ自動拡張は最大128 TB、コンピュートスケールアウトは最大3,072 vCore
- Microsoft Defender によるリアルタイム脅威検知、Microsoft Entra ID によるID管理
- Filtered DiskANNベクトル検索、セマンティックランキング、インデータベースAIモデルキュレーション
- Microsoft Fabric のほぼリアルタイムミラーリング、VS Code統合、GitHub Copilot連携
-
組み込みAIパイプライン
- HorizonDBはpg_durableの耐久的実行の上にマネージドなエンドツーエンド AIパイプライン をレイヤー化し、各段階がチェックポイント化・リトライ・クラッシュ安全を備える
- 段階フロー: Ingest(ドキュメント・データ読み込み) → Chunk(コンテンツ分割) → Embed(ベクトル化) → Index(DiskANN保存) → Serve(検索・ランキング)
1件のコメント
Hacker Newsのコメント
2026年はPostgresキューの年になりそう: DBOS[0]、pgQue[1] のような流れがあり、コミュニティがこうした選択肢を作ってくれるのは素晴らしい。
ただ、元アプリケーションエンジニアとしては、キューのロジックはコードとGitの中にある方が好み。適切なツールがあれば考えが変わるかもしれない。
[0]: https://www.dbos.dev/
[1]: https://github.com/NikolayS/pgque
バージョン管理、デバッグ、テスト、リリースをどうするのか気になる。データ局所性とスタックの単純化のためにすべてを一か所に置くのは良さそうだが、「正しく」やる方法についての有用な知識を多く失う感覚がある。
Supabaseで少しでも複雑なことをしようとするとPostgres関数を作らなければならなかったのも、それで本当に嫌だった。ただ、以前のスタートアップではPostgres上に簡単な作業キューを自作していて、pgQueのようなものがあればもっと洗練された形になっていただろう。
マルチマスター拡張も、すぐ使えて完全に安全という話ではないので、データベースのスケール要件を早めてしまうような書き込み中心の複雑な処理を入れるのには慎重になる。
ローカル設定時にトリガーがローカルデータベースに入るよう、Djangoマイグレーションに押し込んだこともあった。
これは ストアドプロシージャ の匂いがする。単体テストもバージョン管理も難しく、ビジネスロジックがデータベースの中に隠れて「隠れた頭脳」になってしまう。
ノイジーなワークロードを分離するのも難しく、可観測性もなく、スケールの圧力がすべてPostgresに集中する。特にAPI呼び出しのような入出力が弱い。ローカルデータベース内だけで回る処理には良いが、用途は狭そうに見える。
もちろん、適切なデータベースアップグレード手順が必要だ。チームメンバーがrootで任意のSQLマイグレーションを実行するようなやり方だと苦労することになる。
単体テストも他のSQLテストと同じように可能で、データベースを立ち上げる必要があるだけ。ストアドプロシージャをテストできないなら、SQLそのものをテストする方法がないという意味で、それこそが本当の問題だ。
ストアドプロシージャの代替は、データベースにビジネスロジックをまったく置かないことではなく、コードベースの至る所にSQLが散らばってテストしにくく、バージョン管理やカプセル化が弱く、不要に遅い状態になってしまうことが多い。
可観測性については一部その通りで、SQLの問題を掘り下げる作業は多くのプログラミング言語より手間がかかる。しかし、ストアドプロシージャが入出力やスケールの問題を生むなら、それは使い方が間違っているのであって、正しく使えばむしろ入出力を大幅に減らし、スケーラビリティを改善することが多い。
正しく理解しているなら、Pi LLM harnessの開発者たちが作った Absurd は、純粋なデータベースアクセスをできるだけ減らす方向に見える。まだこのテーマを見始めたばかり。
https://github.com/earendil-works/absurd
もちろん、詳細をすべて知っているわけではないので純粋に気になっている。
「使うべきでないとき」として「ワークフローの大半がPostgresの外にあり、複数の異種システムにまたがっている場合」とあるが、だとすると、このプロジェクトが Temporal のようなものとどう比較できるのか分からない。
この推奨が示唆する制限を自分が誤解しているのではないかと思う。
技術的には興味深い成果かもしれないが、こういうSQLを読むのはかなり奇妙だ。
SELECT df.start(@> (($$SELECT ... FROM demo.invoices WHERE status = 'pending'$$ |=> 'inv')~> df.if_rows('inv',$$UPDATE ... SET status = 'processing'$$~> (df.http(...) |=> 'resp')~> df.if($$SELECT $r.ok$$,-- classify, branch, wait for signal ...),df.sleep(5))),'invoice-approval-pipeline');会社がAzureに縛られていて、Azure PostgreSQLがモダンな機能に追いつくのをずっと待っている状態です。
たとえばこれが使えません: https://www.paradedb.com/blog/hybrid-search-in-postgresql-th...
超高次元ベクトルにも対応していません。pg_durableをオープンソース化するのは良いことですが、AWSで当然のように使える基本機能から先に導入してほしいと思います
念のため言うと、私はpg_textsearchのメンテナで、今はAzureにいます。ベクトル対応についての話は正確には理解できていないのですが、Azureで提供されているpgvector + diskannを超える何かを求めているのか気になります
ハイブリッド検索(BM25 + ベクトル)について言えば、ParadeDBのpg_searchもAWSネイティブ機能ではなく、EC2で自前ホスティングする必要があります。Azure PostgreSQLでは、同じBM25ランキングモデルを提供するpg_textsearchをネイティブにし、その主なコントリビューターが現在Azure Postgresチームにいます
ドキュメント: https://learn.microsoft.com/en-us/azure/horizondb/ai/full-te...
高次元ベクトルについては、むしろ先行している分野です。HNSWを使うpgvectorには2,000次元の制限がありますが、Azureはベクトル保存と検索のためのpgvectorをサポートしており、高次元・大規模ワークロード向けにはMicrosoftのグラフベースのベクトルインデックスであるpg_diskannを提供しています。最大16,000次元をサポートし、グラフ走査中にWHERE条件を評価する高度なインデックス内フィルタリングも提供するため、選択的な条件でも再現率を失いません
pgvector: https://learn.microsoft.com/en-us/azure/horizondb/ai/vector-...
DiskANNの高次元対応: https://learn.microsoft.com/en-us/azure/horizondb/ai/vector-...
これらの機能は現在Azure PostgreSQL、特にAzure HorizonDB Previewで利用可能です。特定のワークロードがあれば、より具体的に見ていくこともできます
これはApache AirflowのようなDAGスケジューラがずっと前から解いてきた古い問題に対する、見当違いの解法のように感じます
制御フローをなぜコードではなくデータベースに保存したいのか、不思議です。プロジェクトを貶すつもりはなく、まだよく理解できていません
このプロジェクトは、よりデータベース特化のユースケースに見えます。利点は、ワークフローログとコードベースを突き合わせて1行ずつ追わなくても、ジョブの正確な状態をデータベース自体で追跡できる点だと思います。負荷やレイテンシもより小さく、運用上起動しておくべきコンポーネントが1つ減る効果もありそうです
[1] https://learn.microsoft.com/en-us/azure/durable-task/common/...
一方でこの方式は、現時点ではまだそう動いていないようですが、往復レイテンシのコストなしにほぼリアルタイムの性能フィードバックを受けて自律的に調整できる可能性があります
ドキュメントや例を読んでも、いくつか明確でない点がある。
df.wait_for_schedule()がどのように動作するのか気になる。アプリケーションから呼び出した場合に 冪等 なのか、同じパラメータで 2 回実行するとティックが 2 回発生するのか、クエリコンソールで一度だけ手動で呼び出すものなのか、マイグレーションスクリプトの一部として実行するものなのかがわからない。
例[0] の
timed_outがタイムアウト時に返される固定定数なのかも気になる。エラーや例外処理をどうするのかも、すぐには見当たらない。[0] https://github.com/microsoft/pg_durable/blob/main/examples/i...
df.start()を呼び出すと 耐久関数 が作成され、同時に実行も開始される。この呼び出しはその実行を表すインスタンス ID を返し、以後その実行を参照するために使える。この耐久関数の中で
df.wait_for_signal()を呼び出すが、この呼び出しはその関数インスタンス内で正確に一度だけ実行されるため、重複は起こりえない。df.start()呼び出し自体がタイムアウトして再実行された場合には重複しうるが、その場合は別の関数インスタンスが作られる。SQL の実行中に未処理のエラーが発生した場合、関数インスタンスは失敗し、状態には発生した正確なエラーがそのまま記録される。
データベース外にあるオーケストレーションツールの代わりに、なぜこれを使うべきなのか説明してもらえるだろうか。README や例を読んでもまだ理解できない。
同じデータストアに属する他の構成要素とバックアップを同期する必要がないため、ETL パイプラインや状態機械型のジョブに向いている。ETL の大半が SQL なら、実際の処理が同じサーバー上で実行されることも利点になる。
すべての状態が一つのデータベースにあれば、一貫したバックアップを得られる可能性も高くなる。
https://transport.data.gouv.fr では Postgres をその用途で使っており、かなり多くの処理を行う Elixir アプリで役立っている。pg_durable 自体はまだよく知らないが、似た解決策を使ったり実装したことがあるので共感できる。
データベースはすでに最もスケールさせにくいインフラの一つではないのか。なぜそこに 長時間実行ジョブ まで載せたいのかがわからない。
結局のところ、この種のワークロードは、外部コンポーネントがトリガーするかどうかにかかわらず、データベースに対して実行される処理である。データや AI パイプラインでは、追加コンポーネントによる往復や障害点を避けるため、データベースから HTTP クエリを送るやり方もより一般的になってきた。ただし、計算をデータ側へ持っていくか、データを計算側へ持っていくかは、議論の多い大きな設計判断である。
一般的なプログラミング言語や仮想マシンが、すでに 決定性、計測可能で制御可能な段階的実行、ランタイム状態の一時停止、シリアライズ・デシリアライズと再開をサポートしていたなら不要だった、もう一つの https://en.wikipedia.org/wiki/Inner-platform_effect のように感じられる。