Open6

データベースとメッセージシステムのトランザクション制御を考える

yaguchiiyaguchii

はじめに

  • 2つの異なるシステム(今回はデータベース & メッセージシステム)のトランザクションを同時に制御しようとして問題になるケースが時々起きてる
  • この問題の解決策にはどういったアプローチがあるのか?を調べたのでまとめる

問題になるケース

なにが問題か

  • データベースのコミットよりも先にメッセージ処理が行われる(ことがある)
    • comsumerでは、backendでinsertしたa-1データを使用したいが、commitが遅れると取得に失敗する
  • (仮に)メッセージシステムに障害が発生した場合、すべての処理がエラーになる
    • どちらかというとメッセージシステムの生死に関わらず、DBが稼働していれば正常に処理させたい
yaguchiiyaguchii

どういうアプローチがあるか?

アプローチ1: 遅延キュー

  • 自分が真っ先に思い浮かんだのはこれ
  • AWS SQS だと遅延キューで遅延時間(0-15分)を指定可能
    • GCP CloudTasksだとScheduleTimeというのがあって、タスクを配信する時刻を指定できる
  • データベースのコミットが完了するまで、メッセージが処理されないようにメッセージの配信時間をコントロールする
  • ただし、これだとシステム全体のレイテンシが増加する可能性があるため、要件に応じて適切な遅延時間を設定する必要あり
  • メッセージシステムに障害が発生している場合は、やはりエラーになってしまう
yaguchiiyaguchii

アプローチ2: データベーストリガー

  • データベーストリガを使用して、データベースへの書き込みが成功した後にキューへのメッセージ登録を行う
    • OracleのJavaストアドプロシージャや、PosgresのPL/Pythonなど使うイメージ
  • データベースにトリガ機能がない場合はそもそも利用できない
  • データベースにビジネスロジックを持たせることになるので避けるべき
yaguchiiyaguchii

アプローチ3: Outboxパターン

  • Outboxテーブル(と呼ばれるメッセージを書き込む専用のテーブル)を用意する
  • データ更新と同一トランザクション内でOutboxテーブルにも書き込む
  • メッセージリレーはOutboxテーブルに書き込まれたデータをキューにメッセージ登録する
  • データベースのトランザクションとメッセージ登録処理が切り離されているため、仮にメッセージシステムに障害が発生しても正常に処理することができる
  • メッセージリレーが仲介する分、リアルタイム性は落ちる
yaguchiiyaguchii

アプローチ4: 2フェーズコミット

  • すべてのリソースがコミットを確定するまで、トランザクションがブロックされちゃう
  • これによって、他のトランザクションが待機状態になり、システム全体のスループットが低下する
  • メッセージシステムに障害が発生している場合は、やはりエラーになってしまう