messaging-streaming について
キュー、パブリッシュ/サブスクライブ、ストリーム、耐久性のあるワークフローで、同期リクエストから処理を切り離す設計スキルです。非同期処理の配信、順序、障害対応を検討できます。
主な機能
- 01キュー・Pub/Sub・ストリームの選択
- 02配信と順序の保証の比較
- 03再試行・バックプレッシャー・DLQの計画
- 04メッセージ形式と確認処理の設計
- 05滞留やコンシューマー障害の評価
活用シーン
- 01遅い処理をリクエスト処理から分離
- 02イベントを複数の独立コンシューマーへ配信
- 03トラフィックの急増を吸収し再生可能な処理を管理
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ proyecto26/ system-design-skills --skill messaging-streamingインストール後、Claude Code で/messaging-streamingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/deep-dive.mdreferences/providers/aws.mdazure.mdgcp.mdgeneric.mdtemporal.md詳細
メッセージングとストリーミング
作業を同期リクエストの経路から切り離し、プロデューサーとコンシューマーを分離することで、処理が遅い、負荷が急増する、または失敗しやすい操作によって呼び出し元がブロックされないようにします。ここでの設計を誤ると、問題は見えにくいものです。キューを導入すると配信保証と順序保証がひそかに変わり、負荷がかかったときに急増をうまく吸収できることもあれば、バックログが回復不能になるまでシステムの破綻を隠す要因にもなります。
どんな場合に導入するか
インラインで処理するには遅すぎるステップ(画像のトランスコード、ファンアウト、サードパーティーへの呼び出し)がある場合、書き込み経路の負荷が急増するため、バーストを平準化するバッファーが必要な場合(スパイク係数については back-of-the-envelope を参照)、一方のサービスが失敗したり個別にデプロイされたりしても、もう一方に影響しないようサービスを分離する必要がある場合、または複数のコンシューマーが同じイベントストリームを必要とする場合です。非同期で引き渡すことで、応答性、分離性、伸縮性が得られます(プロデューサーとコンシューマーを個別にスケールできます)。
導入しない場合
呼び出し元が処理を続けるために、結果をすぐに必要とする場合(同期読み取り、確定前の残高確認など)は、キューを使うと遅延と、作業が失われる可能性がある場所が増えるだけです。1つのリクエスト内で強い読み取り後の書き込み整合性が必要な場合も同様です。関数呼び出しで対応できる単純なインプロセス処理には必要ありません。数値や結合の問題によって必要性が明確になる前にブローカーを追加してはいけません(YAGNI)。運用、監視、障害時の動作把握が必要な、新たなステートフルシステムが増えることになります。「いずれ Kafka が必要になる」は、要件ではなく名前を挙げているだけです。
最初に確認すること
- 同期か非同期か? 呼び出し元は結果をインラインで必要としていますか、それとも結果を待たずに処理を進めてよいですか? これによって、そもそもキューが必要かどうかが決まります。
- 必要な配信保証 — メッセージが失われても問題ありませんか(最大1回)、それともすべてのメッセージを処理する必要がありますか(最低1回+冪等なコンシューマー)?
- 順序 — メッセージは全体で、またはキーごと(ユーザーごと、アカウントごと)に順番に処理する必要がありますか? 全体の順序保証はコストが高く、通常はキーごとで十分です。
- スループットと保持期間 — ピーク時のメッセージ数/秒はいくつで、どのくらいの期間再生可能にする必要がありますか?(
back-of-the-envelopeを参照。)一度きりの処理か、再生可能なログかを確認します。 - コンシューマー数と処理パターン — ジョブを処理するワーカープールが1つですか、それとも複数の独立したサブスクライバーが、それぞれすべてのイベントを読み取りますか?
- 障害処理 — 何度も失敗するメッセージはどうなりますか? どこに送られ、誰が確認しますか?
選択肢
同期か非同期か — ツールを選ぶ前に決めてください。 呼び出し元が処理を続けるために結果を必要とし、呼び出しが高速で信頼できる場合は、同期処理を維持してください。直接リクエストするほうが、構築、トレース、把握が簡単です。処理に時間がかかる、負荷が急増する、ファンアウトが多い、または呼び出し元が後から結果に反応できる場合は、非同期処理にしてください。これは、即時整合性と分かりやすいスタックトレースを、応答性と分離性と引き換えにする選択です。以下の選択肢が当てはまるのは、非同期処理を選んだ後だけです。同期的な応答を装うためにキュー経由でリクエスト/レスポンスを構築するのは好ましくありません。直接呼び出すほうが優れた設計です。
キュー(作業/タスクキュー) — 1つの論理コンシューマーグループがメッセージの処理を競い合います。メッセージは1つのワーカーに配信され、確認応答されると削除されます。次の場合に使用します:バックグラウンドジョブやコマンドを、ほぼ一度だけ処理する必要があり、ワーカーを増やして滞留した処理をさばきたい場合。
パブ/サブ(ファンアウト) — 各サブスクライバーがすべてのメッセージのコピーを受け取ります。プロデューサーはサブスクライバーを認識しません。次の場合に使用します:複数の独立したコンシューマーが同じイベントに反応し(通知、インデックス作成、監査など)、疎結合が重要な場合。
ストリーム(永続的で再生可能なログ) — 追記専用でパーティション分割され、保持されるログです。コンシューマーはそれぞれのオフセットを管理し、履歴を再生できます。次の場合に使用します:パーティションごとの順序、異なる位置で処理する複数のコンシューマー、イベントソーシング、または再処理が必要な場合(イベントソーシング/アウトボックスについては → data-storage)。
永続的なワークフロー(オーケストレーションエンジン) — プロセスのクラッシュを乗り越えて実行されるコードです。エンジンが各ステップを永続化し、中断した箇所から再開します。再試行、タイマー、補償処理が組み込まれています。次の場合に使用します:再試行、人による対応待ち、ロールバック(サーガ)を伴う複数ステップのプロセスを、キュー、状態フラグ、cronジョブを手作業で組み合わせた壊れやすい仕組みにせずに実装したい場合。
配信セマンティクスはこれらすべてに関係します。最大1回(送信して終わり。失われる可能性がある)、最低1回(確認応答されるまで再試行するため、重複する可能性がある — 実用上のデフォルト)、厳密に1回(欠落も重複もない)です。エンドツーエンドで真に厳密に1回を実現するのは現実的ではありません。ブローカーの「EOS」(Kafkaなど)はクラスタ内に限られるため、システム間では常に、最低1回の配信+冪等性を備えた/重複排除するコンシューマーとして実装します(→ api-design の冪等性キー)。仕組みについては references/deep-dive.md を参照してください。
トレードオフ
| 選択肢 | 解決すること | 悪化すること | 変更するタイミング |
|---|---|---|---|
| キュー(作業キュー) | 疎結合化とバッファリングを実現し、ワーカーを増やして滞留した処理をさばける | 最低1回の配信では重複が発生し、ワーカー間の順序は保証されない | 再生や多数の独立したコンシューマーが必要になったら → ストリーム/パブ/サブ |
| パブ/サブ(ファンアウト) | 1つのイベントに対して、N個の疎結合な処理が反応できる。コンシューマーを自由に追加できる | 再生できない(一時的)。処理の遅いサブスクライバーは遅延したり、メッセージを取りこぼしたりする可能性がある。ファンアウトによって負荷が増幅される | 履歴/再生またはキーごとの順序が必要になったら → ストリーム |
| ストリーム(ログ) | パーティションごとの順序、再生、複数コンシューマー、イベントソーシングを実現する | 運用負荷が高い。パーティションキーによってホットシャードが発生するリスクがある。コンシューマーがオフセットを管理する必要がある | 単純な単発ジョブにログが不要なら → キュー |
| 永続的なワークフロー | クラッシュに耐える長時間実行サーガを実現し、再試行/補償処理が組み込まれている | 新しいランタイムとプログラミングモデルが必要。レイテンシのオーバーヘッドがある。エンジンのセマンティクスにロックインされる | オーケストレーションを必要としない単一の非同期ステップなら → 通常のキュー |
| 最低1回の配信 | 再試行/クラッシュが発生してもメッセージが失われない | 重複が発生するため、コンシューマーに冪等性が必要(→ api-design の冪等性キー) | 損失を許容でき、重複排除のコストに見合わないなら → 最大1回 |
| 実質的な厳密に1回 | 損失も重複した副作用もない | コスト/複雑さが増す。多くの場合、適用範囲が限定される(複数のシステム間ではなく、1つのブローカー内など) | 冪等性を備えた最低1回で十分なら(通常は十分です) |
負荷がかかったときの挙動
ブローカーの役割は、負荷の急増を吸収することです。ただし、障害を覆い隠すこともあります。
- バックログの増大/上限のないキュー: プロデューサーの処理速度がコンシューマーを上回り、キューがメモリ容量を超えてディスクにあふれ、さらに遅くなります。スループットが問題ないように見える一方で、エンドツーエンドのレイテンシーは気付かないうちに上昇します。対策: バックプレッシャーを適用します。キューに上限を設け、満杯になったらプロデューサーを拒否するか 503 を返し(バックオフを伴う)、際限なくバッファーにため込まないようにします。レートだけでなく、キューの深さとメッセージの経過時間にもアラームを設定します。
- リトライストーム/ポイズンメッセージ: 常に失敗するメッセージが(少なくとも 1 回の配信保証により)際限なく再配信され、コンシューマーの処理能力を消費するとともに、すでに苦境にある下流への負荷をさらに増幅します。対策: 上限付きのリトライにバックオフとジッターを組み合わせ、その後 デッドレターキュー(DLQ) に送ります。これにより、ポイズンメッセージが処理を妨げなくなり、人が調査できるようになります。(リトライ、バックオフ、封じ込めとしての DLQ、バックプレッシャーは
resilience-failureの対象です。) - 重複の増幅: リトライ時に、コンシューマーが重複排除しないと、同じ副作用(課金、メール送信)が 2 回発生します。対策: 冪等性キー/重複排除テーブルを使います。
- ホットパーティション: ストリームでは、偏ったパーティションキー(1 人の著名人、1 つのテナント)により、他のパーティションがアイドル状態のまま、1 つのパーティションに負荷が集中します。これは
data-storageのシャーディングと同じホットキーのパターンです。対策: より適切なキー、サブパーティショニング、またはバッチ処理を使います。 - ファンアウトでの低速なコンシューマー: 遅れているサブスクライバー 1 つが、他の処理を滞らせたり、メッセージを破棄したりします。コンシューマーを分離し、1 つのコンシューマーが他を停止させないようにします。
監視項目: キューの深さ、最古のメッセージの経過時間/コンシューマーのラグ(最も有用な単一のシグナル)、再配信/DLQ のレート、プロデューサーのレートと比較したコンシューマーのスループット、エンドツーエンドのレイテンシー。
適用方法
- 入力条件を明確にします。 まず同期か非同期かを確定します。呼び出し元が結果をその場で必要とするなら、そこで中止します。ブローカーは不要です。次に、配信保証、順序の適用範囲、スループット/保持期間、コンシューマーパターン、障害処理について確認します(まず明確にすることを参照)。
- トレードオフ表から形を選びます。 1 つのワーカープールでジョブを処理する場合は キュー、1 つのイベントに対して複数の独立した反応が必要な場合は パブ/サブ、順序付け、再生、または複数のオフセットが必要な場合は ストリーム、複数ステップのリトライ/補償を伴うサーガには 永続ワークフローを選びます。制約を満たす最も低コストな形を選びます。
- 主要な設定値を決めます。 配信セマンティクス(デフォルトは少なくとも 1 回の配信+冪等なコンシューマー)、ACK のタイミング(処理後か前か)、パーティション/順序付けキー、保持期間、DLQ に送るまでのリトライ上限を決めます。
- 選択をストレステストします。 バックログの増大、リトライストーム/ポイズンメッセージ、重複の増幅、ホットパーティション、ファンアウトでの低速なコンシューマーについて検討します。該当する箇所にバックプレッシャー、DLQ、重複排除を追加します。
- 数値を使って規模を見積もります。 ピーク時の生成レートと持続可能な消費レート、
consume_rate − produce_rateによる解消速度、パーティション数とコンシューマーの並列度、ストレージ量 = レート × メッセージサイズ × 保持期間を計算します(→back-of-the-envelope)。 - プロバイダーを選びます。 デフォルトでは汎用的な手順を使います。ユーザーがクラウドを指定し、マネージドサービスの制限によって選択が変わる場合に限り、プロバイダーのファイルを確認します。
- 回答でトレードオフを示します — ここで選んだ各項目(オプション、ストア、障害ポリシーなど)につき 1 行を設けた
Decision | Solves | Worsens | Change it when…の表を提示します。最後の列には、具体的な切り替え条件(数値または観測可能なシグナル)を記載します。その後、最初に破綻する要素を示します。
すべきこと/避けるべきこと
すべきこと
- ブローカーを選ぶ前に、同期か非同期かを決めます。同期処理は同期処理のままにします。
- デフォルトでは少なくとも1回の配信とし、コンシューマーを冪等にします(→
api-designのキー)。 - キューに上限を設け、バックプレッシャーを適用します。最古メッセージの経過時間/コンシューマーの遅延をアラートで通知します。
- リトライ回数に上限を設け、バックオフとジッターを適用してから、処理不能なメッセージを DLQ に送ります。
- メッセージエンベロープ(
message_id、スキーマバージョン、キー、trace_id)を最初に定義します。
避けるべきこと
- 数値や結合度の問題による必要性が明らかになる前に、「将来のスケールのため」にブローカーを追加しないでください。
- 同期応答を装うために、キュー上でリクエスト/リプライを構築しないでください。
- 冪等な少なくとも1回の配信で十分な場合に、厳密に1回の配信を選ばないでください。
- 上限のないバックログをバッファリングしないでください。回復不能になるまで破綻が隠れてしまいます。
- キーごとの順序保証で十分な場合に、グローバルな順序保証を選ばないでください。
重要な数値
選択する前に数値を定量化します。ピーク時の生成レートと持続的な消費レート(プロデューサーが長時間コンシューマーを上回る可能性がある場合は、バックプレッシャーと深さのアラームが必要です)、保持期間(ストレージ容量を左右します=レート × メッセージサイズ × 保持期間)、パーティション数(コンシューマーの並列度の上限を決めます。グループごとに1パーティションにつき1コンシューマーです)です。バックログは (consume_rate − produce_rate) の速度で解消されます。この値が負の場合、バックログは解消されません。スパイク係数、メッセージサイズ、ストレージ容量の見積もりには back-of-the-envelope を使い、ここでその表を繰り返さないでください。
インターフェースの概要
メッセージは契約です。「イベント」とだけ定義するのではなく、明示的に定義します。
- エンベロープ: 重複排除のための安定した
message_id、type/schema_version、partition_key/順序キー、timestamp、関連付け用のtrace_id。 - ペイロード: バージョン管理されたスキーマです。複数の送信先に配信する場合は、コマンドよりイベントの事実(
OrderPlaced{order_id, total})を優先し、小さく、前方互換性のあるものにします。 - 確認応答の契約: コンシューマーはいつ確認応答を返しますか。副作用の前ですか、後ですか。処理後に確認応答を返すと少なくとも1回の配信になり、処理前に返すと最大1回の配信になります。
- DLQ の形式: 失敗したメッセージには、元のエンベロープに加えて失敗理由と試行回数を保持し、調査や再生ができるようにします。
プロバイダーの選択
まずは上記の汎用的な方法をデフォルトとします。ユーザーがクラウドを指定した場合は、マネージドサービスへの対応付け、クォータ/制限、プロバイダー固有のトレードオフについて references/providers/<provider>.md を参照します。そのプロバイダー用のファイルがなければ、汎用的な方法を回答とします。
図
プロデューサー → ブローカー → コンシューマーの経路、複数のサブスクライバーへのファンアウト、またはリトライ → DLQ の流れを図示するには、プラグイン内の architecture-diagram スキルを使います。簡単なインライン図:producer → [queue] → workers ─fail→ [DLQ]。メイン経路は実線、DLQ への分岐は破線にします。
関連する構成要素
resilience-failure— この概念の担当箇所: リトライ/バックオフ/ジッター、デッドレターキュー、障害封じ込めとしてのバックプレッシャー。このスキルではそれらを取り上げ、もう一方では調整方法を扱います。api-design— 依存先: 少なくとも1回の配信を安全にする仕組みである、同スキルの冪等性キー。data-storage— イベントソーシングやトランザクショナルアウトボックスでこのスキルと組み合わせます。この概念の担当箇所: ストリームが引き継ぐ、ホットシャード/パーティションキーの問題。consistency-coordination— この概念の担当箇所: 順序保証、厳密に1回の配信と冪等性の違い、永続的なワークフローを支えるサーガ/分散トランザクションの理論。back-of-the-envelope— ブローカーのサイズを決める生成/消費レート、保持期間、ストレージ容量のサイジングにつなげます。system-design— 非同期処理への切り替え時にここへ誘導するオーケストレーターです。
参考資料
references/deep-dive.md— 配信保証の仕組み(確認応答、可視性タイムアウト、オフセット、冪等/重複排除コンシューマー、トランザクショナルアウトボックス)、順序制御の内部構造、パーティショニング、DLQ の設計、および手作りのキュー+リトライ+サーガより永続的なワークフローエンジンが適するケース。メッセージング層を詳細に設計する際に参照してください。references/providers/{generic,aws,azure,gcp,temporal}.md— 各環境のサービス対応付け、選択を左右する制限、注意点。