Skip to content
Published on

システムデザイン面接完全ガイド2025:大規模システム設計、スケーラビリティ、可用性パターン

Authors

はじめに

システムデザイン面接(めんせつ)は、シニアエンジニア採用(さいよう)において最(もっと)も重要(じゅうよう)な評価項目(ひょうかこうもく)です。アルゴリズム問題(もんだい)がコーディング能力(のうりょく)を測定(そくてい)するのに対(たい)し、システムデザインは大規模(だいきぼ)システムを設計(せっけい)しトレードオフを理解(りかい)する総合的(そうごうてき)なエンジニアリング能力(のうりょく)を評価(ひょうか)します。

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時間ベース予測可能なパターン
PredictiveMLベース繰り返しパターン

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終端、より柔軟
特性L4L7
レイヤーTCP/UDPHTTP/HTTPS
パフォーマンス非常に高速相対的に遅い
ルーティングIP/PortURL, Header, Cookie
SSL終端不可可能
使用例AWS NLBAWS 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!')
特性RedisMemcached
データ構造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, MySQLMongoDB, 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残高不整合は不可
DNSAP少し古いレコード許容
MongoDBCP強い一貫性がデフォルト
CassandraAP結果整合性(Eventual)
ZooKeeperCP分散コーディネーション

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消費者)|
                  +------------------------+
特性KafkaRabbitMQSQS
モデルログベースストリーミングAMQPメッセージブローカーマネージドキュー
スループット毎秒数百万メッセージ毎秒数万メッセージ自動スケーリング
メッセージ保持設定期間保持消費後削除14日
順序保証パーティション内保証キュー単位保証FIFOキュー使用時
消費モデルPull(Consumer Poll)Push + PullPull(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 BucketO(1)高い低い
Leaky BucketO(1)高い低い
Sliding Window LogO(N)非常に高い中程度
Sliding Window CounterO(1)近似値中程度
Fixed WindowO(1)低い非常に低い

9. 実践(じっせん)設計(せっけい)例(れい)

9.1 URL Shortener設計(せっけい)

要件(ようけん):

  • 1億DAU、読取:書込 = 100:1
  • 短縮(たんしゅく)URLはできるだけ短(みじか)く(7文字(もじ))
  • カスタムURLサポート
  • リダイレクションレイテンシ100ms以下(いか)

Back-of-Envelope計算(けいさん):

書込: 1DAU * 平均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    |
+---------------+----------+----------+--------------+
-> 時間順ソート可能 + グローバルユニーク
-> 毎秒約400ID生成可能

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セグメントに切替
帯域幅回復   -> 段階的に画質を上げる

HLSHTTP 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つ説明してください。

回答:

  1. コスト構造: 垂直スケーリングは高スペック機器でコストが急増しますが、水平スケーリングは低スペック機器を追加するため線形的なコスト増加です
  2. 障害分離: 垂直スケーリングは単一障害点(SPOF)ですが、水平スケーリングは1台のサーバー障害が全体に影響しません
  3. スケーリング限界: 垂直スケーリングには物理的限界がありますが、水平スケーリングは理論上無制限です

水平スケーリングのトレードオフは、データ一貫性管理、セッション管理、運用複雑度が増加することです。

Q2. CAP定理(ていり)

銀行システムはなぜCPを選択するのですか?APを選択するとどんな問題が発生しますか?

回答: 銀行システムがCPを選択する理由は、残高の一貫性が絶対的に重要だからです。APを選択すると、ネットワークパーティション発生時に2つのノードで同時に出金が可能になり、実際の残高より多い金額が出金される**二重支出(Double Spending)**問題が発生する可能性があります。これは金融損失に直結するため、可用性を一部犠牲にしても一貫性を保証する必要があります。

Q3. キャッシング

Cache Stampede(キャッシュスタンピード)とは何ですか?どのように防止しますか?

回答: Cache Stampedeは、人気キャッシュエントリのTTLが満了した時に、数千のリクエストが同時にデータベースに殺到する現象です。

防止方法:

  1. Mutex/分散ロック: 最初のリクエストのみDBから取得し、残りは待機
  2. 事前更新: TTL満了前にバックグラウンドで更新(TTLの80%時点)
  3. TTLランダム化: 同時に満了しないよう少しのランダム値を追加
  4. 外部更新: 専用ワーカーが定期的にキャッシュを更新

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生成可能
  • 短い文字列で大きな数値を表現可能

ハッシュ衝突処理:

  1. Auto-increment ID + Base62: 衝突なし(推奨)
  2. MD5/SHA256 + 先頭7文字: 衝突時に文字を追加
  3. 衝突検出 + リトライ: DBで重複チェック後、別のハッシュを試行
  4. Bloom Filter: 存在確認を高速化し、衝突時に再生成

実務では方法1(分散環境ではSnowflake ID使用)が最も信頼性があります。


参考資料(さんこうしりょう)

  1. Alex Xu, "System Design Interview - An Insider's Guide" (2020)
  2. Alex Xu, "System Design Interview Volume 2" (2022)
  3. Martin Kleppmann, "Designing Data-Intensive Applications" (2017)
  4. Google SRE Book - https://sre.google/sre-book/table-of-contents/
  5. AWS Well-Architected Framework - https://aws.amazon.com/architecture/well-architected/
  6. Meta Engineering Blog - https://engineering.fb.com/
  7. Netflix Tech Blog - https://netflixtechblog.com/
  8. Uber Engineering Blog - https://eng.uber.com/
  9. Cassandra vs MongoDB vs DynamoDB - https://db-engines.com/en/system/Amazon+DynamoDB%3BCassandra%3BMongoDB
  10. Kafka Documentation - https://kafka.apache.org/documentation/
  11. Redis Documentation - https://redis.io/documentation
  12. Consistent Hashing Paper - Karger et al. (1997)
  13. Dynamo Paper - Amazon (2007)
  14. MapReduce Paper - Google (2004)