代表的な面接トピック

システムデザイン面接:リプレイ攻撃に安全なWebhook配信をどのように設計するか?

システム設計難しい
Offer.cc 編集チーム公開日 更新日

質問

何千もの顧客エンドポイントに決済イベントを配信するWebhookプラットフォームを設計してください。タイムアウト、重複配信、リプレイ攻撃、および数時間に及ぶサブスクライバーのオフライン状態に耐える必要があります。

プロンプトと前提条件

イベントプロデューサーは、外部のHTTPエンドポイントに向けてビジネスイベントを発行します。プラットフォームはイベントを永続的に保持し、少なくとも1回配信し、重複が発生しても安全に処理できるようにし、各テナントに明確なリトライおよびリプレイの制御機能を提供する必要があります。

面接官がテストするポイント

  • HTTP上でexactly-once(正確に1回)を約束するのではなく、明示的な配信保証を選択できるか。
  • 取り込み、スケジューリング、配信試行、およびサブスクライバー側の副作用(side effect)を分離できるか。
  • イベントID、署名タイムスタンプ、リトライポリシー、デッドレター処理、および公平性(fairness)を適切に組み合わせられるか。

回答前に確認すべき明確化のための質問

  • イベント量、ペイロードサイズ、エンドポイント数、および許容される最大配信遅延はどれくらいか?
  • 順序保証はテナントごと、エンドポイントごとに必要か、それとも不要か?
  • コンシューマーはイベントを冪等(idempotent)に処理できるか、また重複排除の状態はどのくらいの期間保持する必要があるか?
  • ペイロードにはどのようなリプレイ、削除、プライバシー、および監査の要件が適用されるか?

30秒の回答フレームワーク

不変のイベントを安定したIDとともに永続化し、配信試行をキューに入れ、受信側のパスから迅速に応答を返します。ワーカーはタイムスタンプとともに未加工ペイロード(raw payload)に署名し、ジッター(jitter)を伴う指数バックオフ(exponential backoff)を適用して、レスポンスをリトライ可能な失敗と恒久的な失敗に分類します。コンシューマーは副作用を実行する前にイベントIDによって重複排除を行います。テナントごとのスケジューラー、サーキットブレーカー、およびデッドレターキューにより、1つのオフラインエンドポイントが他のエンドポイントのリソースを枯渇させるのを防ぎます。リプレイは、元のイベントIDを変更することなく新しい試行を作成します。

ステップバイステップの詳細解説

1. まずイベントを永続化する

ビジネスイベントとその配信レコードをトランザクション内または信頼性の高いアウトボックス(outbox)パターンを通じて書き込みます。配信レコードには、テナント、エンドポイント、イベントID、試行回数、次回試行時刻、およびステータスを保存します。送信後、成功の記録前にクラッシュすることは想定内です。この場合、再試行が発生するため、コンシューマーは重複を許容できなければなりません。

2. 安全な検証と署名

タイムスタンプに加えて未加工の正確なバイト列に署名し、署名付きメッセージ内にイベントIDを含めます。Stripeは、リプレイ攻撃を制限するためにタイムスタンプ付きの署名を文書化しています。コンシューマーはパースする前に署名を検証し、設定された許容範囲外のタイムスタンプを拒否し、すでにキューに入っているイベントを予期せず無効化することなくシークレットをローテーションします。

3. レスポンスの分類とリトライ

ネットワークタイムアウト、接続障害、および一部の5xxレスポンスをリトライ可能として扱います。不正な認証、未対応のイベントバージョン、およびほとんどの4xxレスポンスは、恒久的障害またはオペレーターによる確認が必要なものとして扱います。ジッターを伴う指数バックオフ、最大試行時間枠、およびデッドレター状態を使用します。恒久的に失敗しているエンドポイントに対して無制限にリトライを繰り返してはいけません。

4. 重複とリプレイを明示的にする

コンシューマーは、処理済みイベントIDを永続的な一意性制約とともに保存し、可能であればビジネスの副作用とともにそのレコードをコミットします。手動リプレイでは不変のイベントペイロードを再利用して新しい配信試行を記録し、ダッシュボードでは初回配信、自動リトライ、オペレーターによるリプレイを明確に区別します。exactly-onceの副作用を実現するにはコンシューマー側のトランザクションが必要であり、ネットワーク自体はat-least-once配信しか提供しません。

5. テナント間の枯渇を起こさずにスケーリングする

キューをテナントまたはエンドポイントごとにパーティション分割し、テナントごとの同時実行数とレート制限を適用し、正常なテナントのためにキャパシティを確保します。サーキットブレーカーは、連続して障害が発生したエンドポイントを一時停止します。メトリクスには、最も古い保留中イベントの経過時間、成功レイテンシ、試行回数の分布、重複率、署名失敗、およびデッドレター量を含める必要があります。

高品質な回答例

「私は配信をスケジュールする前に、安定したIDを持つ各イベントを永続化し、at-least-onceのセマンティクスを約束します。各試行では、イベントIDとタイムスタンプを使用して未加工ペイロードに署名します。ワーカーはタイムアウトと5xxレスポンスをジッター付きリトライ用として分類し、恒久的な4xxレスポンスはデッドレターに移動します。コンシューマーは、副作用のトランザクション内でイベントIDの重複排除を行います。テナントごとのキュー、サーキットブレーカー、経過時間ベースのアラート、およびリプレイワークフローにより、オフラインのサブスクライバーが他者を枯渇させるのを防ぎ、リカバリを監査可能にします。」

よくある間違い

  • HTTP経由でexactly-onceを約束する → クラッシュにより結果が曖昧になる → at-least-onceを明記し、コンシューマーに冪等性を求める。
  • 未加工バイト列ではなくパース後のJSONに署名する → 同等のフォーマットでも検証に失敗する可能性がある → 正確なペイロードのバイト列に対して署名および検証を行う。
  • すべての4xxを永遠にリトライする → 恒久的な障害がキャパシティを消費する → エラーを分類し、恒久的なケースはデッドレターにする。
  • 1つのグローバルキューを使用する → 1つのテナントがすべてのリソースを枯渇させる可能性がある → パーティション分割、レート制限、テナントごとのキャパシティ確保を行う。

フォローアップの質問と回答

重複排除の状態はどのくらいの期間保持すべきですか?

少なくとも自動リトライおよびサポートされているリプレイ期間と同じ期間に、安全マージンを加えた期間です。数か月後にリプレイが発生する可能性がある場合は、コンパクトなイベント台帳を維持するか、コンシューマーにリプレイ識別子と保持ポリシーを明示的に選択させる必要があります。

リプレイには新しいイベントIDを付与すべきですか?

通常は付与しません。ビジネスイベントの同一性は安定したままであり、配信試行に独自のIDと監査レコードが付与されます。これにより、コンシューマーはリプレイを同じイベントとして認識し、重複したビジネス副作用を防ぐことができます。

コンシューマーが副作用をコミットする前に200を返した場合はどうなりますか?

コンシューマーの規約違反となります。プラットフォームはレスポンスから成功を推測できません。コンシューマーは永続的な受け入れ後に確認応答(ACK)を返し、受信トレイ(inbox)パターンまたはトランザクション重複排除レコードを使用し、曖昧な結果に対する照合機能を提供する必要があります。

公開情報ソース

関連する質問

関連面接ツール

システム設計の回答には「回答する」を使用

まず要件を明確にし、スケール、アーキテクチャ、コンポーネント選定、トレードオフの順に進めます。

ツールを見る