Transactional Outbox パターン:マイクロサービスにおける信頼性の高いイベント発行
現代のイベント駆動型マイクロサービス(Event-Driven Architecture)では、サービス間を疎結合に保ちつつ状態変更を通知するために、OrderCreated や PaymentProcessed などのイベントが日常的に発行されます。
しかし、イベント発行の完全な信頼性を確保するには大きな課題があります:「データベースの更新」と「イベント発行」の双方を、確実に「両方成功」か「両方失敗」にするにはどうすればよいか?
マイクロサービスがローカルDB(PostgreSQLやMySQL)を更新した直後にメッセージブローカー(KafkaやRabbitMQ)へネットワーク経由で送信しようとすると、ネットワーク障害やタイムアウトによってデータ不整合が発生します。
この問題を根本的に解決するのが Transactional Outbox パターン です。
根本的な課題:二重書き込み問題 (Dual-Write Problem)
二重書き込みを単純に行うと、以下のリスクが生じます:
- ローカルデータベースへビジネスデータを保存
- メッセージブローカーへイベントを発行
DBコミットが成功した後にKafkaへの送信がネットワークエラーで失敗した場合、DBにはデータが存在するのに後続サービスへイベントが届きません。結果:データの不一致。
Transactional Outbox パターンとは?
リレーショナルデータベースの ローカルACIDトランザクション を活用する設計パターンです。
イベントを直接ネットワーク経由で送信するのではなく、ビジネスデータの保存と全く同じトランザクション内で、専用の Outbox テーブル にイベントデータを書き込みます。
ACIDの保証により、両方がコミットされるか、両方がロールバックされるかのいずれかになります。
その後、独立したバックグラウンドプロセス(Message Relay)がOutboxテーブルから読み出し、メッセージブローカーへ発行します。
メッセージリレー戦略:Polling Publisher vs CDC (Debezium)
1. Polling Publisher(ポーリング方式)
定期的なスケジューラで未処理レコード(SELECT * FROM outbox_events WHERE processed = FALSE)を取得し、Kafkaに送信後に更新。
- メリット: 追加インフラが不要で実装がシンプル。
- デメリット: ポーリングによる遅延とDB負荷。
2. Change Data Capture(Debezium CDC方式)
DBのトランザクションログ(PostgreSQLのWAL)をDebeziumが直接キャプチャし、リアルタイムでKafkaへ配信。
- メリット: ほぼゼロ遅延、アプリ層のDB負荷ゼロ。
- デメリット: Kafka Connect等の追加インフラが必要。
まとめ
Transactional Outbox パターン は、分散システムにおけるデータの一貫性と耐障害性を保証するための必須パターンです。