Day 10: ECサイトとレートリミッターの設計
今日学ぶこと
- 商品カタログサービスの設計
- ショッピングカートサービス
- 注文・決済処理
- 在庫管理(売り過ぎ防止)
- Elasticsearchによる検索
- 分散トランザクション(Sagaパターン)
- レートリミッターのアルゴリズム詳解
- 分散レートリミティング
- 面接の最終アドバイス
Part 1: ECプラットフォーム
全体アーキテクチャ
flowchart TB
Client["クライアント"]
GW["API Gateway"]
Client --> GW
subgraph Services["マイクロサービス"]
Product["商品サービス"]
Cart["カートサービス"]
Order["注文サービス"]
Payment["決済サービス"]
Inventory["在庫サービス"]
Search["検索サービス"]
Notify["通知サービス"]
end
GW --> Product
GW --> Cart
GW --> Order
GW --> Search
subgraph Data["データストア"]
ProductDB["Product DB\n(PostgreSQL)"]
CartDB["Cart\n(Redis)"]
OrderDB["Order DB\n(PostgreSQL)"]
InvDB["Inventory DB\n(PostgreSQL)"]
ES["Elasticsearch"]
MQ["Kafka"]
end
Product --> ProductDB
Cart --> CartDB
Order --> OrderDB
Order --> Payment
Order --> Inventory --> InvDB
Product --> ES
Search --> ES
Order --> MQ --> Notify
style Services fill:#3b82f6,color:#fff
style Data fill:#8b5cf6,color:#fff
商品カタログサービス
-- Product catalog schema
CREATE TABLE products (
id UUID PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
price DECIMAL(10,2) NOT NULL,
category_id UUID REFERENCES categories(id),
seller_id UUID NOT NULL,
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT NOW()
);
CREATE TABLE product_variants (
id UUID PRIMARY KEY,
product_id UUID REFERENCES products(id),
sku VARCHAR(100) UNIQUE NOT NULL,
color VARCHAR(50),
size VARCHAR(20),
price DECIMAL(10,2),
image_urls TEXT[]
);
検索にはElasticsearchを使用:
{
"query": {
"bool": {
"must": [
{ "match": { "name": "ワイヤレスイヤホン" } }
],
"filter": [
{ "range": { "price": { "gte": 3000, "lte": 10000 } } },
{ "term": { "category": "electronics" } }
]
}
},
"sort": [
{ "_score": "desc" },
{ "sales_count": "desc" }
]
}
ショッピングカートサービス
カートはRedisに保存し、高速なアクセスを実現します。
flowchart LR
subgraph CartFlow["カートのフロー"]
Add["商品追加"]
Update["数量変更"]
Remove["商品削除"]
Checkout["チェックアウト"]
end
subgraph Storage2["ストレージ"]
Redis3["Redis\n(ログインユーザー)"]
Cookie["Cookie/LocalStorage\n(ゲストユーザー)"]
end
CartFlow --> Storage2
style CartFlow fill:#3b82f6,color:#fff
style Storage2 fill:#f59e0b,color:#fff
// Redis data structure for cart
Key: cart:{user_id}
Value: Hash
field: {product_variant_id}
value: {quantity, added_at, price_at_add}
// TTL: 30 days (inactive carts expire)
| ユーザー種別 | カート保存先 | 特徴 |
|---|---|---|
| ログインユーザー | Redis(サーバー側) | デバイス間で同期 |
| ゲストユーザー | Cookie / LocalStorage | ログイン時にマージ |
注文・決済処理
sequenceDiagram
participant U as ユーザー
participant O as 注文サービス
participant I as 在庫サービス
participant P as 決済サービス
participant N as 通知サービス
U->>O: 注文確定
O->>I: 在庫確保(予約)
I->>O: 確保成功
O->>P: 決済処理
alt 決済成功
P->>O: 決済完了
O->>I: 在庫確定
O->>N: 注文確認メール送信
O->>U: 注文完了
else 決済失敗
P->>O: 決済失敗
O->>I: 在庫解放(ロールバック)
O->>U: 注文失敗
end
在庫管理 — 売り過ぎ防止
人気商品やフラッシュセールでは、同時に大量の注文が来ます。売り過ぎを防ぐ仕組みが重要です。
flowchart TB
subgraph Problem["問題:売り過ぎ"]
Stock["在庫: 1個"]
R1["ユーザーA: 購入"]
R2["ユーザーB: 購入"]
R1 -->|"在庫チェック: 1 > 0 ✓"| Stock
R2 -->|"在庫チェック: 1 > 0 ✓"| Stock
Result["結果: 2人に販売(在庫は1個なのに!)"]
end
style Problem fill:#ef4444,color:#fff
解決策:
| 方式 | 仕組み | メリット | デメリット |
|---|---|---|---|
| 悲観的ロック | SELECT ... FOR UPDATE | 確実 | スループット低下 |
| 楽観的ロック | バージョン番号チェック | 高スループット | リトライが必要 |
| Redis DECR | アトミックなデクリメント | 超高速 | Redis障害時のリスク |
| 在庫予約パターン | 仮確保 → 確定/解放 | 安全 | 複雑 |
// Redis-based inventory check (atomic operation)
WATCH inventory:{product_id}
val = GET inventory:{product_id}
if val > 0:
MULTI
DECR inventory:{product_id}
EXEC
else:
// Out of stock
分散トランザクション — Sagaパターン
マイクロサービスでは、複数のサービスにまたがるトランザクションにACIDは使えません。代わりにSagaパターンを使います。
flowchart LR
subgraph Saga["Saga: 注文処理"]
S1["1. 在庫確保"]
S2["2. 決済処理"]
S3["3. 配送手配"]
S4["4. 通知送信"]
end
subgraph Compensate["補償トランザクション"]
C1["在庫解放"]
C2["返金処理"]
C3["配送キャンセル"]
end
S1 -->|"成功"| S2
S2 -->|"成功"| S3
S3 -->|"成功"| S4
S3 -.->|"失敗"| C2
C2 -.->|"ロールバック"| C1
style Saga fill:#22c55e,color:#fff
style Compensate fill:#ef4444,color:#fff
Sagaの2つの実装方式:
| 方式 | 説明 | メリット | デメリット |
|---|---|---|---|
| Choreography | 各サービスがイベントを発行・購読 | 疎結合 | フローが追いにくい |
| Orchestration | 中央のオーケストレーターが制御 | フローが明確 | オーケストレーターがSPOF |
flowchart TB
subgraph Orchestration["Orchestration方式"]
Orch["Saga Orchestrator"]
IS["在庫サービス"]
PS["決済サービス"]
SS["配送サービス"]
Orch -->|"1. 在庫確保"| IS
IS -->|"OK"| Orch
Orch -->|"2. 決済"| PS
PS -->|"OK"| Orch
Orch -->|"3. 配送"| SS
end
style Orchestration fill:#3b82f6,color:#fff
Part 2: レートリミッター
なぜレートリミッターが必要か
- DDoS攻撃の防御
- APIの公平な利用の確保
- コスト管理(クラウドリソースの過剰消費防止)
- サービスの安定性確保
アルゴリズム詳解
1. Token Bucket
flowchart TB
subgraph TokenBucket["Token Bucket"]
Refill["トークン補充\n(10個/秒)"]
Bucket2["バケット\n容量: 20個"]
Refill -->|"一定レートで\n補充"| Bucket2
end
Req["リクエスト"] --> Bucket2
Bucket2 -->|"トークンあり\n→ 消費"| Allow2["許可"]
Bucket2 -->|"トークンなし"| Deny2["拒否 (429)"]
style TokenBucket fill:#3b82f6,color:#fff
style Allow2 fill:#22c55e,color:#fff
style Deny2 fill:#ef4444,color:#fff
- バースト対応: バケットが満タンなら一時的に多くのリクエストを許可
- パラメータ: バケット容量、補充レート
- 使用例: AWS API Gateway、Stripe
2. Leaky Bucket
flowchart TB
subgraph LeakyBucket["Leaky Bucket"]
Queue2["キュー\n(FIFO)"]
Process["一定レートで処理\n(10個/秒)"]
end
Req2["リクエスト"] --> Queue2
Queue2 -->|"キューに空きあり"| Process --> Allow3["処理"]
Queue2 -->|"キューが満杯"| Deny3["拒否 (429)"]
style LeakyBucket fill:#8b5cf6,color:#fff
style Allow3 fill:#22c55e,color:#fff
style Deny3 fill:#ef4444,color:#fff
- 出力が均一: バーストを平滑化
- パラメータ: キューサイズ、処理レート
3. Fixed Window Counter
Window: 1分間
Limit: 100リクエスト
[00:00 - 01:00] → カウント: 87 ✓
[01:00 - 02:00] → カウント: 103 ✗ (3件拒否)
- 問題: ウィンドウ境界でバーストが発生する可能性
flowchart LR
subgraph Boundary["境界問題"]
W1["Window 1\n00:00-01:00\n最後に90件"]
W2["Window 2\n01:00-02:00\n最初に90件"]
W1 -->|"00:30-01:30の間に\n180件通過!"| W2
end
style Boundary fill:#ef4444,color:#fff
4. Sliding Window Log
各リクエストのタイムスタンプをログに記録し、ウィンドウ内のカウントを計算します。
Limit: 100/分
Window: 直近1分間
Request at 01:00:45 → Log: [00:59:50, 00:59:55, 01:00:10, ..., 01:00:45]
Count timestamps in [00:00:45, 01:00:45] = 98 → 許可 ✓
- 正確: 境界問題なし
- コスト: メモリ消費が大きい(全タイムスタンプを保存)
5. Sliding Window Counter
Fixed WindowとSliding Window Logの良いとこ取り。
Previous window count: 84
Current window count: 36
Current position in window: 25% elapsed
Weighted count = 84 × 0.75 + 36 = 99 → 許可 ✓
アルゴリズム比較
| アルゴリズム | メモリ効率 | 精度 | バースト許容 | 実装の複雑さ |
|---|---|---|---|---|
| Token Bucket | 良い | 中 | あり | 低 |
| Leaky Bucket | 良い | 中 | なし(平滑化) | 低 |
| Fixed Window | 良い | 低 | 境界問題あり | 低 |
| Sliding Window Log | 悪い | 高 | なし | 中 |
| Sliding Window Counter | 良い | 中〜高 | 少しあり | 中 |
分散レートリミティング
複数のサーバーでレートリミットを共有する必要があります。
flowchart TB
subgraph Clients["クライアント"]
C1["Client A"]
C2["Client B"]
end
subgraph LBs["Load Balancer"]
LB2["LB"]
end
subgraph Servers["APIサーバー"]
S1["Server 1"]
S2["Server 2"]
S3["Server 3"]
end
subgraph RateLimit["共有レートリミット"]
Redis4["Redis Cluster\n(中央カウンター)"]
end
Clients --> LBs --> Servers
S1 --> Redis4
S2 --> Redis4
S3 --> Redis4
style Servers fill:#3b82f6,color:#fff
style RateLimit fill:#ef4444,color:#fff
Redisでの実装:
// Sliding window counter with Redis
MULTI
ZADD rate_limit:{client_id} {timestamp} {request_id}
ZREMRANGEBYSCORE rate_limit:{client_id} 0 {timestamp - window_size}
ZCARD rate_limit:{client_id}
EXEC
// If count > limit → reject (429)
レートリミティングの適用レイヤー:
| レイヤー | 実装場所 | 目的 |
|---|---|---|
| L3/L4 | ファイアウォール、iptables | IP単位でDDoS防御 |
| L7 | API Gateway、Nginx | ユーザー/API単位で制御 |
| アプリケーション | アプリ内ミドルウェア | ビジネスロジック単位 |
面接の総まとめとアドバイス
システム設計面接のフレームワーク
flowchart LR
subgraph Framework["面接の進め方 (40-45分)"]
S1["1. 要件確認\n(5分)"]
S2["2. 概算見積もり\n(5分)"]
S3["3. ハイレベル設計\n(10分)"]
S4["4. 詳細設計\n(15分)"]
S5["5. スケーリング\n& まとめ\n(5-10分)"]
end
S1 --> S2 --> S3 --> S4 --> S5
style Framework fill:#3b82f6,color:#fff
10日間の振り返り
| Day | テーマ | キーワード |
|---|---|---|
| 1 | 基礎概念 | スケーラビリティ、レイテンシ、可用性 |
| 2 | ネットワークとプロトコル | TCP/UDP、HTTP、DNS、CDN |
| 3 | データベース設計 | SQL/NoSQL、インデックス、シャーディング |
| 4 | キャッシュと整合性 | Redis、Cache-Aside、CAP定理 |
| 5 | 分散システム | レプリケーション、コンセンサス、メッセージキュー |
| 6 | マイクロサービスとAPI | REST/gRPC、API Gateway、OAuth |
| 7 | URL短縮サービス | Base62、キャッシュ戦略、リダイレクト |
| 8 | SNSフィードとチャット | Fan-out、WebSocket、プレゼンス |
| 9 | 動画配信とストレージ | HLS、CDN、ファイルチャンキング |
| 10 | ECサイトとレートリミッター | Saga、在庫管理、Token Bucket |
面接で高評価を得るためのポイント
| ポイント | 説明 |
|---|---|
| 質問する | 要件を自分から確認する。曖昧なまま設計を始めない |
| トレードオフを語る | 「Aの方が良いがBのデメリットがある」と比較する |
| 概算を示す | QPS、ストレージ、帯域を数値で見積もる |
| ボトルネックを予測する | 設計のどこが問題になるか自ら指摘する |
| 段階的に設計する | まず動くものを設計し、その後スケーリングを議論する |
| 図を描く | コンポーネント図、データフロー図を積極的に使う |
| 非機能要件を意識する | 可用性、レイテンシ、セキュリティに言及する |
まとめ
今日のポイント一覧
| トピック | 重要ポイント |
|---|---|
| 商品カタログ | PostgreSQL + Elasticsearchで全文検索 |
| ショッピングカート | Redisに保存、ゲストはCookieで管理 |
| 注文処理 | 在庫確保 → 決済 → 確定の順序 |
| 在庫管理 | Redis DECRまたは楽観的ロックで売り過ぎ防止 |
| Sagaパターン | 補償トランザクションで分散ロールバック |
| Token Bucket | バースト許容、最も一般的なアルゴリズム |
| 分散レートリミット | Redisで中央カウンターを共有 |
| レイヤー別制限 | L3/L4(DDoS)、L7(API)、アプリ(ビジネスロジック) |
練習問題
基礎レベル
- Token BucketとSliding Window Counterの違いを図を使って説明してください
- Sagaパターンの2つの実装方式(Choreography vs Orchestration)のメリット・デメリットを比較してください
中級レベル
- フラッシュセール(タイムセール)で1秒間に10万件の注文が来る場合の在庫管理システムを設計してください
- 分散レートリミッターをRedisで実装する場合、Redisがダウンした際の対策を3つ以上考えてください
チャレンジ
- チケット予約システム(Ticketmaster型)を設計してください。以下を含めてください:
- 座席選択と仮予約(5分間のロック)
- 決済処理
- 売り切れ防止
- 1人あたりの購入枚数制限
- 販売開始時の大量アクセス対策(バーチャル待合室)
- 1つのイベントに10万人が同時アクセスする場合のスケーラビリティ
参考リンク
- Amazon Architecture
- Saga Pattern (Microsoft)
- Elasticsearch Documentation
- Rate Limiting Algorithms
- System Design Interview by Alex Xu
- Designing Data-Intensive Applications
おめでとうございます!
おめでとうございます!10日間のシステム設計の学習を完了しました。
この10日間で、システム設計面接に必要な基礎概念から実践的なシステム設計まで幅広く学びました。
これからの学習のために
- 繰り返し練習する: 学んだシステムを白紙から自分で設計し直してみましょう
- 実際のシステムを研究する: 有名企業のエンジニアリングブログを読みましょう
- 模擬面接をする: 友人やオンラインプラットフォームで練習しましょう
- 深掘りする: 興味のあるトピックをさらに深く学びましょう
最も大切なこと: システム設計に「唯一の正解」はありません。トレードオフを理解し、要件に基づいて合理的な判断ができることが、面接で最も評価されるポイントです。
あなたの面接の成功を応援しています!