📝ノート💻テック

システム設計面接:元Meta Staff Engineerと一緒に広告クリック集計システムを設計する

Tony Duong

Tony Duong

6月 13, 20262

他の言語:🇫🇷🇬🇧
#system-design#ad-tech#kinesis#flink#streaming#cassandra#interview#distributed-systems
システム設計面接:元Meta Staff Engineerと一緒に広告クリック集計システムを設計する

Hello Interview(Evan、元Meta Staff Engineer)による広告クリック集計システムの設計についてのノート。これはトップ企業でよく出るシステム設計の問題であり、彼自身も何度も出題してきたものだ。

プロダクト設計(Ticketmaster、Uber、Dropbox)とは異なり、これはインフラ設計の問題だ。ユーザー向けのAPIやエンティティよりも、データパイプラインと分析が中心になる。

面接のロードマップ

  1. 要件(機能要件 + 非機能要件)
  2. システムインターフェースとデータフロー(コアエンティティ + APIの代わりに)
  3. ハイレベル設計(機能要件を満たす)
  4. ディープダイブ(非機能要件を満たす)

このシステムがやること

ユーザーが広告をクリック → 広告主にリダイレクトされる → クリックがログに記録される → 広告主が時系列でクリック指標をクエリする(キャンペーンの効果、期間ごとのクリック数など)。

最小のクエリ粒度:1分(例:先週分を時間単位の解像度で、昨日分を分単位の解像度で)。

スケールの前提

  • プラットフォーム上に常時約1,000万件の広告
  • ピーク時に約1秒あたり10,000クリック

これらの数字は、スケーラビリティと集計の設計を左右するため重要だ。

機能要件

  • ユーザーが広告をクリック → 広告主のウェブサイトにリダイレクトされる
  • 広告主が自分のキャンペーンのクリック指標を時系列でクエリできる

非機能要件(文脈に固有のもの)

観点 広告クリック集計システムでの捉え方
スケーラビリティ ピークの1秒あたり10Kクリックを処理する
低レイテンシ分析 広告主のクエリが1秒未満で返る
高いデータ整合性 クリックを失わない — 課金/支払いの正確性がこれに依存する
ニアリアルタイム 1分粒度の範囲で、できるだけ新鮮な指標を提供する
冪等性 / セキュリティ クリックスパム / 広告指標の不正な水増しを防ぐ

システムインターフェースとデータフロー

入力: ユーザーのブラウザからのクリックイベント
出力: 広告主がクエリできる集計済みクリック指標

ハイレベルのパイプライン:

  1. ユーザーが広告をクリック → クリックプロセッササービスにヒットする
  2. クリックデータを検証する(冪等性 / 不正対策)
  3. 生のクリックデータをログに記録する
  4. 読み取りに最適化された形へ集計する
  5. クエリサービスが広告主のダッシュボードに提供する

ハイレベル設計(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つある:

  1. シンプル: 即座にリダイレクトし、並行してクリックイベントを送信する — 広告ブロッカーがイベントをスキップできる
  2. より良い: クリックプロセッサを経由したサーバーサイドのリダイレクト — リダイレクト前にクリックがログに記録されることを保証する(面接官とトレードオフを議論する)

重要なポイント

  • インフラ設計の問題では、エンティティ/APIの代わりにシステムインターフェース + データフローを使う
  • 生クリックの保存だけでは10K CPSで1秒未満の分析クエリを満たせない — 事前集計が必要だ
  • ストリーム経路: Kinesis → Flinkでリアルタイムの分単位集計を行う
  • バッチ経路: Cassandra/S3上のSparkでバックフィルと照合を行う
  • Kinesisのホットシャードは、バズった広告のために明示的なパーティショニング戦略を必要とする
  • インプレッションID + Redisによる重複排除を介した冪等性が指標の整合性を守る
  • NFRは数字と文脈で指定する(10K CPS、1分粒度、1秒未満のクエリレイテンシ) — 汎用的なバズワードではなく

🌐 Claudeによる翻訳

Tony Duong

著者: Tony Duong

デジタル日記。思考、経験、そして人生についての考え。