システム設計面接:元Meta Staff Engineerと一緒に広告クリック集計システムを設計する
Tony Duong
6月 13, 2026 ・ 2 分

Hello Interview(Evan、元Meta Staff Engineer)による広告クリック集計システムの設計についてのノート。これはトップ企業でよく出るシステム設計の問題であり、彼自身も何度も出題してきたものだ。
プロダクト設計(Ticketmaster、Uber、Dropbox)とは異なり、これはインフラ設計の問題だ。ユーザー向けのAPIやエンティティよりも、データパイプラインと分析が中心になる。
面接のロードマップ
- 要件(機能要件 + 非機能要件)
- システムインターフェースとデータフロー(コアエンティティ + APIの代わりに)
- ハイレベル設計(機能要件を満たす)
- ディープダイブ(非機能要件を満たす)
このシステムがやること
ユーザーが広告をクリック → 広告主にリダイレクトされる → クリックがログに記録される → 広告主が時系列でクリック指標をクエリする(キャンペーンの効果、期間ごとのクリック数など)。
最小のクエリ粒度:1分(例:先週分を時間単位の解像度で、昨日分を分単位の解像度で)。
スケールの前提
- プラットフォーム上に常時約1,000万件の広告
- ピーク時に約1秒あたり10,000クリック
これらの数字は、スケーラビリティと集計の設計を左右するため重要だ。
機能要件
- ユーザーが広告をクリック → 広告主のウェブサイトにリダイレクトされる
- 広告主が自分のキャンペーンのクリック指標を時系列でクエリできる
非機能要件(文脈に固有のもの)
| 観点 | 広告クリック集計システムでの捉え方 |
|---|---|
| スケーラビリティ | ピークの1秒あたり10Kクリックを処理する |
| 低レイテンシ分析 | 広告主のクエリが1秒未満で返る |
| 高いデータ整合性 | クリックを失わない — 課金/支払いの正確性がこれに依存する |
| ニアリアルタイム | 1分粒度の範囲で、できるだけ新鮮な指標を提供する |
| 冪等性 / セキュリティ | クリックスパム / 広告指標の不正な水増しを防ぐ |
システムインターフェースとデータフロー
入力: ユーザーのブラウザからのクリックイベント
出力: 広告主がクエリできる集計済みクリック指標
ハイレベルのパイプライン:
- ユーザーが広告をクリック → クリックプロセッササービスにヒットする
- クリックデータを検証する(冪等性 / 不正対策)
- 生のクリックデータをログに記録する
- 読み取りに最適化された形へ集計する
- クエリサービスが広告主のダッシュボードに提供する
ハイレベル設計(v1 — 素朴版)
Browser → Click Processor → Click DB (Cassandra) → Query Service → Advertiser browser
- Cassandraは面接でよく選ばれる選択肢だ — LSMスタイルの書き込み(メモリ上のmemtable、定期的なディスクへのフラッシュ)が高い書き込みスループットをうまく処理する
- Cassandraはキーによるポイントルックアップに最適化されており、範囲クエリや集計には向いていない — しかし広告主が必要とするのはまさに後者だ
問題: 1週間分の生クリックを分粒度でクエリするということは、数百万行のスキャン/集計を意味する — 1秒未満のNFRを満たすには遅すぎる。PostgresやDynamoDBであっても同様だ。
ディープダイブ:バッチによる事前集計(Spark)
Sparkのバッチレイヤーを追加する:
- 定期的なmap-reduceジョブがすべてのCassandraシャードを読み取る
- クリックを分間隔で集計する
- 事前集計したカウントを読み取りに最適化されたOLAP DB(あるいはこのよりシンプルなクエリ形状ならDynamoDB / Postgres)に書き込む
クエリサービスは事前集計された分単位のバケットを読み取るようになる → 広告主には十分に速い。
トレードオフ: バッチ間隔がレイテンシを追加する(例:指標が表示されるまで5分の遅延)。
ディープダイブ:ストリーム処理(Kinesis + Flink)
素朴な書き込み経路をストリームで置き換える(あるいは補完する):
Click Processor → Kinesis (click event stream) → Flink (stream aggregator) → Aggregated store → Query Service
- Kinesis(またはKafka)がクリックイベントストリームを保持する
- Flinkがイベントをリアルタイムで消費し、時間ウィンドウごとのインメモリ集計を維持する(例:45分、カウント = 12)
- ローリング集計を読み取りストアに書き込む → バッチジョブを待たずにニアリアルタイムの分析を実現する
面接では、マネージドのKinesis/Kafkaは常に利用可能だと仮定してよい。
ホットシャード問題
バズった広告(例:Nike + LeBron)はKinesisでホットシャードを生み出しうる — 1つのパーティションが書き込みに圧倒される → レイテンシの増加、あるいはデータ損失すら起こる。
対策: デフォルトのキーを超えてデータをさらにパーティション分割する(例:複合パーティションキー、ソルティング)ことで、単一のシャードがすべてのトラフィックを吸収しないようにする。
冪等性とクリック検証
問題: 広告ブロッカーを使うユーザーはリダイレクトURLを抽出してクリックイベントの送信をスキップできる。攻撃者は偽のクリックをスパムできる。
アプローチ:
- 広告が表示されたときに広告インプレッションIDを生成する(リターゲティング:月曜と木曜の同じ広告は別々に追跡される)
- インプレッションIDをクリック処理まで引き継ぐ
- Redisキャッシュが確認済みのインプレッションIDを保存する — カウントする前に重複を拒否 / クリックの正当性を検証する
定期的な照合(リコンシリエーション)
ストリーム + Flinkの経路は、純粋なLambdaでも純粋なKappaでもない:
- Kappa: すべてをリアルタイムのストリーム処理で行う
- Lambda: バッチレイヤー + 別のリアルタイムレイヤー(リアルタイムは近似でよい)
最終設計では**定期的な照合(リコンシリエーション)**を追加する:
- Kinesisが生のクリックイベントをS3にダンプできるようにする
- 時間ごと/日ごとのバッチジョブ(Spark)が生イベントを再処理する
- リアルタイム経路からのドリフトや損失を補正する → 課金のためのデータ整合性を保証する
リダイレクトフローのニュアンス
クリック → リダイレクトを扱う方法は2つある:
- シンプル: 即座にリダイレクトし、並行してクリックイベントを送信する — 広告ブロッカーがイベントをスキップできる
- より良い: クリックプロセッサを経由したサーバーサイドのリダイレクト — リダイレクト前にクリックがログに記録されることを保証する(面接官とトレードオフを議論する)
重要なポイント
- インフラ設計の問題では、エンティティ/APIの代わりにシステムインターフェース + データフローを使う
- 生クリックの保存だけでは10K CPSで1秒未満の分析クエリを満たせない — 事前集計が必要だ
- ストリーム経路: Kinesis → Flinkでリアルタイムの分単位集計を行う
- バッチ経路: Cassandra/S3上のSparkでバックフィルと照合を行う
- Kinesisのホットシャードは、バズった広告のために明示的なパーティショニング戦略を必要とする
- インプレッションID + Redisによる重複排除を介した冪等性が指標の整合性を守る
- NFRは数字と文脈で指定する(10K CPS、1分粒度、1秒未満のクエリレイテンシ) — 汎用的なバズワードではなく
🌐 Claudeによる翻訳