Pub/Sub は、Publisher と Subscriber を疎結合にするメッセージングの仕組みです。
ただし、概念だけを見ても使いどころは分かりにくいです。Pub/Sub は、API、Worker、DWH、ストレージ、ログ基盤などと組み合わせて初めて設計上の意味が見えてきます。
この記事では、Pub/Sub の代表的なユースケースとアーキテクチャパターンを整理します。
Pub/Sub の基本構造は『Pub/Subとは何か』で扱っています。
この記事で出てくる用語
この記事では、Pub/Sub 以外にもいくつか周辺用語が出てきます。
| 用語 | 内容 |
|---|---|
| Worker | キューや Subscription からメッセージを受け取り、実際の処理を行うプログラムです。 |
| Stream Processor | 継続的に流れてくるデータを変換、集計、フィルタリングする処理基盤です。 |
| DWH | Data Warehouse の略です。分析用に整理されたデータを保存し、SQLなどで集計するための基盤です。 |
| Data Lake | 加工前のログ、イベント、ファイルなどをまとめて保存するデータ置き場です。 |
| SIEM | Security 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 は、単体で完結するサービスというより、上流と下流を疎結合にするための中間層です。