Open6
データベースとメッセージシステムのトランザクション制御を考える
はじめに
- 2つの異なるシステム(今回はデータベース & メッセージシステム)のトランザクションを同時に制御しようとして問題になるケースが時々起きてる
- この問題の解決策にはどういったアプローチがあるのか?を調べたのでまとめる
問題になるケース
なにが問題か
- データベースのコミットよりも先にメッセージ処理が行われる(ことがある)
- comsumerでは、backendでinsertしたa-1データを使用したいが、commitが遅れると取得に失敗する
- (仮に)メッセージシステムに障害が発生した場合、すべての処理がエラーになる
- どちらかというとメッセージシステムの生死に関わらず、DBが稼働していれば正常に処理させたい
どういうアプローチがあるか?
アプローチ1: 遅延キュー
- 自分が真っ先に思い浮かんだのはこれ
- AWS SQS だと遅延キューで遅延時間(0-15分)を指定可能
- GCP CloudTasksだとScheduleTimeというのがあって、タスクを配信する時刻を指定できる
- データベースのコミットが完了するまで、メッセージが処理されないようにメッセージの配信時間をコントロールする
- ただし、これだとシステム全体のレイテンシが増加する可能性があるため、要件に応じて適切な遅延時間を設定する必要あり
- メッセージシステムに障害が発生している場合は、やはりエラーになってしまう
アプローチ2: データベーストリガー
- データベーストリガを使用して、データベースへの書き込みが成功した後にキューへのメッセージ登録を行う
- OracleのJavaストアドプロシージャや、PosgresのPL/Pythonなど使うイメージ
- データベースにトリガ機能がない場合はそもそも利用できない
- データベースにビジネスロジックを持たせることになるので避けるべき
アプローチ3: Outboxパターン
- Outboxテーブル(と呼ばれるメッセージを書き込む専用のテーブル)を用意する
- データ更新と同一トランザクション内でOutboxテーブルにも書き込む
- メッセージリレーはOutboxテーブルに書き込まれたデータをキューにメッセージ登録する
- データベースのトランザクションとメッセージ登録処理が切り離されているため、仮にメッセージシステムに障害が発生しても正常に処理することができる
- メッセージリレーが仲介する分、リアルタイム性は落ちる
アプローチ4: 2フェーズコミット
- すべてのリソースがコミットを確定するまで、トランザクションがブロックされちゃう
- これによって、他のトランザクションが待機状態になり、システム全体のスループットが低下する
- メッセージシステムに障害が発生している場合は、やはりエラーになってしまう
参考