アウトボックスパターンとはなにか

こんにちは。サイオステクノロジーの和田です。アドベントカレンダー17日目です。今回はアウトボックスパターンという設計パターンを学んだので、紹介したいと思います。まず初めにアウトボックスパターンの説明をしてから、その設計を使った具体的なサービス例、そして冪等性の考慮について書いていきます。それではいきましょう。

アウトボックスパターンとは

アウトボックスパターンとはマイクロサービスアーキテクチャにおいて、データベースの更新と「イベント発行(メッセージ送信要求)」を矛盾なく行うための設計パターンです。マイクロサービスでは、あるサービスが自身のデータベースを更新し、その変更をイベントとして他のサービスに通知するという処理がよくあります。

アウトボックスパターンでは、送信したいイベント(= メッセージの内容)を Outbox テーブルに書き込み、ユーザーデータの更新と同一トランザクションでコミットします。実際のメッセージブローカーへの送信は後段のリレーが行うため、「DB に書けたのにイベントが失われる / DB に書けていないのにイベントだけ流れる」といった不整合を避けられます。

例:ユーザー登録サービス

ここでは具体例として、Web サービスの新規ユーザー登録を例に考えます。 ユーザーが登録ボタンを押した際に、システムでは以下の 2 つを行う必要があります。

  1. 「ユーザーデータベース」にユーザー情報(ID、メールアドレス、パスワード等)を書き込む。
  2. 「メール配信サービス」に対して、「ユーザー登録が完了した」というメッセージをメッセージブローカーに送信する(登録完了メールや認証メールを送るため)。

この 2 つの処理を、アプリケーションが順番に実行しようとすると、以下のような問題が発生する可能性があります。

ケース 1:DB 書き込み成功、メッセージ送信失敗

ユーザーデータベースへの保存は成功したが、その直後にメッセージブローカーがダウンしてしまった。

  • 結果 ユーザーのアカウントは作成されたが、登録完了メール(または認証メール)が届かない。ユーザーはログインの手順を完了できず、サービスを利用開始できない。

ケース 2:DB 書き込み失敗、メッセージ送信成功

何らかの理由(メールアドレスの重複など)でデータベースへの書き込みが失敗(ロールバック)したが、そのあとにメッセージだけ送信されてしまった。

  • 結果 データベースにユーザーは存在しない(登録できていない)のに、「登録ありがとうございます」というメールだけがユーザーに届いてしまう。ユーザーがメール内のリンクをクリックしても、アカウントが存在しないためエラーとなる。

このように、どちらのケースの場合でも結果に不整合が生じてしまいます。


解決策:アウトボックスパターン

このような問題に対する解決策として使えるのがアウトボックスパターンです。アウトボックスパターンでは「外部への送信そのもの」ではなく「送信したい内容を Outbox に永続化するところまで」を、データベースへの書き込みと同じトランザクションで処理することで問題を解決します。具体的には以下の流れで実現します。

1. ユーザー登録サービスの処理

ユーザー登録サービスではユーザーテーブルへの書き込みと同時に、送信したいメッセージ(メール送信依頼)をOutbox テーブルと呼ばれる特別なテーブルに書き込みます。

ここで重要なのは、ユーザーテーブルへの書き込みと Outbox テーブルへの書き込みを単一のデータベーストランザクションで実行することです。

トランザクションの結果

  • トランザクションが成功した場合 ユーザーデータと送信すべきメッセージの両方がデータベースにコミットされます。
  • トランザクションが失敗した場合 何らかのエラー(DB エラーやバリデーションエラーなど)があれば、両方の書き込みがロールバックされます。

この結果、データベースへのユーザー登録が成功したならば、送信すべきメッセージも必ず DB 内に保存されている状態が保証され、データの整合性を担保することができます。

一方で、メッセージブローカーへの反映はリレーのタイミングに依存するため、通知は(多くの場合)最終的整合になります。つまり、登録完了直後に必ずメール送信が開始されるとは限らず、多少の遅延が起こり得ます。

2. メッセージのリレー

実際の送信ではメッセージリレーと呼ばれるアプリケーションとは別の独立したプロセスがメッセージの送信を行います。

このリレーが Outbox テーブルを定期的に監視(ポーリング)し、未送信のメッセージを見つけた場合はメッセージブローカーへ送信します。送信が完了したら Outbox テーブルの該当のレコードを削除(または送信済みステータスに変更)します。

監視の方法はポーリング以外にも、CDC(Change Data Capture)などで変更を検知する構成もあります。

3. メールの配信

最後にメールを送信するプロセスがメッセージブローカーからメッセージを受け取り、実際にメールの送信を行います。

冪等性の考慮

アウトボックスパターンを実装する際には、冪等性についても考慮する必要があります。冪等性とは、同じ操作を何度実行しても、結果が 1 回実行した場合と同じになる性質のことです。

メッセージの重複送信の可能性

メッセージリレーが Outbox テーブルからメッセージを読み取り、メッセージブローカーに送信した後、送信済みのマークを付ける前にクラッシュした場合、同じメッセージが再度送信される可能性があります。

例えば、ユーザー登録のケースでは以下のような問題が発生する可能性があります

  1. メッセージリレーがメッセージブローカーにメール送信依頼を送信
  2. メッセージブローカーへの送信は成功
  3. Outbox テーブルの更新前にメッセージリレーがクラッシュ
  4. リレーが再起動し、同じメッセージを再送信
  5. ユーザーに登録完了メールが 2 通届いてしまう

今回の例のようなメール送信の場合は、2 通届いても致命的ではないケースもあります。しかし、他のストレージにデータを保存する処理などを行う場合は冪等性を確実に実装する必要があります。

典型的な対策としては、各メッセージに一意な eventId(メッセージ ID)を付与し、受信側で「処理済み eventId」を保存して重複を検知・無視する方法があります。

まとめ

今回はアウトボックスパターンという設計パターンについて紹介しました。アウトボックスパターンを使うことで、マイクロサービス間でのデータの整合性を担保しやすくなり、「データは保存されたのに通知が届かない」といった事象を避けられます。その結果、より堅牢なシステムを構築できます。

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です