Postgresトランザクションを使用して分散システムの課題を解決する

Postgresトランザクションを使用して分散システムの課題を解決する

ワークフロー状態とデータの共存(Co-locating)

アプリケーションのワークフロー状態をPostgresデータベース内に共存させることで、開発者は「二重書き込み(dual-write)」問題、つまりシステムがデータベースを更新したもののメッセージキューへの通知に失敗する、あるいはその逆が発生するリスクを排除できます。データベースをビジネスデータとワークフローの進行状況の両方における単一の真実のソース(single source of truth)として扱うことで、開発者は状態遷移とその対応する副作用がアトミックにコミットされることを保証できます。

このアプローチは、分散協調問題をローカルなトランザクション問題へと変換します。2つの異なるシステム(例:データベースとメッセージブローカー)にわたってトランザクションを調整しようとする代わりに、システムはデータ変更と意図されたアクションの両方を、単一のACIDトランザクション内で同じデータベースに書き込みます。

Transactional Outboxパターン

Transactional Outboxパターンは、データベースと外部メッセージキューの間でアトミック性を実現するための主要なメカニズムです。これは、データベースの役割を「プライマリ状態」と「アウトボックス(outbox)」テーブルの2つの部分に分割することで機能します。

  1. Atomic Write: アプリケーションは、単一のPostgresトランザクション内で、ビジネス状態を更新し、アウトボックステーブルにメッセージを挿入します。
  2. Asynchronous Dispatch: 別のプロセスがアウトボックステーブルをポーリングするか、データベースのトリガー/UDFを使用して、メッセージを外部キューにプッシュします。
  3. Confirmation: 外部システムが受信を確認した後、メッセージは処理済みとしてマークされるか、アウトボックスから削除されます。

コミュニティメンバーが指摘しているように、このパターンは、メッセージを送信する意図がデータ自体と同じくらい永続的に保存されることを保証することで、データベースとキューの間にトランザクションが存在するかのように「擬似的に」機能します。

金融システムにおける二重書き込みバグの排除

送金システムのような極めて重要な環境では、チェックポイントを書き込みと共存させることが、「中途半端なコミット(half-committed)」状態を防ぐために不可欠です。ワークフローの途中でシステムがクラッシュした場合、リカバリプロセスはPostgresに保存されたワークフロー状態を参照して、プロセスが正確にどこで停止したかを判断し、トランザクションの重複や欠落なしにその時点から再開できます。

"This is the trick that kills the dual-write bug in money-movement systems: co-locate the checkpoint with the write so a mid-workflow crash can't leave you half-committed."

トレードオフと実装上の考慮事項

Postgresをワークフローエンジンとして使用することは強力な保証を提供しますが、特定のアーキテクチャ上のトレードオフを伴います。

結合度と複雑さ

ワークフローの進行単位をデータベースのコミット単位と一致させることは、データベーススキーマとアプリケーションワークフローの間の密な結合(tight coupling)を生み出します。これによりアウトボックスパターンが簡素化されますが、将来的にワークフローエンジンをデータベースから分離することは困難になります。しかし、多くのサービスにおいて、データベースはスタックの中で最も安定した部分であるため、このトレードオフは許容可能です。

べき等性と副作用

データベーストランザクションは、物理的な世界ですでに発生した副作用(例:メールの送信)をロールバックすることはできません。したがって、外部サービスとのインタラクションにはべき等性(idempotency)が必須となります。

例えば、メール通知システムでは、「少なくとも一度(at-least-once)」の配信を「正確に一度(exactly-once)」の配信よりも優先する戦略が一般的です。システムは、保留リストからメッセージを削除するトランザクションを閉じる前に、メールが正常に送信されたことを確認します。これにより、稀な失敗によってメッセージが失われるのではなく、、メッセージが重複して送信される可能性があることを受け入れます。

集約化のリスク

状態と協調の両方に単一のデータベースを使用することは、単一障害点(single point of failure)を生み出します。データベースの停止はシステム全体を整合的に停止させるため、運用オーバーヘッドは簡らぎますが、コーディネーション層をデータ層とは独立してスケールさせる能力をことはできません。

分散システムの代替案との比較

一部の論者は、このアプローチプローチが中央のデータベースに依存しているため、厳密な意味での「分散システム」ではないと主張しています。しかし、これはTemporalやレプリカ状態マシン(replicated state machines)のような複雑な分散協調ツールに代わる、実用的な代替案として機能します。ステップのボリュームが管理可能な範囲であり、ワークフローの進行状況の永続的な記録が必要なシステムにおいては、Postgresに直接永続的なワークフローを実装することは、RedisやValkeyのような追加のインフラストラクチャを導入するよりも効率的な場合が多いです。

Sources