# スケーラブルなコンサートチケット予約システム — 設計計画 ## 1. 前提条件 - 単一のクラウドリージョン(例:AWS us-east-1)、3つのAZを使用。マネージドサービスを優先。 - カタログ(イベント、会場、座席マップ)は読み取り負荷が高く、変更はまれ。 - 予約はホットパスであり、決済は非同期コールバックを介したオフホットパス。 - 「決して売り切らない」は厳格な不変条件であり、二重販売よりも一時的な利用不可を優先する。 - 外部決済プロバイダー(例:Stripe/Adyen)がPCIスコープを処理し、トークンのみを保存する。 - 平均イベントサイズ30,000席。ピーク時のバーストは数分から約1時間持続する。 ## 2. ハイレベルアーキテクチャ クライアント(We...
全文を表示 ▼
# スケーラブルなコンサートチケット予約システム — 設計計画 ## 1. 前提条件 - 単一のクラウドリージョン(例:AWS us-east-1)、3つのAZを使用。マネージドサービスを優先。 - カタログ(イベント、会場、座席マップ)は読み取り負荷が高く、変更はまれ。 - 予約はホットパスであり、決済は非同期コールバックを介したオフホットパス。 - 「決して売り切らない」は厳格な不変条件であり、二重販売よりも一時的な利用不可を優先する。 - 外部決済プロバイダー(例:Stripe/Adyen)がPCIスコープを処理し、トークンのみを保存する。 - 平均イベントサイズ30,000席。ピーク時のバーストは数分から約1時間持続する。 ## 2. ハイレベルアーキテクチャ クライアント(Web/モバイル)→ CDN(CloudFront)→ API Gateway / L7ロードバランサー → エッジ認証(JWT)→ 3つのAZにまたがるKubernetes(EKS)上のステートレスマイクロサービス。 コアサービス: - **Identity Service**: サインアップ、ログイン、JWT発行、MFA。 - **Catalog Service**: イベント、会場、座席マップのメタデータ。読み取り最適化。 - **Inventory/Seat Service**: 権威ある座席状態、ホールド、予約。一貫性のアンカー。 - **Reservation Service**: ホールド → チェックアウト → 決済意図をオーケストレーション。 - **Payment Service**: プロバイダーと統合し、Webhookコールバックを冪等に処理。 - **Ticket Service**: 決済成功後に署名付きデジタルチケット(JWT/QR)を発行。 - **Notification Service**: メール/プッシュ(SES/SNS)。 - **Waiting Room / Virtual Queue Service**: オンセール時のスパイクのエントリをスロットリング。 - **Expiration Worker**: 10分後に未払いホールドを解放。 - **Admin/Onsale Service**: イベント設定、座席マップアップロード、オンセールスケジュール。 クロスファンクショナル:Kafka(MSK)イベント用、Redis(ElastiCache、クラスターモード)ホットステートとロック用、PostgreSQL(Aurora Multi-AZ)トランザクションデータ用、DynamoDB冪等キーとチケットストア用、S3座席マップJSON/画像用、OpenSearchイベント検索用。 ## 3. データストア - **Aurora PostgreSQL(Multi-AZ、ライター1台+リーダー2台)**: イベント、ユーザー、予約、決済、チケット(記録システム)。継続的バックアップ;PITR。 - **Redis Cluster(Multi-AZ、レプリカあり)**: 座席ごとのホールド状態、イベントごとの座席ビットマップキャッシュ、レートリミット、待機室トークン。ホールドに対する高速CASに使用。 - **DynamoDB**: 決済冪等キー、Webhook重複排除、発行済みチケットルックアップ(低レイテンシ、デフォルトでマルチAZ)。 - **Kafka(MSK)**: ドメインイベント(`SeatHeld`, `ReservationCreated`, `PaymentSucceeded`, `PaymentFailed`, `ReservationExpired`, `TicketIssued`)。AZをまたぐレプリケーションファクター3。 - **S3**: 静的な座席マップアーティファクト、チケットPDF。 - **CDN**: イベントリスト、座席マップのスケルトンをキャッシュ(ライブ可用性ではない)。 - **OpenSearch**: イベント検索/フィルタリング。 ## 4. コアデータモデル **events**(event_id PK, venue_id, name, onsale_at, status, version)。 **seats**(seat_id PK, event_id, section, row, number, price_tier, status ENUM[available, held, reserved, sold], hold_id NULL, version BIGINT)。複合インデックス(event_id, status)。楽観的ロックのための行レベルバージョン管理。 **reservations**(reservation_id PK, user_id, event_id, seat_ids[], state ENUM[pending_payment, confirmed, expired, cancelled], created_at, expires_at, payment_intent_id, idempotency_key UNIQUE)。 **payments**(payment_id PK, reservation_id, provider_ref UNIQUE, status, amount, currency, received_at)。少なくとも1回の重複排除のためのプロバイダー参照の一意制約。 **tickets**(ticket_id PK, reservation_id, seat_id, qr_payload, issued_at, signature)。 **outbox**(id, aggregate, payload, published_at) トランザクションアウトボクシングパターン → Kafka用。 Redisキー: - `event:{id}:seat:{seat_id}` → status + ホールド所有者 + TTL 600秒。 - `event:{id}:availability` → 高速カウント用のビットマップ/HLL。 - `hold:{reservation_id}` → 座席リスト、TTL 600秒。 ## 5. コアAPI(REST + 冪等性ヘッダー) - `GET /events?filters` → ページネーションされたリスト(CDNキャッシュ可能、TTL 30秒)。 - `GET /events/{id}` → イベント詳細。 - `GET /events/{id}/seatmap` → 静的なレイアウト(長期キャッシュ)。 - `GET /events/{id}/availability` → 大まかな可用性(セクション);キャッシュ期間1〜5秒。 - `GET /events/{id}/seats?section=A` → 詳細な座席状態(短時間キャッシュまたはライブ)。 - `POST /reservations` (Idempotency-Key) → `{event_id, seat_ids[]}` → 10分間のホールドを作成。 - `GET /reservations/{id}` → 状態、有効期限。 - `DELETE /reservations/{id}` → ユーザーがキャンセルし、座席を解放。 - `POST /reservations/{id}/checkout` → プロバイダーで決済意図を作成し、クライアントシークレットを返す。 - `POST /webhooks/payments` → プロバイダーコールバック(署名付き、冪等)。 - `GET /tickets/{id}` → 署名付きデジタルチケット。 - 管理用:`POST /events`, `POST /events/{id}/seats:bulk`, `POST /events/{id}/onsale`。 ## 6. リクエストフロー ### ブラウジング クライアント → CDN(カタログ/座席マップのヒット)→ ミスの場合、API → カタログサービス → Auroraリーダー / OpenSearch。可用性カウントはRedisから1〜5秒の遅延で提供され、個々の座席状態はユーザーが表示しているセクションに対してライブで取得される。 ### 予約(ホールド) 1. クライアントがIdempotency-Keyと対象座席を指定して`POST /reservations`を送信。 2. API Gatewayが待機室トークンをチェックし、ユーザー/IPごとにレート制限。 3. Reservation Serviceがイベントステータスと座席IDを検証。 4. **Redis Luaスクリプトによるアトミックなホールド取得**:各座席キーについて、`SETNX`でホールドIDとTTL 600秒を設定。いずれかの座席で失敗した場合、成功したものをロールバックして競合する座席とともに409を返す。 5. Auroraに`pending_payment`状態で予約行を永続化(アウトボックイベントとの単一トランザクション)。座席に対する楽観的ロックを使用:`UPDATE seats SET status='held', hold_id=?, version=version+1 WHERE seat_id=? AND status='available'`。Auroraが耐久性のある真実であり、Redisは高速なガードである。両方が一致する必要がある。 6. `expires_at`とともに予約を返す。p95 < 800 ms。 ### 決済 1. クライアントが`POST /reservations/{id}/checkout`を呼び出す。Payment ServiceがプロバイダーでPaymentIntentを作成し、`provider_ref`を予約IDをキーとして保存(冪等)。 2. クライアントはプロバイダーSDKを介して直接決済を完了する(PCIスコープ外)。 3. プロバイダーがWebhookを送信 → `POST /webhooks/payments`。 4. Webhookハンドラー:署名を検証 → `provider_ref` UNIQUEを使用して`payments`にupsert(重複排除)。追加の冪等性のためにDynamoDBの条件付きPutを使用。 5. `succeeded`の場合:トランザクション更新 — 予約→`confirmed`、座席→`sold`、`tickets`を書き込み、アウトボックイベント`TicketIssued`を追記。順不同の安全性:ハンドラーはイベントタイムスタンプを比較し、古い遷移を無視する(ステートマシン:pending → confirmed/failed/expired、ターミナル状態は遅延重複を吸収)。 6. Ticket Serviceが`TicketIssued`を消費し、署名付きQR/PDFを生成し、S3 + DynamoDBに保存。Notification Serviceがユーザーにメール送信。 ### 期限切れ - プライマリ:Redis TTLが`hold:*`キーを期限切れにする → キー空間通知がExpiration Workerをトリガー。Workerは、予約がまだ`pending_payment`の場合のみ座席を解放するAuroraトランザクションを実行(状態に対するCAS)。 - バックストップ:30秒ごとにスケジュールされたジョブが`reservations WHERE state='pending_payment' AND expires_at < now() - 30s`をスキャンして解放。 遅延Webhook到着:座席が既に再販されている場合、決済を`refund_required`とマークし、自動返金をトリガー。座席がまだ空いている場合、オプションで再確認 — ただしデフォルトポリシーは返金であり、再販された可能性のある座席を再ホールドできないため。決済プロバイダーの5分遅延は10分ホールドウィンドウ内に収まるため、通常のケースでは競合しない。 ## 7. 過剰販売の防止(一貫性) - 座席は単一の所有リソース:すべての状態遷移はAuroraで**楽観的並行性**を使用(`WHERE status=expected_status AND version=expected_version`)。 - Redis SETNXは、DBに負荷をかけずに高速な第一線での拒否(8k RPS)を提供。Auroraの行更新は第二線であり、法的な真実である。 - すべての決済側書き込みは、`provider_ref`の一意性とDynamoDB重複排除テーブルにより冪等。 - アウトボックパターンにより、DBコミットに対してドメインイベントがKafkaにExactly-onceで発行される。 - 単一の座席行内での強い一貫性。集計可用性カウント(ブラウズビューに表示される)については、結果整合性のみが許容される。 ## 8. スパイクに対するスケーリング戦略 - **仮想待機室**:`concurrent_users > threshold`の場合、新規ユーザーはキューチケットを取得。予約エンドポイントには1秒あたりNトークンのみが許可される。システムを既知の容量(例:予約試行8,000件/秒を吸収するために10,000件/秒を許可)に保つ。 - **水平オートスケーリング**(EKS上のHPA):CPUとカスタムRPSメトリクスに基づき、オンセール前に15分間ポッドを事前ウォームアップ。 - **ホットイベントのシャーディング**:`event_id`でRedisキーをパーティション化し、単一のメガイベントが専用シャードに着地するようにする。既知のオンセール用にシャードを事前プロビジョニング可能。 - **読み取りスケーリング**:Auroraリードレプリカ + 可用性用のRedis + 静的データ用のCDN。 - **バックプレッシャー**:API Gatewayのユーザーごとのリクエストクォータ。Retry-After付きの429。 - **非同期ファンアウト**:Kafkaはチケット生成、メール、分析をホットパスから分離。 - **コネクションプーリング**:Auroraコネクションストームを回避するためのRDS Proxy / PgBouncer。 - **ボット対策**:オンセール中の`POST /reservations`に対するWAF、CAPTCHA、デバイスフィンガープリンティング。 ## 9. 信頼性とディザスタリカバリ - すべての状態コンポーネント(Aurora、レプリカ付きRedis + 自動フェイルオーバー、MSK RF=3、DynamoDB)に対するMulti-AZ。 - Aurora:S3への継続的バックアップ、保持期間内の任意の秒へのPITR → RPO ≤ 1分達成。フェイルオーバー約30〜60秒 → RTO ≤ 15分達成。 - Redis:自動フェイルオーバー付きMulti-AZ。データはAuroraから再構築可能(ホールドはコールドスタート時に`reservations WHERE state='pending_payment'`から再構築可能)。 - Kafka:階層型ストレージ、RF=3、最小ISR=2。 - DRランブック:リージョン全体の障害復旧のためのクロスリージョンAurora Global Databaseレプリカ(RPO約数秒)、文書化されたプロモーション手順。 - カオスドリル:AZブラックアウト、Redisプライマリキル、決済プロバイダー障害シミュレーションを四半期ごとに実施。 - ヘルスチェック(ALBレベル);サービス間および決済プロバイダーへのサーキットブレーカー(Resilience4jライク)。 - グレースフルデグラデーション:Redisが利用不可の場合、DBのみのパスにフォールバックし、より厳格なレート制限を適用。決済プロバイダーがダウンした場合、チェックアウトをキューに入れ、ユーザーに通知。 ## 10. モニタリングとアラート - **メトリクス(Prometheus + CloudWatch)**:エンドポイントごとのRPS、p50/p95/p99レイテンシ、予約成功率、ホールド取得競合率、決済Webhook遅延、期限切れWorker遅延、Auroraレプリカ遅延、Redis CPU/メモリ/エビクション、Kafkaコンシューマー遅延。 - **SLO**:オンセールウィンドウ中の99.95%の可用性。p95ブラウジング < 300 ms。p95予約 < 800 ms。エラーバジェット消費アラート。 - **トレーシング**:OpenTelemetryエンドツーエンド(クライアント → API → サービス → DB)。 - **ロギング**:CloudWatch/Elasticへの構造化JSON。相関ID。 - **ビジネスダッシュボード**:秒間ホールド数、コンバージョン率(ホールド→支払い)、過剰販売カウンター(0であること — 非ゼロの場合はアラート)。 - **アラート**:過剰販売=0違反(P0)、Webhookバックログ > 1分、予約p95が5分間 > 800 ms、Auroraフェイルオーバー、Redisフェイルオーバー、決済成功率の低下 > 2σ。 ## 11. 主要なトレードオフと代替案 - **ホールド権威としてのRedis vs. DBのみ**:純粋なDBアプローチはよりシンプルで強力だが、単一行ホットスポットで8k RPSを維持できなかった。Redisファーストはスパイクを吸収し、DB行更新が正確性を保証する。 - **悲観的ロック(SELECT FOR UPDATE)**:検討したが、人気のあるセクションでのロック競合がスループットを低下させるため却下。楽観的ロックとリトライの方がスケーラブル。 - **指定席 vs. 一般入場カウンター**:上記の設計は座席ごと。一般入場の場合、単一のデクリメントカウンター(Redisの`DECR`、フロア0、DBにミラーリング)を使用する。 - **単一リージョン vs. マルチリージョンアクティブ/アクティブ**:制約により1リージョン。アクティブ/アクティブは「決して売り切らない」を複雑にする(クロスリージョンコンセンサスが必要)。DRのためにクロスリージョンリードレプリカのみを使用。 - **Kafka vs. SQS**:順序付けられ、リプレイ可能なイベントログ(順不同の決済処理や状態再構築に役立つ)のためにKafkaを選択。 - **同期決済 vs. Webhookのみ**:PCIライトを維持し、プロバイダーの遅延に耐えるためにWebhookのみを選択。Webhookの遅延(最大5分)は10分ホールドウィンドウ内に収まる。 - **待機室 vs. 純粋なオートスケーリング**:オートスケーリングだけではステートフルストアを保護できない。キューは決定論的なロード形状を提供し、大量の503エラーよりも優れたユーザーエクスペリエンスを提供する。 - **チケットの同期発行 vs. 非同期**:Kafkaを介した非同期発行により、決済コールバックハンドラーは高速かつ回復力を持つ。ユーザーはプッシュ/リフレッシュを通じて数秒以内にチケットを確認できる。 この設計は、述べられたSLOを満たし、レイヤ化された楽観的並行性により過剰販売を防ぎ、仮想キュー+Redisフロントエンドホールドによりオンセールスパイクを吸収し、PITRとリハーサル済みフェイルオーバーを備えたMulti-AZマネージドストアを通じてRPO/RTOターゲットを満たします。
判定
勝利票
3 / 3
平均スコア
総合点
総評
回答Aは、フラッシュセール特有の制約、正確性の要件、および運用目標に直接対応する、具体的でエンドツーエンドの設計を提示しています。その最も強力な点は、レイヤ化された売り切れ防止戦略、明確なリクエストフロー、待機室/負荷整形アプローチ、冪等な支払いコールバック処理、および詳細な信頼性/監視セクションです。また、フォールバック動作やトレードオフについても、妥当な具体性をもって議論しています。わずかな弱点としては、RedisとAuroraのデュアルライトホールドパスにおける複雑性の追加や、ドリフトを回避するために注意深いエンジニアリングが必要となる実装上の選択がいくつかありますが、全体としてベンチマーク品質の高いシステム設計の回答と言えます。
採点詳細を表示 ▼
設計の質
重み 30%カタログ、在庫、予約、支払い、チケット発行、待機室、および期限切れ処理ワーカーの間で明確な分離があり、適切に選択されたコンポーネントを備えた強力なアーキテクチャです。設計は、読み取りパス、書き込みパス、イベント処理、および永続ストレージを一貫して結びつけ、在庫サービスを整合性のアンカーとして明示的に特定しています。RedisとAuroraのレイヤ化されたホールドアプローチは洗練されており、問題に適していますが、調整の複雑さを伴います。
完全性
重み 20%前提条件、サービス、データストア、API、データモデル、詳細なブラウジング/予約/支払い/期限切れフロー、売り切れ防止の整合性、スパイク処理、DR、監視、およびトレードオフを網羅しています。また、順不同および遅延したコールバック、バックストップ期限切れスキャン、グレースフルデグラデーション、ボット対策にも対処しています。プロンプトの領域で手つかずのままの部分はほとんどありません。
トレードオフの説明力
重み 20%Redis優先対DBのみ、楽観的ロック対悲観的ロック、Kafka対SQS、Webhookのみの支払い、待機室対オートスケーリング、単一リージョン対アクティブ/アクティブなど、いくつかの意味のあるトレードオフを提供しています。その理由はプロンプトの制約に特化しており、正確性と負荷整形が設計上の選択を支配する理由を説明しています。
拡張性・信頼性
重み 20%スケーラビリティと信頼性の両方において強力です。明確な待機室、レート制限、事前ウォームアップ、ホットイベントのためのシャード対応Redis戦略、読み取りのためのCDNおよびキャッシュレイヤリング、Kafkaによる非同期分離、コネクションプーリング、マルチAZデプロイメント、PITR、フェイルオーバーの期待値、Auroraからのバックストップ再構築、および詳細なアラートが含まれます。明示されたフラッシュセールおよびDRの要件に直接対処しています。
分かりやすさ
重み 10%明確にセクション分けされ、ステップバイステップのフローで構成されており、構造化されていて理解しやすいです。回答は情報量が多いですが、それでも読みやすいです。レイヤ化された整合性戦略のために一部がやや複雑になっていますが、構成によって理解可能な状態に保たれています。
総合点
総評
回答Aは、プロンプトのすべての制約に直接対応する、包括的で深く技術的な設計計画です。レイヤードの一貫性モデル(Redis SETNX + Auroraの楽観的ロック)、フラッシュセールトラフィック向けの具体的な仮想待機室戦略、Redis TTLプライマリパスとDBスキャンバックストップの両方を備えた詳細な有効期限フロー、アウトボックパターンによる冪等な支払い処理、および特定のSLO連動アラートを提供します。データモデルは正確であり、リクエストフローは段階的かつ機械的に健全であり、トレードオフは一般的な声明ではなく具体的な理由で議論されています。軽微な弱点:一部のセクションは密であり図があると有益ですが、クロスリージョンのDRセクションは簡潔ですが、全体としてこれはベンチマーク品質の回答です。
採点詳細を表示 ▼
設計の質
重み 30%回答Aは、明確な関心の分離、正確な2フェーズの一貫性モデル(Redis SETNX + Auroraの楽観的ロック)、信頼性の高いKafka公開のためのトランザクショナルアウトボック、プロバイダー参照の一意性とDynamoDBの重複排除による冪等な支払い処理、および仮想待機室を備えた、よくレイヤードされたアーキテクチャを提示します。すべてのコンポーネントの選択は正当化され、特定の制約に結び付けられています。データモデルは、バージョン列、hold_id、アウトボックテーブルを含め、詳細かつ正確です。
完全性
重み 20%回答Aは、サービス、データストア、API、データモデル、4つのリクエストフロー(ブラウズ、予約、支払い、期限切れ)、スケーリング戦略、信頼性/DR(RPO/RTO分析付き)、整合性保証、監視(特定のSLO連動アラート付き)、およびトレードオフを含む、すべての必須セクションをカバーしています。また、ボット対策、コネクションプーリング、事前ウォーミング、期限切れ後の遅延Webhook処理にも対応しています。
トレードオフの説明力
重み 20%回答Aは、具体的な、よく議論されたトレードオフを提供します:Redisファースト対DBのみ(RPSの正当化付き)、悲観的ロック対楽観的ロック(競合の理由付き)、シングルリージョン対マルチリージョンアクティブ-アクティブ(売り越しリスクの説明付き)、Kafka対SQS、待機室対純粋な自動スケーリング、非同期対同期チケット発行。各トレードオフは、特定の制約または障害モードに結び付けられています。
拡張性・信頼性
重み 20%回答Aは、150k同時ユーザー/8k RPSのスパイクに対して、仮想待機室(Nトークン/秒を許可)、セール前の15分間のポッド事前ウォーミング、イベントシャーディングされたRedis、コネクションプーリングのためのRDSプロキシ、ボット対策のためのWAF/CAPTCHA、および非同期Kafkaファンアウトで対応します。信頼性は、すべてのストアのマルチAZ、RPO < 1分のRDS PITR、RTO < 15分のフェイルオーバー、DBからのRedis再構築可能性、カオスドリル、およびサーキットブレーカーをカバーします。クロスリージョンのグローバルデータベースは、リージョン全体のDRのために言及されています。
分かりやすさ
重み 10%回答Aは、番号付きセクション、明確なヘッダー、段階的なフローでよく整理されています。Redisキーのスキーマとデータモデルは明示的に記載されています。文章は冗長ではなく、正確かつ技術的です。軽微な問題:一部のセクション(特に整合性とスケーリング)の密度は、要約図があると有益かもしれませんが、文章は全体的に明確です。
総合点
総評
回答Aは、傑出した、非常に詳細なシステム設計を提供しています。その中核的な強みは、Redis Luaスクリプトとデータベースの楽観的ロックを組み合わせて、高速かつアトミックなチェックを行い、正確性を確保するという、高同時実行性の座席予約問題に対処するための具体的で堅牢なメカニズムにあります。スケーリング戦略は包括的かつ先を見越したものであり、仮想待機室、インスタンスの事前ウォームアップ、キャッシュ内のホットイベントのシャーディングといった実践的な詳細が含まれています。この設計は完全であり、十分に論理的で、関連するトレードオフに対する深い理解を示しています。
採点詳細を表示 ▼
設計の質
重み 30%アーキテクチャは優れています。高速ガードとしてRedisをアトミック操作(SETNX/Lua)と共に使用し、その前面に楽観的ロックを備えたデータベースを配置するという選択は、この高競合問題に対する優れたパターンです。これにより、データベースに過負荷をかけることなく、8k RPSの負荷を効果的に処理できます。仮想待機室の統合も重要な強みです。
完全性
重み 20%回答は非常に包括的で、プロンプトのすべての点を実質的な詳細をもって扱っています。トランザクショナルアウトボックスパターン、冪等性のためのDynamoDBの使用、ボット対策戦略などの具体的な実装詳細が含まれており、これらは基本的な要件を超えています。
トレードオフの説明力
重み 20%トレードオフの理由は優れており、深い専門知識を示しています。Redis対DBのみ、悲観的ロック対楽観的ロック、そしてSQS対Kafkaの選択に関する議論はすべて直接関連しており、システムの特定の制約の文脈で十分に正当化されています。
拡張性・信頼性
重み 20%これは際立ったセクションです。スケーリング戦略は包括的かつ先を見越したものであり、Podの事前ウォームアップ、ホットイベントのためのイベントIDによるRedisのシャーディング、RDSプロキシの使用といった具体的な技術に言及しています。信頼性計画も最高レベルで、すべてのコンポーネントに対するマルチAZ、クロスリージョンのDR計画、カオスドリルへのコミットメントが含まれています。
分かりやすさ
重み 10%回答は非常に明瞭で、構成が良く、理解しやすいです。番号付きセクション、小見出し、太字の使用は、読者を複雑な設計を通して効果的に誘導します。