- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- 1. システムデザイン面接(めんせつ)4段階(だんかい)フレームワーク
- 2. スケーラビリティ
- 3. ロードバランシング
- 4. キャッシング
- 5. データベース設計(せっけい)
- 6. CAP定理(ていり)とPACELC
- 7. メッセージキュー
- 8. レートリミティング
- 9. 実践(じっせん)設計(せっけい)例(れい)
- 10. Back-of-Envelope推定(すいてい)
- 11. よくある間違(まちが)いとヒント
- 12. クイズ
- 参考資料(さんこうしりょう)
はじめに
システムデザイン面接(めんせつ)は、シニアエンジニア採用(さいよう)において最(もっと)も重要(じゅうよう)な評価項目(ひょうかこうもく)です。アルゴリズム問題(もんだい)がコーディング能力(のうりょく)を測定(そくてい)するのに対(たい)し、システムデザインは大規模(だいきぼ)システムを設計(せっけい)しトレードオフを理解(りかい)する総合的(そうごうてき)なエンジニアリング能力(のうりょく)を評価(ひょうか)します。
Google、Meta、Amazon、Netflixなどのビッグテック企業(きぎょう)は、45〜60分(ぷん)で数百万(すうひゃくまん)〜数億人(すうおくにん)のユーザーを処理(しょり)できるシステムの設計(せっけい)を求(もと)めます。成功(せいこう)するためには、要件(ようけん)を分析(ぶんせき)し、適切(てきせつ)な技術(ぎじゅつ)を選択(せんたく)し、トレードオフを明確(めいかく)に説明(せつめい)できる必要(ひつよう)があります。
このガイドでは、4段階(だんかい)フレームワークからスケーラビリティ、キャッシング、データベース、メッセージキュー、実践(じっせん)設計(せっけい)例(れい)まで体系的(たいけいてき)にカバーします。
1. システムデザイン面接(めんせつ)4段階(だんかい)フレームワーク
1.1 ステップ1:要件(ようけん)の明確化(めいかくか)(5分(ふん))
面接(めんせつ)で最(もっと)も重要(じゅうよう)な最初(さいしょ)のステップです。いきなり設計(せっけい)に入(はい)るのは最(もっと)もよくある間違(まちが)いです。
機能要件(きのうようけん):
- コア機能(きのう)は何(なに)か?
- ユーザーが実行(じっこう)する主要(しゅよう)な操作(そうさ)は?
- 入出力(にゅうしゅつりょく)は何(なに)か?
非機能要件(ひきのうようけん):
- 想定(そうてい)ユーザー数(すう)/ DAU
- 読(よ)み取(と)り対(たい)書(か)き込(こ)み比率(ひりつ)
- レイテンシ要件(ようけん)
- 可用性(かようせい)レベル(99.9%? 99.99%?)
- データ一貫性(いっかんせい)要件(ようけん)
面接官:「URL短縮サービスを設計してください。」
良い質問の例:
Q:「想定DAUはどれくらいですか?」
A:「1億人です。」
Q:「URL短縮とリダイレクションのどちらが重要ですか?」
A:「リダイレクションがより重要です。読み取りの方が圧倒的に多いです。」
Q:「短縮URLの長さに制限はありますか?」
A:「できるだけ短くしてください。」
Q:「カスタムURLをサポートする必要がありますか?」
A:「はい、オプションでサポートしてください。」
1.2 ステップ2:ハイレベル設計(せっけい)(15〜20分(ぷん))
要件(ようけん)をもとにシステム全体(ぜんたい)の大(おお)きな絵(え)を描(か)きます。
+-----------+ +---------------+ +---------------+
| Client |---->| Load Balancer |---->| Web Server |
+-----------+ +---------------+ +------+--------+
|
+--------------------------+
v v
+-----------+ +---------------+
| Cache | | Database |
+-----------+ +---------------+
このステップでカバーすべきこと:
- API設計(せっけい)(RESTfulエンドポイント)
- データモデル設計(せっけい)
- 主要(しゅよう)コンポーネントとその関係(かんけい)
- データフロー
1.3 ステップ3:詳細設計(しょうさいせっけい)(15〜20分(ぷん))
面接官(めんせつかん)が関心(かんしん)のある特定(とくてい)のコンポーネントを深(ふか)く掘(ほ)り下(さ)げます。
- データベーススキーマとインデックス戦略(せんりゃく)
- キャッシング戦略(せんりゃく)と無効化(むこうか)
- スケーラビリティのボトルネックと解決策(かいけつさく)
- 障害(しょうがい)処理(しょり)とリカバリ
1.4 ステップ4:まとめ(5分(ふん))
- 設計(せっけい)のボトルネック特定(とくてい)
- 将来(しょうらい)のスケーリング方向(ほうこう)を議論(ぎろん)
- モニタリング/アラート戦略(せんりゃく)
- 追加機能(ついかきのう)の提案(ていあん)
2. スケーラビリティ
2.1 垂直(すいちょく)スケーリング(Vertical Scaling)
既存(きそん)サーバーのハードウェアスペックを向上(こうじょう)させる方式(ほうしき)です。
スケーリング前: スケーリング後:
+---------------+ +---------------+
| 4 CPU | | 32 CPU |
| 16GB RAM | ---> | 256GB RAM |
| 500GB SSD | | 4TB SSD |
+---------------+ +---------------+
メリット: 実装(じっそう)が簡単(かんたん)、データ一貫性(いっかんせい)の維持(いじ)が容易(ようい)、ソフトウェア変更(へんこう)不要(ふよう)
デメリット: 物理的(ぶつりてき)な限界(げんかい)あり、単一障害点(たんいつしょうがいてん)(SPOF)、コストが指数的(しすうてき)に増加(ぞうか)
2.2 水平(すいへい)スケーリング(Horizontal Scaling)
サーバー数(すう)を増(ふ)やして負荷(ふか)を分散(ぶんさん)する方式(ほうしき)です。
+---------------+
+---->| Server 1 |
| +---------------+
+-----------+ | +---------------+
| Load |-+---->| Server 2 |
| Balancer | | +---------------+
+-----------+ | +---------------+
+---->| Server 3 |
| +---------------+
| +---------------+
+---->| Server N |
+---------------+
メリット: 理論上(りろんじょう)無制限(むせいげん)にスケール可能(かのう)、障害(しょうがい)の分離(ぶんり)、段階的(だんかいてき)な拡張(かくちょう)
デメリット: データ一貫性(いっかんせい)管理(かんり)の複雑(ふくざつ)さ、セッション管理(かんり)が必要(ひつよう)、運用(うんよう)の複雑(ふくざつ)さ増加(ぞうか)
2.3 ステートレスサービス設計(せっけい)
水平(すいへい)スケーリングの鍵(かぎ)はサーバーをステートレスにすることです。
Bad (Stateful):
+-----------+ session +-----------+
| Client |---------->| Server A | (Server Aが落ちるとセッション喪失)
+-----------+ +-----------+
Good (Stateless):
+-----------+ +-----------+ +-----------+
| Client |---->| Any |---->| Redis |
+-----------+ | Server | | (Session) |
+-----------+ +-----------+
セッション、キャッシュ、状態情報(じょうたいじょうほう)を外部(がいぶ)ストア(Redis、Memcached)に保存(ほぞん)すれば、どのサーバーでも同一(どういつ)のリクエストを処理(しょり)できます。
2.4 オートスケーリング戦略(せんりゃく)
# AWS Auto Scaling設定例
AutoScalingGroup:
MinSize: 2
MaxSize: 20
DesiredCapacity: 4
TargetTrackingScaling:
TargetValue: 70.0 # CPU 70%維持
PredefinedMetricType: ASGAverageCPUUtilization
# スケジュールベーススケーリング(予測可能なトラフィック)
ScheduledActions:
- Schedule: "0 8 * * MON-FRI" # 平日午前8時
MinSize: 6
- Schedule: "0 22 * * *" # 毎日午後10時
MinSize: 2
| 戦略 | ベース | 適切な状況 |
|---|---|---|
| Target Tracking | メトリック目標値 | CPU、リクエスト数ベース |
| Step Scaling | 閾値ステップ | 急激なトラフィック変化 |
| Scheduled | 時間ベース | 予測可能なパターン |
| Predictive | MLベース | 繰り返しパターン |
3. ロードバランシング
3.1 ロードバランシングアルゴリズム
1. Round Robin(順次配分)
Request 1 -> Server A
Request 2 -> Server B
Request 3 -> Server C
Request 4 -> Server A (最初に戻る)
2. Weighted Round Robin(加重順次)
Server A (weight=5): 5リクエスト処理
Server B (weight=3): 3リクエスト処理
Server C (weight=2): 2リクエスト処理
3. Least Connections(最少接続)
Server A: 10 connections <-- 新リクエストはここへ
Server B: 25 connections
Server C: 18 connections
4. IP Hash(IPベース配分)
hash(client_ip) % server_count = target_server
-> 同じクライアントは常に同じサーバーへ
3.2 L4 vs L7 ロードバランサー
L4(トランスポート層):
+----------+ TCP/UDP +-----------+
| Client |--------------->| L4 LB |-- IP:Portベースの分配
+----------+ +-----------+
高速、シンプル、SSLオフロード不可
L7(アプリケーション層):
+----------+ HTTP/HTTPS +-----------+
| Client |--------------->| L7 LB |-- URL, Header, Cookieベースの分配
+----------+ +-----------+
コンテンツベースルーティング、SSL終端、より柔軟
| 特性 | L4 | L7 |
|---|---|---|
| レイヤー | TCP/UDP | HTTP/HTTPS |
| パフォーマンス | 非常に高速 | 相対的に遅い |
| ルーティング | IP/Port | URL, Header, Cookie |
| SSL終端 | 不可 | 可能 |
| 使用例 | AWS NLB | AWS ALB, Nginx |
3.3 コンシステントハッシュ
通常(つうじょう)のハッシュはサーバーの追加(ついか)/削除(さくじょ)時(じ)にほぼ全(すべ)てのキーが再配置(さいはいち)されます。コンシステントハッシュはこの問題(もんだい)を解決(かいけつ)します。
通常のハッシュ:
hash(key) % 3 = サーバー番号
-> サーバー追加時: hash(key) % 4 = ほとんどのキーが移動!
コンシステントハッシュ(ハッシュリング):
Server A
/ \
/ 0 \
/ \
Server D Server B
\ /
\ key /
\ | /
Server C
-> サーバー追加時: 隣接キーのみ移動(全体の約1/N)
仮想(かそう)ノードによる均等(きんとう)分配(ぶんぱい):
class ConsistentHash:
def __init__(self, nodes, virtual_nodes=150):
self.ring = SortedDict()
self.virtual_nodes = virtual_nodes
for node in nodes:
for i in range(virtual_nodes):
key = self._hash(f"node-vn-i")
self.ring[key] = node
def get_node(self, data_key):
if not self.ring:
return None
hash_val = self._hash(data_key)
# ハッシュリング上で時計回りに最も近いノードを見つける
idx = self.ring.bisect_right(hash_val)
if idx == len(self.ring):
idx = 0
return self.ring.peekitem(idx)[1]
def _hash(self, key):
return int(hashlib.md5(key.encode()).hexdigest(), 16)
4. キャッシング
4.1 キャッシング戦略(せんりゃく)
1. Cache-Aside(Lazy Loading):
+--------+ 1.読取 +---------+
| App |-------->| Cache |
+---+----+ miss +---------+
| 2.DB読取 ^
v 3.キャッシュ保存
+--------+
| DB |
+--------+
2. Write-Through:
+--------+ 1.書込 +---------+ 2.同期書込 +--------+
| App |-------->| Cache |------------>| DB |
+--------+ +---------+ +--------+
(常に一貫性保証、書込レイテンシ増加)
3. Write-Back(Write-Behind):
+--------+ 1.書込 +---------+ 2.非同期バッチ書込 +--------+
| App |-------->| Cache |==================>| DB |
+--------+ +---------+ +--------+
(書込性能優秀、データ喪失リスク)
4. Write-Around:
+--------+ 1.書込 +--------+
| App |-------->| DB |
+---+----+ +--------+
| 2.読取時キャッシュロード
v
+---------+
| Cache |
+---------+
4.2 Redis vs Memcached
# Redis - 多様なデータ構造をサポート
import redis
r = redis.Redis(host='localhost', port=6379)
# String
r.set('user:1001:name', 'Alice', ex=3600) # 1時間TTL
# Hash
r.hset('user:1001', mapping={
'name': 'Alice',
'email': 'alice@example.com',
'login_count': 42
})
# Sorted Set(リーダーボード)
r.zadd('leaderboard', {'player_a': 1500, 'player_b': 1800})
top_10 = r.zrevrange('leaderboard', 0, 9, withscores=True)
# Pub/Sub
r.publish('notifications', 'New message!')
| 特性 | Redis | Memcached |
|---|---|---|
| データ構造 | String, Hash, List, Set, Sorted Set等 | Key-Valueのみ |
| 永続化 | RDB/AOFサポート | なし |
| レプリケーション | Master-Replica | なし |
| クラスタ | Redis Cluster | クライアント側シャーディング |
| メモリ効率 | 相対的に低い | 高い(シンプル構造) |
| ユースケース | セッション、リーダーボード、キュー、Pub/Sub | 単純キャッシング |
4.3 CDN(Content Delivery Network)
CDNなし:
ユーザー(東京) ---- 10000km ----> Origin Server(米国)
RTT: ~200ms
CDNあり:
ユーザー(東京) ---- 20km ----> CDN Edge(東京) -- cache hit
RTT: ~5ms
Cache Miss時:
ユーザー(東京) -> CDN Edge(東京) -> Origin(米国)
| キャッシュ保存
次リクエストからEdgeで応答
CDNキャッシュ無効化(むこうか)戦略(せんりゃく):
1. TTLベース: Cache-Control: max-age=86400
2. バージョニング: /static/app.v2.3.1.js
3. 手動無効化: CloudFront Invalidation
4. タグベース: Surrogate-Keyヘッダーで選択的パージ
4.4 キャッシュの問題(もんだい)と解決策(かいけつさく)
1. Cache Stampede(キャッシュスタンピード):
TTL満了 -> 数千リクエストが同時にDBへ殺到
解決: Mutexロック + 事前更新
2. Cache Penetration(キャッシュ貫通):
存在しないキーの繰り返し照会 -> 毎回DB照会
解決: Bloom Filter + 空結果もキャッシュ
3. Hot Key Problem:
特定キーにトラフィック集中 -> 単一キャッシュノード過負荷
解決: ローカルキャッシュ + キー複製(key_1, key_2, ...)
5. データベース設計(せっけい)
5.1 SQL vs NoSQL
| 基準 | SQL (RDBMS) | NoSQL |
|---|---|---|
| データモデル | 正規化テーブル | Document, Key-Value, Column, Graph |
| スキーマ | 固定スキーマ | 柔軟なスキーマ |
| スケーリング | 主に垂直 | 水平スケーリング容易 |
| トランザクション | ACID完全サポート | 制限的(一部サポート) |
| ジョイン | 効率的サポート | 制限的/不可 |
| 適切な場合 | 複雑な関係、トランザクション重要 | 大容量、柔軟なスキーマ、高性能 |
| 代表製品 | PostgreSQL, MySQL | MongoDB, Cassandra, DynamoDB |
5.2 データベースレプリケーション
Master-Slaveレプリケーション:
+-----------+ 同期/非同期レプリケーション +-----------+
| Master | ==================================> | Slave 1 | <- 読取専用
| (Write) | ==================================> | Slave 2 | <- 読取専用
+-----------+ +-----------+
Multi-Masterレプリケーション:
+-----------+ <=================================> +-----------+
| Master 1 | 双方向レプリケーション | Master 2 |
| (R/W) | | (R/W) |
+-----------+ +-----------+
(コンフリクト解決が必要)
5.3 データベースシャーディング
シャーディング前:
+-----------------------+
| Single Database |
| 10TB、レイテンシ増加 |
+-----------------------+
シャーディング後(Hashベース):
hash(user_id) % 4 = shard_number
+-----------+ +-----------+ +-----------+ +-----------+
| Shard 0 | | Shard 1 | | Shard 2 | | Shard 3 |
| 2.5TB | | 2.5TB | | 2.5TB | | 2.5TB |
+-----------+ +-----------+ +-----------+ +-----------+
シャーディング戦略(せんりゃく)比較(ひかく):
| 戦略 | メリット | デメリット |
|---|---|---|
| Range-based | 範囲クエリが効率的 | ホットスポット発生可能 |
| Hash-based | 均等分配 | 範囲クエリが非効率 |
| Directory-based | 柔軟なマッピング | ディレクトリがSPOF |
| Geographic | 地域性活用 | リージョン間クエリ困難 |
シャーディング時(じ)の考慮事項(こうりょじこう):
1. ジョインクエリ: シャード間ジョイン不可 -> 非正規化またはアプリレベルジョイン
2. リバランシング: データ増加時のシャード再分配 -> Consistent Hashing活用
3. ホットスポット: 特定シャードにトラフィック集中 -> シャードキー再設計
4. トランザクション: 分散トランザクション -> Sagaパターンまたは2PC
5. ユニークID: グローバルユニークID必要 -> Snowflake ID, UUID
6. CAP定理(ていり)とPACELC
6.1 CAP Theorem
Consistency(一貫性)
/ \
/ \
/ CA \
/ (single DC) \
/------------------\
| |
CP | 分散システム | AP
| Pは避けられない |
|--------------------|
Availability
(可用性)
ネットワークパーティション発生時:
CPシステム -> 一貫性維持、一部リクエスト拒否
APシステム -> 可用性維持、一時的不整合許容
実際(じっさい)のシステムのCAP選択(せんたく):
| システム | 選択 | 理由 |
|---|---|---|
| 銀行口座 | CP | 残高不整合は不可 |
| DNS | AP | 少し古いレコード許容 |
| MongoDB | CP | 強い一貫性がデフォルト |
| Cassandra | AP | 結果整合性(Eventual) |
| ZooKeeper | CP | 分散コーディネーション |
6.2 PACELC理論(りろん)
CAPを拡張(かくちょう)したPACELCは、パーティションがない場合(ばあい)のトレードオフも説明(せつめい)します。
if (Partition) then
Choose between Availability and Consistency
else
Choose between Latency and Consistency
例:
- DynamoDB: PA/EL(可用性 + 低レイテンシ)
- MongoDB: PC/EC(一貫性優先)
- Cassandra: PA/EL(可用性 + 低レイテンシ)
- PostgreSQL(single): PC/EC(一貫性 + 一貫性)
7. メッセージキュー
7.1 非同期処理(ひどうきしょり)の必要性(ひつようせい)
同期処理:
Client -> API -> メール送信(3s) -> 画像リサイズ(2s) -> ログ保存(1s) -> Response
合計6秒待機
非同期処理:
Client -> API -> Message Queue -> Response(即座に)
|
+------+------+
v v
Email Worker Image Worker
(3s) (2s)
合計0.1秒待機
7.2 Kafka vs RabbitMQ vs SQS
Apache Kafka:
+-----------+ +---------------------------+
| Producer |---->| Topic: orders |
+-----------+ | +-------+-------+------+ |
| | P0 | P1 | P2 | |
| +---+---+---+---+--+---+ |
+------+-------+------+-----+
v v v
+------------------------+
| Consumer Group A |
| (各パーティション1消費者)|
+------------------------+
| 特性 | Kafka | RabbitMQ | SQS |
|---|---|---|---|
| モデル | ログベースストリーミング | AMQPメッセージブローカー | マネージドキュー |
| スループット | 毎秒数百万メッセージ | 毎秒数万メッセージ | 自動スケーリング |
| メッセージ保持 | 設定期間保持 | 消費後削除 | 14日 |
| 順序保証 | パーティション内保証 | キュー単位保証 | FIFOキュー使用時 |
| 消費モデル | Pull(Consumer Poll) | Push + Pull | Pull(Long Polling) |
| ユースケース | イベントストリーミング、ログ | タスクキュー、RPC | サーバーレス、デカップリング |
7.3 メッセージ配信保証(はいしんほしょう)
1. At-Most-Once(最大1回):
Producer -> Broker(ACK待たない)
高速だがメッセージ喪失の可能性
用途: ログ、メトリック(喪失許容)
2. At-Least-Once(最低1回):
Producer -> Broker -> ACK
ACK失敗時に再送 -> 重複の可能性
用途: ほとんどのシステム(冪等処理必要)
3. Exactly-Once(正確に1回):
トランザクションベースまたは冪等キー使用
パフォーマンスオーバーヘッドあり
用途: 金融、決済(正確性必須)
8. レートリミティング
8.1 Token Bucketアルゴリズム
import time
import threading
class TokenBucket:
def __init__(self, capacity, refill_rate):
self.capacity = capacity # バケット最大トークン数
self.tokens = capacity # 現在のトークン数
self.refill_rate = refill_rate # 毎秒追加トークン
self.last_refill = time.time()
self.lock = threading.Lock()
def allow_request(self):
with self.lock:
now = time.time()
# トークン補充
elapsed = now - self.last_refill
self.tokens = min(
self.capacity,
self.tokens + elapsed * self.refill_rate
)
self.last_refill = now
# リクエスト許可判定
if self.tokens >= 1:
self.tokens -= 1
return True
return False
# 毎秒10リクエスト、バースト最大20
limiter = TokenBucket(capacity=20, refill_rate=10)
8.2 Sliding Window Logアルゴリズム
import time
from collections import deque
class SlidingWindowLog:
def __init__(self, window_size, max_requests):
self.window_size = window_size # ウィンドウサイズ(秒)
self.max_requests = max_requests
self.requests = deque()
def allow_request(self):
now = time.time()
# ウィンドウ外の古いリクエストを削除
while self.requests and self.requests[0] < now - self.window_size:
self.requests.popleft()
if len(self.requests) < self.max_requests:
self.requests.append(now)
return True
return False
# 1分間に最大100リクエスト
limiter = SlidingWindowLog(window_size=60, max_requests=100)
8.3 Sliding Window Counter
Sliding Window Counter(メモリ効率的):
前ウィンドウカウント: 84
現ウィンドウカウント: 36
ウィンドウサイズ: 60秒
現在位置: ウィンドウの25%地点
加重カウント = 84 * (1 - 0.25) + 36 = 84 * 0.75 + 36 = 99
max_requests = 100 -> 許可(99 < 100)
| アルゴリズム | メモリ | 精度 | 実装複雑度 |
|---|---|---|---|
| Token Bucket | O(1) | 高い | 低い |
| Leaky Bucket | O(1) | 高い | 低い |
| Sliding Window Log | O(N) | 非常に高い | 中程度 |
| Sliding Window Counter | O(1) | 近似値 | 中程度 |
| Fixed Window | O(1) | 低い | 非常に低い |
9. 実践(じっせん)設計(せっけい)例(れい)
9.1 URL Shortener設計(せっけい)
要件(ようけん):
- 1億DAU、読取:書込 = 100:1
- 短縮(たんしゅく)URLはできるだけ短(みじか)く(7文字(もじ))
- カスタムURLサポート
- リダイレクションレイテンシ100ms以下(いか)
Back-of-Envelope計算(けいさん):
書込: 1億DAU * 平均0.1 URL/日 = 1000万/日
QPS(書込)= 10,000,000 / 86,400 = ~116 QPS
QPS(読取)= 116 * 100 = ~11,600 QPS
ピークQPS = 11,600 * 3 = ~35,000 QPS
ストレージ容量(5年):
10,000,000 * 365 * 5 = 182.5億レコード
各レコード ~500 bytes -> 182.5億 * 500B = ~9.1TB
システムアーキテクチャ:
+----------+ +------+ +---------------+
| Client |---->| LB |---->| API Server |
+----------+ +------+ +------+--------+
|
+----------------+----------------+
v v v
+-----------+ +-----------+ +-----------+
| Redis | | MySQL | | Analytics |
| Cache | | (Sharded) | | (Kafka) |
+-----------+ +-----------+ +-----------+
URLエンコーディング戦略(せんりゃく):
import hashlib
def generate_short_url(long_url, counter):
# 方法1: Base62エンコーディング(auto-increment ID)
chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
short = ""
n = counter
while n > 0:
short = chars[n % 62] + short
n //= 62
return short # 例: "dK4x9Z"
# 方法2: MD5ハッシュ + Base62(最初の7文字)
hash_val = hashlib.md5(long_url.encode()).hexdigest()
num = int(hash_val[:12], 16)
short = ""
for _ in range(7):
short = chars[num % 62] + short
num //= 62
return short
# 62^7 = 約3.5兆個のユニークURL可能
9.2 Twitter/Xフィード設計(せっけい)
コア問題(もんだい):Fan-out
Fan-out on Write(Pushモデル):
ユーザーがツイート投稿時 -> 全フォロワーのタイムラインに事前保存
User Aがツイート -> Fan-out Service
+--> Follower 1のタイムラインキャッシュに追加
+--> Follower 2のタイムラインキャッシュに追加
+--> Follower Nのタイムラインキャッシュに追加
メリット: 読取が非常に高速(事前計算済み)
デメリット: フォロワーが多いと書込負荷が膨大
Fan-out on Read(Pullモデル):
タイムラインリクエスト時 -> フォロー中ユーザーのツイートをリアルタイムで組立
User Bのタイムラインリクエスト -> フォローユーザー照会
+--> User Xの最新ツイート
+--> User Yの最新ツイート
+--> User Zの最新ツイート
-> マージ + ソート -> 返却
メリット: 書込がシンプル
デメリット: 読取時に複数DB照会が必要
Celebrity Problem解決(かいけつ)(ハイブリッド):
一般ユーザー(フォロワー < 10,000): Fan-out on Write
セレブリティ(フォロワー > 10,000): Fan-out on Read
タイムライン取得:
1. キャッシュから事前ファンアウト済みツイートを取得
2. フォロー中セレブリティの最新ツイートを照会
3. 両結果をマージ + 時間順ソート
9.3 チャットシステム設計(せっけい)
WebSocketベースのリアルタイム通信:
+----------+ WebSocket +---------------+
| User A |<==========>| Chat |
+----------+ | Server |
| Cluster |
+----------+ WebSocket | |
| User B |<==========>| |
+----------+ +------+-------+
|
+-----------+-----------+
v v v
+----------+ +--------+ +----------+
| Redis | | Kafka | | Message |
| Presence | | | | DB |
+----------+ +--------+ +----------+
メッセージ順序保証(じゅんじょほしょう):
方法1: サーバータイムスタンプ(シンプルだが時計同期問題)
方法2: Snowflake ID(時間 + サーバー + シーケンス)
Snowflake ID構造(64ビット):
+---------------+----------+----------+--------------+
| Timestamp | Datactr | Machine | Sequence |
| 41 bits | 5 bits | 5 bits | 12 bits |
+---------------+----------+----------+--------------+
-> 時間順ソート可能 + グローバルユニーク
-> 毎秒約400万ID生成可能
9.4 Netflix/ビデオストリーミング設計(せっけい)
ビデオアップロードパイプライン:
+-----------+ +-----------+ +-------------------+
| Uploader |--->| Object |--->| Transcoding |
| | | Store | | Pipeline |
+-----------+ | (S3) | | |
+-----------+ | +---------------+ |
| | 240p | |
| | 480p | |
| | 720p | |
| | 1080p | |
| | 4K | |
| +---------------+ |
+---------+----------+
v
+-------------------+
| CDNにデプロイ |
|(全世界Edge) |
+-------------------+
Adaptive Bitrate Streaming(ABR):
ユーザーのネットワーク状態に応じて画質を自動調整:
帯域幅10Mbps -> 1080pセグメントをリクエスト
帯域幅3Mbps -> 480pセグメントに切替
帯域幅回復 -> 段階的に画質を上げる
HLS(HTTP Live Streaming)方式:
1. ビデオを2~10秒セグメントに分割
2. 各セグメントを複数画質でエンコード
3. m3u8マニフェストファイルでセグメント一覧を提供
4. クライアントが帯域幅に合ったセグメントを選択
10. Back-of-Envelope推定(すいてい)
10.1 よく使(つか)う数字(すうじ)
レイテンシ:
- L1キャッシュ参照: ~1ns
- L2キャッシュ参照: ~4ns
- メインメモリ参照: ~100ns
- SSDランダム読取: ~150us
- HDDシーク: ~10ms
- 同一DCラウンドトリップ: ~0.5ms
- 米国 - 欧州ラウンドトリップ: ~75ms
容量:
- 1文字 = 1 byte(ASCII)/ 2~4 bytes(UTF-8)
- 1 UUID = 36 bytes(文字列)/ 16 bytes(バイナリ)
- 1画像(中サイズ)= ~200KB
- 1動画(1分、720p)= ~50MB
日次換算:
- 1日 = 86,400秒 = ~100,000秒(簡易計算)
- 100万リクエスト/日 = ~12 QPS
- 1億リクエスト/日 = ~1,200 QPS
10.2 QPS計算例(けいさんれい)
Twitter規模の推定:
- DAU: 3億人
- 平均ツイート閲覧: 100回/日
- 読取QPS = 3億 * 100 / 86,400 = ~347,000 QPS
- ピークQPS = 347,000 * 3 = ~1,000,000 QPS
- 平均ツイート投稿: 2回/日
- 書込QPS = 3億 * 2 / 86,400 = ~6,944 QPS
ストレージ:
- ツイートサイズ: ~300 bytes(テキスト + メタデータ)
- 日次: 6億ツイート * 300B = ~180GB/日
- 5年: 180GB * 365 * 5 = ~328TB
- メディア含む: ~5倍 = ~1.6PB
11. よくある間違(まちが)いとヒント
11.1 避(さ)けるべき間違(まちが)い
1. 要件確認なしにいきなり設計開始
-> 必ず5分は質問に投資
2. 1つのコンポーネントだけに集中
-> 全体像を先に、面接官が求める部分を深堀り
3. 完璧な設計を追求
-> トレードオフを説明する方が重要
4. 数字なしの設計
-> QPS、ストレージ量、帯域幅を必ず計算
5. 一方的に話し続ける
-> 面接官と対話しフィードバックを受容
6. 過剰な技術導入
-> 問題に適した適切な技術選択
11.2 面接(めんせつ)のコツ
1. 構造化されたアプローチ:
- 4段階フレームワークを常に遵守
- 各ステップでの時間管理
2. トレードオフの説明:
- 「このアプローチのメリットはA、デメリットはBです。」
- 「代替案Cもありますが、今回の状況ではDがより適切です。」
3. 実例の引用:
- 「Netflixはこの問題をXアプローチで解決しました。」
- 「以前のプロジェクトで同様の問題を経験しました。」
4. ホワイトボードの活用:
- きれいなダイアグラム
- データフローの矢印
- 数値/メトリックの表示
5. 面接官のヒントを受容:
- 面接官の質問はヒント
- 「良い指摘です、その部分を改善すれば…」
12. クイズ
Q1. スケーラビリティ
水平スケーリングと垂直スケーリングの主な違いを3つ説明してください。
回答:
- コスト構造: 垂直スケーリングは高スペック機器でコストが急増しますが、水平スケーリングは低スペック機器を追加するため線形的なコスト増加です
- 障害分離: 垂直スケーリングは単一障害点(SPOF)ですが、水平スケーリングは1台のサーバー障害が全体に影響しません
- スケーリング限界: 垂直スケーリングには物理的限界がありますが、水平スケーリングは理論上無制限です
水平スケーリングのトレードオフは、データ一貫性管理、セッション管理、運用複雑度が増加することです。
Q2. CAP定理(ていり)
銀行システムはなぜCPを選択するのですか?APを選択するとどんな問題が発生しますか?
回答: 銀行システムがCPを選択する理由は、残高の一貫性が絶対的に重要だからです。APを選択すると、ネットワークパーティション発生時に2つのノードで同時に出金が可能になり、実際の残高より多い金額が出金される**二重支出(Double Spending)**問題が発生する可能性があります。これは金融損失に直結するため、可用性を一部犠牲にしても一貫性を保証する必要があります。
Q3. キャッシング
Cache Stampede(キャッシュスタンピード)とは何ですか?どのように防止しますか?
回答: Cache Stampedeは、人気キャッシュエントリのTTLが満了した時に、数千のリクエストが同時にデータベースに殺到する現象です。
防止方法:
- Mutex/分散ロック: 最初のリクエストのみDBから取得し、残りは待機
- 事前更新: TTL満了前にバックグラウンドで更新(TTLの80%時点)
- TTLランダム化: 同時に満了しないよう少しのランダム値を追加
- 外部更新: 専用ワーカーが定期的にキャッシュを更新
Q4. メッセージキュー
KafkaとRabbitMQはそれぞれどのような状況で選択すべきですか?
回答: Kafka選択:
- イベントストリーミング(ログ収集、リアルタイム分析)
- 高スループット必要(毎秒数百万メッセージ)
- メッセージ再処理必要(消費後も保持)
- イベントソーシングパターン
RabbitMQ選択:
- 従来型タスクキュー(メール送信、画像処理)
- 複雑なルーティング必要(Exchange, Routing Key)
- メッセージ優先度必要
- RPCパターン
- メッセージ単位ACK必要
核心的な違い:Kafkaは「イベントログ」、RabbitMQは「メッセージブローカー」です。
Q5. 実践設計(じっせんせっけい)
URL ShortenerでBase62エンコーディングを使用する理由とハッシュ衝突の処理方法は?
回答: Base62使用理由:
- URLセーフ文字のみ使用(0-9, a-z, A-Z)
- 62^7 = 約3.5兆個のユニークURL生成可能
- 短い文字列で大きな数値を表現可能
ハッシュ衝突処理:
- Auto-increment ID + Base62: 衝突なし(推奨)
- MD5/SHA256 + 先頭7文字: 衝突時に文字を追加
- 衝突検出 + リトライ: DBで重複チェック後、別のハッシュを試行
- Bloom Filter: 存在確認を高速化し、衝突時に再生成
実務では方法1(分散環境ではSnowflake ID使用)が最も信頼性があります。
参考資料(さんこうしりょう)
- Alex Xu, "System Design Interview - An Insider's Guide" (2020)
- Alex Xu, "System Design Interview Volume 2" (2022)
- Martin Kleppmann, "Designing Data-Intensive Applications" (2017)
- Google SRE Book - https://sre.google/sre-book/table-of-contents/
- AWS Well-Architected Framework - https://aws.amazon.com/architecture/well-architected/
- Meta Engineering Blog - https://engineering.fb.com/
- Netflix Tech Blog - https://netflixtechblog.com/
- Uber Engineering Blog - https://eng.uber.com/
- Cassandra vs MongoDB vs DynamoDB - https://db-engines.com/en/system/Amazon+DynamoDB%3BCassandra%3BMongoDB
- Kafka Documentation - https://kafka.apache.org/documentation/
- Redis Documentation - https://redis.io/documentation
- Consistent Hashing Paper - Karger et al. (1997)
- Dynamo Paper - Amazon (2007)
- MapReduce Paper - Google (2004)