システム設計:Go で実装する高スループット Webhook 配信システム
問題¶
Design a high-throughput webhook delivery system that can send millions of webhooks reliably to external customers.
シニア Go エンジニア向けシステム設計面接問題。
解答¶
イベント発生時にコアサービスが直接 HTTP 呼び出しをするのではなく、キューを介して非同期配信する設計が基本。
アーキテクチャ概要¶
[Event Producer]
→ イベント発生(payment_succeeded / order_created / user_deleted)
→ Durable Store に保存 + Kafka/SQS/NATS へ publish
↓
[Webhook Dispatcher]
→ 購読しているカスタマーを特定
→ ペイロードに署名(HMAC-SHA256)
→ カスタマーごと / 優先度別のキューへ配信ジョブを push
↓
[Worker Pool]
→ HTTP リクエストをカスタマーエンドポイントへ送信
→ 失敗時はリトライ or DLQ
解説¶
配信ステータスの管理¶
全配信試行を永続化し、ステータスを追跡する。
CREATE TABLE webhook_deliveries (
id UUID PRIMARY KEY,
webhook_id UUID NOT NULL,
customer_id UUID NOT NULL,
status ENUM('pending', 'delivered', 'failed', 'retrying', 'dead'),
attempt INT DEFAULT 0,
next_retry TIMESTAMP,
created_at TIMESTAMP,
delivered_at TIMESTAMP
);
リトライ戦略(Exponential Backoff)¶
ただし 400 はリトライしない(ペイロードが不正なため、同じ内容を再送しても意味がない)。
カスタマーごとの分離¶
// カスタマー単位のワーカープールで他カスタマーへの影響を遮断
type CustomerWorkerPool struct {
customerID string
sem chan struct{} // 同時実行数の制限
jobs chan DeliveryJob
}
func (p *CustomerWorkerPool) process(ctx context.Context) {
for job := range p.jobs {
p.sem <- struct{}{}
go func(j DeliveryJob) {
defer func() { <-p.sem }()
deliver(ctx, j)
}(job)
}
}
1カスタマーのエンドポイントが遅くても、他のカスタマーへの配信がブロックされない。
HTTP クライアントの適切な設定(Go)¶
// http.Client は使い回す(per-request 生成は NG)
var httpClient = &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 1000,
MaxConnsPerHost: 100,
IdleConnTimeout: 90 * time.Second,
TLSHandshakeTimeout: 5 * time.Second,
ResponseHeaderTimeout: 3 * time.Second,
},
}
ペイロード署名(セキュリティ)¶
func signPayload(secret, payload string) string {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write([]byte(payload))
return "sha256=" + hex.EncodeToString(mac.Sum(nil))
}
// X-Webhook-Signature ヘッダーに付与
カスタマーはシークレットで署名を検証し、なりすましを防止する。
スケール見積もり¶
DAU: 1000万
1ユーザー/日のイベント: 10件
→ 1億 webhook/日 ≈ 1,157 webhook/秒(平均)
→ ピーク: 10,000 webhook/秒
Worker 構成:
- カスタマー A(高トラフィック): 50並列
- カスタマー B(標準): 10並列
- カスタマー C(低量): 2並列
面接でのポイント¶
- 「非同期キュー分離」「per-customer ワーカー分離」の2点は必須
- リトライは 4xx と 5xx で戦略を分ける(4xx はリトライしない)
- HMAC 署名によるセキュリティへの言及で加点
- Go 固有:
http.Clientの使い回し・Transport設定・context.Contextによるタイムアウト管理 - DLQ・ステータス永続化でオブザーバビリティも評価される