Pub/Subを設計するときに考えること

Pub/Sub は、Publisher と Subscriber を疎結合にする便利な仕組みです。

ただし、Pub/Sub を挟めば自動的に安全な非同期処理になるわけではありません。Ack、再配信、重複処理、冪等性、Dead Letter Topic などを理解していないと、二重実行やメッセージ滞留で問題が起きます。

この記事では、Pub/Sub を設計するときに考えるべき論点を整理します。

Pub/Sub の基本構造は『Pub/Subとは何か』で、メッセージの流れは『Pub/Subのデータの流れをシーケンス図で理解する』で扱っています。

Ackのタイミングを決める

Ack は、Subscriber が「このメッセージの処理は完了した」と Pub/Sub に伝える操作です。

Subscription
  ↓ message
Subscriber
  ↓ 処理成功
Subscription
  ↑ ack

Pub/Sub は、Subscriber にメッセージを渡しただけでは処理完了とみなしません。

Subscriber が ack して初めて、その Subscription 上で処理済みになります。

設計時に決めるべきなのは、Subscriber がどの処理まで終えたら ack するかです。

  • 処理前に ack する場合
    • Subscriber が処理中に落ちると、メッセージは失われた扱いになります
  • 処理後に ack する場合
    • Subscriber が処理中に落ちても、再配信によって別の Subscriber が処理できます

基本的には、DB更新、外部API呼び出し、メール送信などの副作用を含む処理が成功したあとに ack します。

再配信を前提に冪等性を設計する

再配信と冪等性は、別々の論点ではなく1セットで考えます。再配信という仕組みがある以上、同じメッセージが複数回届く可能性が生まれます(前提)。その前提に耐えるための設計が冪等性です(対応)。

前提: 再配信によって同じメッセージが複数回届きうる

Subscriber が ack しないまま一定時間が過ぎると、Pub/Sub はそのメッセージを再配信します。

Subscriber が message を受け取る

処理中に落ちる

ack されない

Pub/Sub が再配信する

再配信は、障害時にメッセージを失わないために必要です。

一方で、同じメッセージが2回以上届く可能性も生まれます。これは、Pub/Sub の基本的な考え方が at-least-once delivery だからです。

At-least-once delivery は「少なくとも1回は届ける」という意味です。0回にはしませんが、2回以上届く可能性はあります。

対応: 冪等性で重複に備える

冪等性 は、同じ処理を複数回実行しても結果が破綻しない性質です。

再配信によって同じメッセージが重複して届く可能性がある以上、Pub/Sub を使う処理では冪等性の設計が重要です。

GCP Pub/Sub には exactly-once delivery の機能もありますが、常に何も考えなくてよいという意味ではありません。外部API、DB更新、メール送信などの副作用を含む処理では、アプリケーション側の冪等性設計が重要です。

たとえば注文完了メールの送信処理を考えます。event_id = evt-001 のメッセージが2回届いたときに、メールが2通送られると困る場合があります。

この場合は、処理済みイベントを記録します。

Email Worker

event_id = evt-001 が処理済みか確認
  ├─ 処理済みならスキップして ack
  └─ 未処理ならメール送信して処理済みに記録して ack

冪等性を作る代表的な方法は以下です。

  • 一意な event_id をメッセージに含める
  • order_id など業務上の一意キーで処理済み判定する
  • DBに処理済みイベントテーブルを作る
  • 外部APIに idempotency key を渡す
  • 更新処理を上書き型にする

Pub/Sub の再配信は異常ではありません。重複しても壊れない処理にすることが重要です。

Dead Letter Topicを用意する

何度再試行しても成功しないメッセージもあります。

たとえば、メッセージ形式が壊れている、参照先データが存在しない、処理側のバグで必ず失敗する、といったケースです。

このようなメッセージを通常の Subscription に残し続けると、同じ失敗を繰り返します。

そこで、一定回数失敗したメッセージを Dead Letter Topic に逃がします。

Subscription
  ↓ 何度も失敗
Dead Letter Topic

調査・再投入・破棄

Dead Letter Topic を使うと、通常処理の流れから失敗メッセージを隔離できます。

設計時には以下を決めます。

  • 何回失敗したら隔離するか
  • 隔離されたメッセージを誰が見るか
  • 修正後に再投入するか
  • 業務上破棄してよいメッセージか
  • アラートを出す条件は何か

順序保証が必要かを判断する

Pub/Sub では、通常はメッセージ全体の順序を前提にしないほうが安全です。

多くのシステムでは、全体順序ではなく、特定の単位だけ順序が必要です。

たとえば、注文ごとの状態遷移を考えます。

order-123: created
order-123: paid
order-123: shipped

この場合、全注文を1列に並べる必要はありません。order-123 に関するイベントだけ順序が守られれば十分です。

順序保証を考えるときは、以下を確認します。

  • 本当に順序が必要か
  • 順序が必要な単位は何か
  • 全体順序ではなく、キー単位の順序で足りるか
  • 順序保証によってスループットや並列性が落ちてもよいか

順序が不要な処理では、順序保証に寄せすぎないほうがスケールしやすくなります。

PushとPullを選ぶ

Pub/Sub の受信方式には、Push と Pull があります。

Pull は、Subscriber が Subscription にメッセージを取りに行く方式です。

Subscriber → Subscription: pull
Subscription → Subscriber: message

Push は、Pub/Sub が Subscriber のHTTPエンドポイントにメッセージを送る方式です。

Subscription → Subscriber: HTTP POST

選び方の目安は以下です。

方式向いているケース
PushCloud Run や Functions のようなHTTP受信型のサービスで処理したい
PullGKE、VM、常駐Worker、Dataflowのように処理側で取得量を制御したい

Push は構成がシンプルです。一方で、処理側が受信レートを細かく制御したい場合は Pull のほうが扱いやすいです。

メッセージに何を入れるかを決める

Pub/Sub のメッセージに、すべての業務データを詰め込む必要はありません。

よくある設計は、イベントの識別情報と参照キーを入れる形です。

{
  "event_id": "evt-001",
  "event_type": "order.created",
  "order_id": "order-123",
  "occurred_at": "2026-07-10T10:00:00+09:00"
}

この形式では、Subscriber が order_id を使って必要な詳細情報を取得します。

メッセージ設計で考えることは以下です。

  • イベントの種類を表す event_type を入れる
  • 重複排除に使える event_id を入れる
  • 業務データを参照するためのIDを入れる
  • 大きなファイル本体は入れず、ストレージ上の参照を入れる
  • スキーマ変更に備えてバージョンを入れる

Pub/Sub はデータ保存先ではないため、巨大な本体データを運ぶ場所として使いすぎないほうが安全です。

監視する指標を決める

Pub/Sub は非同期処理なので、問題が起きてもユーザーのAPIレスポンスにはすぐ現れないことがあります。

そのため、監視が重要です。

見るべき観点は以下です。

  • 未処理メッセージ数が増え続けていないか
  • 古い未処理メッセージが残っていないか
  • Dead Letter Topic にメッセージが流れていないか
  • Subscriber のエラー率が上がっていないか
  • 再配信が急増していないか
  • publish 側でエラーが出ていないか

Pub/Sub を使うと、上流APIは成功しているのに、下流処理だけ詰まることがあります。

非同期化した処理ほど、下流の滞留や失敗を監視する必要があります。

まとめ

Pub/Sub を設計するときは、メッセージを送ることだけでなく、処理完了、失敗、再試行、重複、隔離、監視まで考えます。

特に重要なのは以下です。

  • Ack は副作用を含む処理が成功したあとに返す
  • Ack されなかったメッセージは再配信される
  • at-least-once delivery により重複配信がありうるため、冪等性をアプリケーション側で担保する
  • 失敗し続けるメッセージは Dead Letter Topic に隔離する
  • 順序保証は必要な単位に限定する
  • Push と Pull は実行基盤に合わせて選ぶ
  • 非同期処理の滞留と失敗を監視する

Pub/Sub は、疎結合で壊れにくいシステムを作るための強力な部品です。一方で、再配信や重複処理を無視すると事故りやすい部品でもあります。

参考

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