システムデザイン面接:Ticketmaster設計(元Meta Staff Engineer)
Tony Duong
6月 5, 2026 ・ 2 分

Hello Interview(元Meta staff engineer)によるTicketmaster風チケット予約サービス設計のメモ — Metaのプロダクトデザイン面接・システムデザイン面接で頻出の問題。
面接のロードマップ
- 要件(機能要件 + 非機能要件)
- コアエンティティ + API
- 高レベル設計(機能要件を満たす)
- 深掘り(非機能要件を満たす)
要件は約5分以内に収める。
機能要件
- ユーザーがイベントを検索できる
- ユーザーがイベントを閲覧できる(詳細、座席マップ、空きチケット)
- ユーザーがチケットを予約できる
非機能要件(文脈に即した記述)
「スケーラビリティ」「可用性」を並べるだけでなく、このシステムが難しい理由を説明する。
| 懸念 | Ticketmaster固有のフレーミング |
|---|---|
| 一貫性 vs 可用性(CAP) | 予約は強い一貫性 — 二重予約なし(1席、1ユーザー)。検索・閲覧は高可用性 — 新イベントが数秒遅れて表示されてもOK |
| 読み取り/書き込み比率 | 約100:1(閲覧が多く購入は少ない;約1%のコンバージョン) |
| スケーリング | 汎用的なスケールではなく — 人気イベントによる急増(Taylor Swift、Super Bowl、ワールドカップ) |
| 低レイテンシ検索 | フルテーブルスキャンは通用しない |
スコープ外(線の下に記載):GDPR、汎用的な障害耐性の詳細など — 面接官と優先度を確認する。
コアエンティティ
- Event(会場、出演者、メタデータ、チケットとの関係)
- Venue(場所、座席マップ)
- Performer
- Ticket(座席、価格、ステータス、event FK)
まずエンティティ名から始め、設計が進むにつれてフィールドを詳細化する。
API
イベント閲覧
GET /events/{eventId} → イベント、会場、出演者、チケット一覧(座席マップ用)
検索
GET /search?term=&location=&type=&date=... → 部分的なイベント一覧(結果表示に十分;詳細は閲覧エンドポイント経由)
予約(2フェーズ — 実際のTicketmasterと同様)
- Reserve:
POST /booking/reserve— ボディ:ticketIdのみ;ユーザーはJWT/ヘッダーから取得、ボディには含めない(セキュリティ) - Confirm:
PUT/PATCH /booking/confirm—ticketId+ 支払い詳細 → Stripe(成功時にwebhookコールバック)
Reserveはユーザーが支払いを完了するまで約10分間座席を確保する。
高レベルアーキテクチャ
Client → API Gateway(認証、レート制限、ルーティング)→ マイクロサービス:
| サービス | 役割 |
|---|---|
| Event CRUD | イベント閲覧、座席マップデータの読み込み |
| Search | イベント発見 |
| Booking | Reserve + confirm |
PostgreSQLをプライマリストレージに — リレーション(event → tickets)、予約の正確性のためのACIDトランザクション。面接ではSQL vs NoSQLの議論は弱い;必要な品質(トランザクション、チケットの一貫性)に焦点を当てる。PostgresでもDynamoDB + トランザクションでも成立しうる。
チケットステータス: available | reserved | booked(予約済み時はユーザーID付き)
Stripeで支払い(非同期;bookedにマークする前にwebhookで成功を確認)。
予約期限 — 重要な設計判断
問題: ユーザーが予約後、支払いを放棄 — 座席がreservedのまま永遠にロックされる。
ミドルレベル: タイムスタンプ + クエリまたはcron
reserved_timestampを追加;10分超のreservedを空きとして扱うクエリ- または約10分ごとのcronで古い予約をリセット
欠点(シニアとの差): cronは遅延nを導入 — 座席が10分を過ぎてもcron間隔分ロックされ続ける可能性(例: 合計19分)。
シニア: Redis分散ロック + TTL
- 予約時: Postgresのステータスは更新しない — Redisに
ticketIdをTTL = 10分で設定 - 座席マップ表示時:
availableチケットをクエリし、Redisロックに存在するIDを除外 - TTL期限切れ → 座席は自動的に空きになる(cron遅延なし)
- 複数のbookingサービスインスタンスが一貫したロックビューを必要とするため、Redisは別途用意
Redis障害時: 二重予約の試行が可能になる短いウィンドウ;PostgresのACIDはconfirm時に依然として1人の勝者を保証 — 敗者にとっては悪いUXだが、プロダクトと議論すべき許容可能なトレードオフ。
深掘り
低レイテンシ検索 — Elasticsearch + CDC
初期設計: SQL LIKEワイルドカード → フルテーブルスキャン — 遅すぎる。
Elasticsearchで転置インデックスによるテキスト検索;位置情報用のジオスペーシャルサポート。
ESをプライマリストアにしない(耐久性、トランザクション)。Postgresから同期:
- アプリコードでのデュアルライト(部分障害の処理)、または
- CDC → ストリーム → ES更新(面接でよくある回答;イベント/会場はめったに変わらない — キュー不要)
人気検索のキャッシュ:
- OpenSearchノードのクエリキャッシュ
- 検索語をキーにしたRedis
- CDNで
GET /searchを約30–60秒キャッシュ(同一クエリパラメータで有効;パーソナライズされたランキングや緯度経度のような高カーディナリティパラメータでは機能しない)
リアルタイム座席マップ + 急増対応
古い座席マップ: ユーザーがページを読み込み;数秒で座席が売り切れる → クリックでエラー表示。
選択肢:
- Long polling — 安価、ページ上の短いセッションに適する
- SSE (Server-Sent Events) — サーバーが座席更新をクライアントにプッシュ(一方向;ここでは十分)。WebSocketsも可能だが過剰。
人気イベント: ページ読み込み、座席が即座に消える — 悪いUX。
仮想待機キュー(Redis sorted setで到着時刻順、または公平性のためランダム):
- 数百万人が同時アクセス → イベントページではなくキュー入りメッセージを表示
- キャパシティに応じてバッチでユーザーを解放(例: 予約された座席数ごと)
- 入場許可時にSSEで通知 → その後予約
シンプルで創造的な絞り込みポイント — 常に最も複雑な技術である必要はない。
読み取りのスケーリング
- API Gateway + サービスは水平スケール(マネージドLB)
- イベント/会場/出演者をRedisにキャッシュ(めったに変わらない) — 閲覧APIは動的チケットのみDBにアクセス
- 計算で必要と判明したらPostgresをシャーディング(シャードキーの議論: 地理的には
eventIdvsvenueId)
計算のコツ: 結果が設計を変える場合にのみバックオブザベンベロープ見積もりを行う — 最初のチェックボックス作業ではない。
DDIAとの関連
Designing Data-Intensive Applicationsを想起させるパターン:
- 派生データ — 正(ソースオブトゥルース)から維持されるElasticsearchインデックス(Ch. 11 CDC、データ統合)
- ログ/ストリーム — OLTP DBからの変更ストリームとしてのCDC
- 一貫性 — ビジネスが要求する箇所では強い保証(予約)、許容できる箇所では緩和(検索)
- キャッシュ — ホットパスで鮮度と読み取りレイテンシのトレードオフ
要点
- 二重予約の防止が予約パスの強い一貫性を駆動;検索/閲覧は可用性を優先できる
- 2フェーズ予約(reserve → 支払い → confirm)は標準;APIをそれに合わせて設計する
- Redis TTLロックは10分ホールドにcronより優れる — シニアとの差別化ポイント
- 検索にはElasticsearch + CDC;Postgresとのデュアルプライマリではない
- セレブイベント急増には仮想待機キュー
- 座席マップの鮮度と読み取り偏重トラフィックにはSSE + キャッシュ
- 非機能要件は汎用バズワードではなく問題の文脈で具体化する
🌐 Claudeによる翻訳