Pub/Subのユースケースとアーキテクチャパターン

Pub/Sub は、Publisher と Subscriber を疎結合にするメッセージングの仕組みです。

ただし、概念だけを見ても使いどころは分かりにくいです。Pub/Sub は、API、Worker、DWH、ストレージ、ログ基盤などと組み合わせて初めて設計上の意味が見えてきます。

この記事では、Pub/Sub の代表的なユースケースとアーキテクチャパターンを整理します。

Pub/Sub の基本構造は『Pub/Subとは何か』で扱っています。

この記事で出てくる用語

この記事では、Pub/Sub 以外にもいくつか周辺用語が出てきます。

用語内容
Workerキューや Subscription からメッセージを受け取り、実際の処理を行うプログラムです。
Stream Processor継続的に流れてくるデータを変換、集計、フィルタリングする処理基盤です。
DWHData Warehouse の略です。分析用に整理されたデータを保存し、SQLなどで集計するための基盤です。
Data Lake加工前のログ、イベント、ファイルなどをまとめて保存するデータ置き場です。
SIEMSecurity Information and Event Management の略です。セキュリティログや監査ログを集約し、検知や調査に使う基盤です。
Forwarderログやイベントを別のシステムへ転送する中継プログラムです。
WebSocketクライアントとサーバーの間で双方向通信を行うための通信方式です。

APIと重い処理を切り離す

最も分かりやすいユースケースは、API と重い後続処理の切り離しです。

flowchart LR
  client["Client"]
  api["API"]
  topic["Topic"]
  sub["Subscription"]
  worker["Worker"]

  client -->|"request"| api
  api -->|"publish"| topic
  topic --> sub
  sub --> worker

たとえば注文APIで、注文作成後にメール送信、請求書生成、外部システム連携を行うケースがあります。

これらをAPIリクエスト内で同期実行すると、後続処理の遅延や障害がAPIレスポンスに影響します。

Pub/Sub を挟むと、APIはイベントを publish して早く応答し、後続処理は Worker が非同期で実行できます。

採用しやすいケースは以下です。

  • APIのレスポンスを速く返したい
  • 後続処理が重い
  • 後続処理が一時的に失敗しても再試行したい
  • APIとWorkerを別々にスケールさせたい

1つのイベントを複数処理に配る

1つのイベントを複数用途で使いたい場合は、1つの Topic に複数の Subscription を作ります。

flowchart LR
  topic["Topic: order-created"]
  emailSub["Subscription: send-email-sub"]
  analyticsSub["Subscription: save-analytics-sub"]
  inventorySub["Subscription: update-inventory-sub"]
  emailWorker["Email Worker"]
  analyticsWorker["Analytics Worker"]
  inventoryWorker["Inventory Worker"]

  topic --> emailSub --> emailWorker
  topic --> analyticsSub --> analyticsWorker
  topic --> inventorySub --> inventoryWorker

これは ファンアウト です。

注文作成イベントを、メール送信、分析保存、在庫更新にそれぞれ使えます。

この構成の利点は、処理ごとに独立した Subscription を持てることです。

  • メール送信が失敗しても、分析保存は独立して進む
  • 分析保存だけ遅れても、在庫更新には影響しにくい
  • 新しい処理を追加するときに、Publisher 側を変更せずに Subscription を追加できる

イベントの利用者が増えそうな領域では、ファンアウト構成が効きます。

同じ処理を複数Workerで分担する

同じ処理の量が多い場合は、1つの Subscription を複数の Subscriber で読みます。

flowchart LR
  sub["Subscription: send-email-sub"]
  worker1["Email Worker 1"]
  worker2["Email Worker 2"]
  worker3["Email Worker 3"]

  sub -->|"msg-1"| worker1
  sub -->|"msg-2"| worker2
  sub -->|"msg-3"| worker3

この場合、同じメッセージが全 Worker に届くわけではありません。各 Worker がメッセージを分担して処理します。

たとえば、セール開始の告知メールを数万人の会員へ一斉配信する場面です。1台の Worker では配信が終わるまでに時間がかかり、Subscription にメッセージが積み上がります。同じ Subscription を読む Worker を増やせば、配信を並列に進められます。

採用しやすいケースは以下です。

  • メッセージ量が多い
  • 1件あたりの処理が重い
  • Workerを水平スケールさせたい
  • 同じ処理を複数台で分担したい

ファンアウトと並列Workerは混同しやすいですが、目的が違います。

目的構成
同じイベントを複数用途に配る1 Topic に複数 Subscription
同じ処理を複数台で分担する1 Subscription を複数 Subscriber で読む

ストリーミング分析の入口にする

Pub/Sub は、ストリーミング分析の入口としても使われます。

flowchart LR
  producer["Producer"]
  pubsub["Pub/Sub"]
  processor["Stream Processor"]
  dwh["DWH"]
  lake["Data Lake"]

  producer --> pubsub
  pubsub --> processor
  processor --> dwh
  processor --> lake

たとえば、アプリのイベントログ、センサーの計測データ、クリックストリームをリアルタイムに収集し、後段で変換して分析基盤へ保存する構成です。

GCP であれば、以下のような構成が代表例です。

flowchart LR
  pubsub["Pub/Sub"]
  dataflow["Dataflow"]
  bigquery["BigQuery"]

  pubsub --> dataflow --> bigquery

Pub/Sub はメッセージを受ける入口を担当し、Dataflow のようなストリーム処理基盤が変換、集計、ウィンドウ処理、重複排除を担当します。

たとえば、ECサイトの閲覧・カート追加・購入イベントを Pub/Sub で受け、ストリーム処理基盤で集計して DWH に書き込む場面です。キャンペーン中の売れ行きを当日中にダッシュボードで追える状態を作れます。

採用しやすいケースは以下です。

  • 継続的にイベントが発生する
  • ほぼリアルタイムに分析基盤へ流したい
  • 下流で変換や集計が必要
  • Producer と分析処理を切り離したい

イベント駆動処理の起点にする

Pub/Sub は「何かが起きたら処理を動かす」イベント駆動の起点にもなります。

flowchart LR
  storage["Storage"]
  db["DB"]
  scheduler["Scheduler"]
  app["App"]
  pubsub["Pub/Sub"]
  function["Function"]
  worker["Worker"]

  storage -->|"event"| pubsub
  db -->|"event"| pubsub
  scheduler -->|"event"| pubsub
  app -->|"event"| pubsub
  pubsub --> function
  pubsub --> worker

たとえば、ファイルがアップロードされたらサムネイルを生成する、ログが出力されたら外部監視基盤に転送する、定期時刻になったらバッチ処理を起動する、といった構成です。

この用途では、Pub/Sub はイベントを受け渡す中継点として働きます。

採用しやすいケースは以下です。

  • 何かの発生をきっかけに処理したい
  • イベント発生元と処理側を切り離したい
  • 後からイベントの利用者を増やしたい
  • 処理失敗時に再試行したい

ログや監査イベントを外部連携する

ログや監査イベントを Pub/Sub に流し、外部の監視基盤や分析基盤へ転送するパターンもあります。

flowchart LR
  app["App"]
  logging["Logging"]
  pubsub["Pub/Sub"]
  forwarder["Forwarder"]
  processor["Stream Processor"]
  siem["SIEM"]
  dwh["DWH"]
  storage["Object Storage"]

  app --> pubsub
  logging --> pubsub
  pubsub --> forwarder
  pubsub --> processor
  forwarder --> siem
  processor --> dwh
  processor --> storage

アプリケーションやクラウドサービスのログを、リアルタイムに外部サービスへ送る場合に使います。

たとえば、監査要件に対応するために全サービスの操作ログをSIEMへ集約し、同じログを分析用のDWHにも保存する場面です。ログの利用先が増えても、Subscription を追加するだけで転送先を増やせます。

採用しやすいケースは以下です。

  • ログを複数の宛先に配りたい
  • 外部SIEMや監視基盤に転送したい
  • ログ転送先が一時的に落ちてもバッファしたい
  • ログの加工やフィルタリングを挟みたい

WebSocketサーバー間のイベント共有に使う

Pub/Sub は、複数のWebSocketサーバー間でイベントを共有する用途にも使われます。

flowchart LR
  clientA["Client A"]
  server1["WebSocket Server 1"]
  pubsub["Pub/Sub"]
  server2["WebSocket Server 2"]
  clientB["Client B"]

  clientA <-->|"WebSocket"| server1
  server1 -->|"publish"| pubsub
  pubsub --> server2
  server2 <-->|"WebSocket"| clientB

WebSocket は、基本的には接続しているサーバーとクライアントの間の通信です。

複数台のWebSocketサーバーがある場合、あるサーバーに接続しているクライアントのイベントを、別のサーバーに接続しているクライアントへ届ける必要があります。

このとき、Pub/Sub をサーバー間のイベント伝播に使えます。

たとえばチャットサービスで、ユーザーAとユーザーBが別々のサーバーに接続している場面です。Aの発言を Pub/Sub 経由で全サーバーに配れば、Bが接続しているサーバーがそれを受け取り、WebSocket でBに届けられます。

採用しやすいケースは以下です。

  • WebSocketサーバーを複数台にスケールさせたい
  • チャットや通知を複数サーバー間で共有したい
  • サーバー間の直接通信を避けたい
  • リアルタイムイベントを複数ノードに伝播したい

Pub/Subを使わないほうがよいケース

Pub/Sub は便利ですが、すべての非同期処理に向くわけではありません。

使わないほうがよいケースもあります。

やりたいこと向いている選択肢
特定の1つのHTTPエンドポイントに、細かいリトライ制御つきでタスクを実行したいタスクキュー
メッセージを長期保存して検索・集計したいDB、DWH、Object Storage
複雑な変換やウィンドウ集計を行いたいストリーム処理基盤(Pub/Sub は処理基盤ではない)
厳密なワークフロー状態管理が必要ワークフローエンジン

Pub/Sub は、処理そのものではなく、イベントを運ぶための層です。

GCP で Pub/Sub の代わりにどのサービスを選ぶか、どのサービスと組み合わせるかは『GCP Pub/Subと周辺サービスの役割分担』で整理しています。

まとめ

Pub/Sub の代表的なユースケースは以下です。

  • APIと重い後続処理を切り離す
  • 1つのイベントを複数処理に配る
  • 同じ処理を複数Workerで分担する
  • ストリーミング分析の入口にする
  • イベント駆動処理の起点にする
  • ログや監査イベントを外部連携する
  • WebSocketサーバー間のイベント共有に使う

Pub/Sub は、単体で完結するサービスというより、上流と下流を疎結合にするための中間層です。

タグ: PubSub, メッセージング