Pub/Sub を使用したイベント ドリブン アーキテクチャ

このドキュメントでは、オンプレミスのメッセージ キュー ドリブンのアーキテクチャと、Pub/Sub に実装されたクラウドベースのイベント ドリブン アーキテクチャの違いについて説明します。オンプレミスのパターンをクラウドベースのテクノロジーにそのまま適用してしまうと、クラウドの持つ独自の価値が失われる可能性があります。

このドキュメントは、オンプレミス アーキテクチャをクラウドベースに移行するシステム アーキテクトを対象とし、メッセージング システムについて十分な知識があることを前提としています。

次の図に、メッセージ キュー モデルと Pub/Sub モデルの概要を示します。

メッセージ キュー モデルのアーキテクチャと Pub/Sub を使用したイベント ドリブン モデルの比較。

上の図では、メッセージ キュー モデルと Pub/Sub イベント ストリーム モデルを比較しています。メッセージ キュー モデルの場合、パブリッシャーはメッセージをサブスクライバーに push し、サブスクライバーは特定のキューをリッスンします。Pub/Sub を使用するイベント ストリーム モデルの場合、パブリッシャーは複数のサブスクライバーがリッスン可能なトピックにメッセージを push します。以降のセクションでは、これらのモデルの違いについて説明します。

イベント ストリームとキューベースのメッセージングの比較

オンプレミス システムを使用している方は、エンタープライズ サービスバス(ESB)メッセージ キューについてすでに精通していると思いますが、イベント ストリームは新しいパターンであり、最新のリアルタイム システムにはないメリットがあります。

このドキュメントでは、トランスポート メカニズムとイベント ドリブン アーキテクチャのペイロード データの主な違いについて説明します。

メッセージ トランスポート

このモデルでデータを移動するシステムはメッセージ ブローカーと呼ばれ、さまざまなフレームワークが実装されています。まず、パブリッシャーから受信者にメッセージを転送する基盤のメカニズムについてみてみましょう。オンプレミスのメッセージ フレームワークの場合、送信側のシステムはメッセージ キューをトランスポートとして使用し、明示的に分離されたリモート メッセージをダウンストリームの処理システムに送信します。

次の図に、メッセージ キュー モデルを示します。

パブリッシャーからのメッセージは、サブスクライバーごとに一意のキューに push されます。

上の図では、メッセージ キューを使用して、アップストリームのパブリッシャー プロセスからダウンストリームのサブスクライバー プロセスにメッセージが送信されます。

システム A(パブリッシャー)は、システム B(サブスクライバー)用に指定されたメッセージ ブローカー上のキューにメッセージを送信します。キューのサブスクライバーが複数のクライアントで構成されていても、これらのクライアントはすべて、スケーリングと可用性のためにデプロイされているシステム B の重複インスタンスになります。追加のダウンストリーム プロセス(システム C など)がプロデューサー(システム A)から同じメッセージを使用する必要がある場合は、新しいキューが必要になります。新しいキューにメッセージを公開するには、プロデューサーの更新が必要になります。このモデルはメッセージの受け渡しと呼ばれます。

これらのキューのメッセージ トランスポート層がメッセージの順序を保証しているとは限りません。多くの場合、メッセージ キューは、タスクキューと同様に厳格な先入れ先出し(FIFO)アクセスモデルを使用し、データの順序を保証するモデルを提供する必要があります。初期の段階では、このパターンは簡単に実装できますが、最終的にはスケーリングと運用の課題を抱えることになります。順序付きのメッセージを実装するには、システムでデータを一元的に管理するプロセスが必要です。このプロセスは単一障害点となるため、スケーリング機能が制限され、サービスの可用性も低減します。

このアーキテクチャのメッセージング ブローカーは、どのサブスクライバーがどのメッセージを受信したかを追跡し、サブスクライバーの負荷をモニタリングするためのロジックを別に実装する傾向があります。サブスクライバーは通常リアクティブで、システム全体の情報はなく、メッセージの受信時に機能を実行するだけです。このようなアーキテクチャは、スマートパイプ(メッセージ キュー システム)、ダム エンドポイント(サブスクライバー)と呼ばれています。

Pub/Sub トランスポート

イベント ストリーミング システムでは、メッセージ ドリブンのシステムと同様に、送信元システムのメッセージが分離された宛先システムに転送されます。ただし、イベント ターゲット システムではプロセスの各キューにメッセージを送信しません。その代わりに、共有トピックにメッセージを公開し、1 つ以上のレシーバーがそのトピックをサブスクライブして、関連するメッセージをリッスンします。

次の図は、アップストリーム パブリッシャーから 1 つのトピックにさまざまなメッセージが送信され、関連するダウンストリーム サブスクライバーにルーティングされる方法を示しています。

パブリッシャーからのメッセージは、すべてのサブスクライバーに対して 1 つのトピックに push されます。

このような公開とサブスクライブのパターンにちなんで pub/sub という名前が付いています。このパターンは、Pub/Sub という Google Cloud プロダクトの基礎でもあります。このドキュメント全体を通して、pubsub はパターンを、Pub/Sub はプロダクトを指します。

pubsub モデルの場合、メッセージング システムがサブスクライバーを認識する必要はありません。どのメッセージを受信したかも追跡されません。また、使用プロセスの負荷も管理しません。その代わり、サブスクライバーが受信メッセージを追跡し、負荷レベルとスケーリングをユーザー側で管理する必要があります。

大きな利点の 1 つは、pubsub モデルでデータが新たに使用される際に、新しいキューや重複データに公開するように元のシステムを更新する必要がないことです。代わりに、新しいコンシューマーを新しいサブスクリプションに関連付けます。既存のシステムに影響を及ぼすことはありません。

ほとんどの場合、イベント ストリーミング システムでの呼び出しは非同期です。イベントを送信した後、レスポンスは待機しません。非同期イベントを使用することで、プロデューサーとユーザーの両方のスケーリング オプションを増やすことができます。ただし、FIFO のメッセージ順序が想定される場合、この非同期パターンにより課題が発生する可能性があります。

メッセージ キュー データ

通常、メッセージ キュー システムと pubsub ベースのシステム間で渡されるデータは、いずれのコンテキストでもメッセージと呼ばれます。ただし、データが表すモデルは異なります。メッセージ キュー システムの場合、メッセージにはダウンストリーム データの状態を変更するコマンドが反映されています。オンプレミスのメッセージ キュー システムのデータを見ると、コンシューマーが行う必要のある処理が明示されている場合があります。たとえば、在庫メッセージに次のものが含まれていることがあります。

<m:SetInventoryLevel>
    <inventoryValue>3001</inventoryValue>
</m: SetInventoryLevel>

この例では、プロデューサーは在庫数を 3001 に設定する必要があることをコンシューマーに伝えています。このアプローチは、プロデューサーが各コンシューマーのビジネス ロジックを理解し、ユースケースごとに異なるメッセージ構造を作成する必要があるため、簡単に実装できないこともあります。このメッセージ キュー システムは、多くの企業が実装している大規模なモノリスでよく使用されています。しかし、さらなる高速化、スケーリング、イノベーションが必要になると、こうした一元化されたシステムの変更にはリスクが伴い、また時間もかかるため、ボトルネックとなる可能性があります。

このパターンには運用上の課題もあります。誤ったデータや重複レコードなどの問題が発生した場合、このようなメッセージング モデルの修正は容易ではありません。たとえば、前の例で使用したメッセージをロールバックする必要がある場合、前の状態を参照できないため、訂正値を設定する方法がわかりません。メールの送信前に在庫値が 3,000 だったのか、4000 だったのか、判断できる材料がありません。

Pubsub データ

メッセージ データをイベントで送信することもできます。イベント ドリブン システムは、発生する結果ではなく、発生したイベントに着目するシステムです。データを送信してコンシューマーが行うアクションを示すのではなく、実際に発生したイベントの詳細を提示します。イベント ドリブン システムはさまざまなプラットフォームで実装できますが、よく見られるのは pubsub ベースのシステムです。

たとえば、在庫イベントは次のようになります。

{ "inventory":-1 }

以前のイベントデータは、在庫が 1 つ減少したイベントの発生を示しています。メッセージは、将来の状態の変化ではなく、過去に発生したイベントにフォーカスしています。パブリッシャーはメッセージを非同期で送信できるため、イベント ドリブン システムはメッセージ キュー モデルよりも容易にスケーリングできます。pubsub モデルでは、ビジネス ロジックを切り離すことが可能です。プロデューサーはロジックで実行されたアクションのみを把握し、ダウンストリーム プロセスについて理解している必要はありません。そのデータのサブスクライバーが、受け取ったデータの処理方法を選択できます。これらのメッセージは命令型コマンドではないため、メッセージの順序はそれほど重要ではありません。

このパターンでは、変更のロールバックが容易になります。この例では、在庫値に負の値を指定して逆方向に移動できるため、追加情報は不要です。メッセージの遅延や順不同が発生しても問題ありません。

モデルの比較

このシナリオでは、在庫に同じ商品が 4 つある場合について考えてみます。1 人のユーザーが 1 個の商品を返品し、次のユーザーが同じ商品を 3 個購入したとします。また、このシナリオでは、返品に関するメッセージが遅れたと仮定します。

次の表では、メッセージ キュー モデルで在庫数を正しい順序で受け取った場合と順不同で受け取った場合の在庫数を比較します。

メッセージ キュー(正しい順序) メッセージ キュー(順不同)
初期の在庫: 4 初期の在庫: 4
メッセージ 1: setInventory(5) メッセージ 2: setInventory(2)
メッセージ 2: setInventory(2) メッセージ 1: setInventory(5)
在庫数: 2 在庫数: 5

メッセージ キューモデルでは、メッセージに事前に計算された値が含まれるため、メッセージが受信される順序は重要です。この例では、メッセージが正しい順序で到着すると、在庫数が 2 になります。メッセージが順不同で到着すると、在庫数が 5 になり、これは正確な値ではありません。

次の表に、pubsub ベースのシステムで、在庫数を正しい順序で受信した場合と順不同で受信した場合の違いを示します。

Pubsub(正しい順序) Pubsub(順不同)
初期の在庫: 4 初期の在庫: 4
メッセージ 2: "inventory":-3 メッセージ 1: "inventory":+1
メッセージ 1: "inventory":+1 メッセージ 2: "inventory":-3
在庫数: 2 在庫数: 2

pubsub ベースのシステムの場合、メッセージの順序は重要ではありません。これは、イベントを生成するサービスによって通知されるためです。メッセージの順序に関係なく、在庫数は正確な値になります。

メッセージ キュー モデルでは、キューがコマンドを実行して状態の変化をサブスクライバーに通知しますが、pubsub モデルでは、パブリッシャーで発生したイベントに対応してサブスクライバーが処理を行います。この違いを次の図に示します。

コマンドへの対応とイベントへの対応の違いを示す会計の例。

イベント ドリブン アーキテクチャの実装

イベント ドリブン アーキテクチャを実装する際には、さまざまなコンセプトを検討する必要があります。以降のセクションでは、その一部を紹介します。

配信の保証

システムについて議論する際に、メッセージ配信の保証に対する信頼性を検討する必要があります。ベンダーやシステムによって提供される信頼性が異なる可能性があるため、その違いを理解することは重要です。

まず「メッセージが確実に送信されるかどうか」を考えます。これは at-least-once 配信といいます。この場合、メッセージは少なくとも 1 回は配信されることが保証されますが、複数回送信されることもあります。

もう一つの保証は at-most-once 配信です。at-most-once 配信の場合、メッセージは最大で 1 回しか配信されませんが、実際に配信される保証はありません。

また、exactly-once 配信という保証もあります。このモデルでは、メッセージの配信が確実に 1 回だけ行われます。

順序と重複

オンプレミス アーキテクチャでは、多くの場合、メッセージは FIFO モデルに従います。このモデルを実現するため、一元化された処理システムでメッセージの順序を管理し、順序の正確性を維持します。順序付けされたメッセージの場合、1 つのメッセージの送信に失敗すると、すべてのメッセージが順番に再送信されるため、課題が発生します。また、一元化されたシステムの場合、可用性とスケーラビリティが問題になる可能性があります。順序を一元管理するシステムをスケーリングする場合、通常は既存のマシンにリソースを追加することでスケーリングを実現します。順序を 1 つのシステムで管理しているので、信頼性の問題はそのマシンだけでなく、システム全体に影響します。

スケーラビリティと可用性に優れたメッセージ サービスの場合、少なくとも 1 回は確実にメッセージを配信するために複数の処理システムが使用されていますが、多くのシステムでは、メッセージの順序の管理は保証されていません。

イベント ドリブン アーキテクチャは、メッセージの順序に依存しないため、メッセージの重複を許容できます。順序が必要な場合は、サブシステムで集計とウィンドウ処理を実装できます。ただし、この方法では、そのコンポーネントでのスケーラビリティと可用性が損なわれます。

フィルタリングとファンアウトの手法

イベント ストリームにすべてのサブスクライバーで必要なデータが含まれているとは限りません。このため、特定のサブスクライバーが受信するデータの制限が必要になることがあります。この要件を満たすには、イベント フィルタとイベント ファンアウトという 2 つの方法があります。

次の図は、サブスクライバーに対するメッセージのフィルタリングを行うイベント ドリブン システムを示しています。

サブスクライバーに対するメッセージをフィルタリングするイベント フィルタが設定されたイベント ドリブン モデル。

上の図では、フィルタリング メカニズムを使用して、サブスクライバーに到達するイベントを制限しています。このモデルでは、1 つのトピックにメッセージのすべてのバリエーションが含まれています。サブスクライバーが各メッセージを読み取り、必要なメッセージかどうか確認する代わりに、メッセージング システムのフィルタリング ロジックがメッセージを評価し、必要のないサブスクライバーにメッセージを配信しないようにしています。

次の図は、複数のトピックを使用するイベント ファンアウトというイベント フィルタ パターンを示しています。

イベント ドリブン モデルとイベント ファンアウトを使用してトピック上でメッセージを再公開します。

上の図では、プライマリ トピックにメッセージのすべてのバリエーションが含まれていますが、イベント ファンアウト メカニズムにより、サブスクライバーのサブセットに関連するトピックにメッセージを再公開しています。

未処理のメッセージ キュー

優れたシステムでも障害が発生する可能性があります。未処理のメッセージ キューは、このような障害に対処するための方法です。ほとんどのイベント ドリブン アーキテクチャでは、サブスクライバーがメッセージを確認するまで、メッセージ システムはサブスクライバーにメッセージを提供します。

メッセージに問題がある場合(メッセージ本文に無効な文字が含まれている場合など)、サブスクライバーがメッセージを確認できないことがあります。システムがこのシナリオに対応できず、プロセスを終了することもあります。

通常は、未確認またはエラーのあるメッセージは再試行されます。所定の時間が経過すると、未確認の無効なメッセージはタイムアウトし、トピックから削除されます。運用の観点からは、メッセージを削除するのではなく、確認できるようにしたほうが良いでしょう。そのため、未処理のメッセージ キューを使用します。トピックからメッセージを削除する代わりに、メッセージを別のトピックに移動することで、エラーになった理由を確認し、再度処理を行うことができます。

ストリームの履歴とリプレイ

イベント ストリームは連続したデータのフローです。この履歴データへのアクセスは有用です。たとえば、システムが特定の状態になった理由を確認したい場合があります。また、セキュリティ関連の問題の中にはデータの監査が必要なものもあります。イベント ドリブン システムを長期的に運用する場合は、イベントの履歴ログを取得できるようにしておく必要があります。

過去のイベントデータはリプレイ システムで使用できます。このリプレイはテスト目的で使用されます。ステージやテストなどの他の環境で本番環境のイベントデータをリプレイすることで、実際のデータセットを使用して新しい機能の検証を行うことができます。履歴データをリプレイして、エラー状態から回復することもできます。システムが停止した場合やデータが失われた場合、確認されている正常なポイントからイベント履歴をリプレイし、失われた状態を再現することもできます。

これらのイベントをログベースのキューやロギング ストリームにキャプチャすることは、サブスクライバーがさまざまな頻度でイベントにアクセスする必要がある場合にも有用です。ロギング ストリームは、オフライン機能を備えたシステムで確認できます。ストリーム履歴を使用すると、最後に読んだポインタからストリームを読み取ることで、最新のエントリを処理できます。

データビュー: リアルタイムとほぼリアルタイム

すべてのデータがシステムを通過するので、データを使用できるようにしておくことが重要になります。これらのイベント ストリームにアクセスして使用する方法はたくさんありますが、特定時点でのデータの全体的な状態を把握するために使用されるのが一般的です。多くの場合、数や現在のレベルなど、計算が必要になる項目が問題となりますが、これらの指標は他のシステムや手動での作業でも使用されています。こうした問題を解決するには、いくつかの実装方法があります。

  • リアルタイム システムの場合、処理を継続的に実行し、現在の状態を追跡できます。ただし、システムによっては計算がメモリ内でのみ行われ、ダウンタイムが 0 と計算されることがあります。
  • 各リクエストの履歴テーブルから値を計算することも可能ですが、これが問題になる可能性があります。たとえば、データの増加中にすべてのリクエストの値を計算しようとすると、計算不能な状態になる可能性があります。
  • 一定の間隔で計算のスナップショットを作成できますが、スナップショットだけではリアルタイム データを表すことはできません。

実装に適したパターンは、ほぼリアルタイムとリアルタイムの両方の機能を提供する Lambda アーキテクチャです。たとえば、e コマースサイトの商品ページでは、在庫データがほぼリアルタイムで表示されます。顧客が注文を行うと、在庫データのステータスがリアルタイムで更新されます。このパターンを実装する場合、サービスは、一定の間隔で計算された値を含むスナップショット テーブルを使用してほぼリアルタイムでリクエストに応答します。リアルタイムのリクエストでは、スナップショット テーブルと前回のスナップショット以降の履歴テーブルの両方を使用して、最新の状態を取得します。イベント ストリームのマテリアライズド ビューにより、実際のビジネス プロセスを推進する実用的なデータを提供できます。

次のステップ