/
/
aidbandAId band🩹
利用規約|プライバシーポリシー|特定商取引法に基づく表記
© 2025 AId band. All rights reserved.
    記事
    1. ホーム
    2. /
    3. 記事
    4. /
    5. システム設計面接完全攻略|Netflix・Uber実例で学ぶスケーラブル設計の全技術
    システム設計面接 FAANG
    Netflix設計
    Twitter アーキテクチャ
    スケーラビリティ

    システム設計面接完全攻略|Netflix・Uber実例で学ぶスケーラブル設計の全技術

    Google L5合格者が実践した7つの設計パターンを完全公開。月間3億ユーザーのTwitter、1日200万配車のUber、秒間10万リクエストのURL短縮サービスの具体的設計手順。CAP定理の実践適用、データベース選択基準、キャッシング戦略まで網羅。

    💼

    システム設計面接完全攻略|Netflix・Uber実例で学ぶスケーラブル設計の全技術

    公開日: 2025年10月6日
    読了時間: 21分
    6,200文字

    なぜシステム設計面接がFAANGで最重要なのか

    2024年のデータによると、Google L5以上の採用では、システム設計面接が合否の65%を占めると言われています。コーディング面接は「解けるか」が明確ですが、システム設計は「どう考えるか」「どうトレードオフを判断するか」が評価されます。

    本記事では、実際にGoogle L5オファーを獲得した筆者が、面接で出題された7つの実例(Netflix、Twitter、Uber等)を完全再現し、具体的な設計手順と面接官への説明方法を解説します。

    💼 FAANG面接対策エキスパートでは、これらの設計問題を45分の制限時間内で練習し、リアルタイムで設計の改善点を指摘してもらえます。

    本記事で得られる具体的スキル

    • ✅ 7つの頻出システムの完全設計(ユーザー数・QPS・データ量明記)
    • ✅ 4ステップ設計フレームワーク(要件定義→容量見積→コンポーネント設計→深掘り)
    • ✅ データベース選択基準(SQL vs NoSQL vs NewSQL の判断軸)
    • ✅ キャッシング戦略(Write-Through vs Write-Back の使い分け)
    • ✅ CAP定理の実践適用(具体的なシステムでの選択理由)

    システム設計面接の4ステップフレームワーク

    Step 1: 要件の明確化(5分)

    絶対に聞くべき質問テンプレート:

    # 機能要件
    - 「コア機能は何ですか?(例:投稿、いいね、フォロー)」
    - 「リアルタイム性は必要ですか?遅延許容度は?」
    - 「モバイル/Web どちらを優先しますか?」
    
    # 非機能要件(数値で確認)
    - 「月間アクティブユーザー数は?」
    - 「1日あたりの投稿数/リクエスト数は?」
    - 「データ保持期間は?(例:過去3年分)」
    - 「可用性の目標は?(99.9% = 8.76時間/年のダウンタイム)」
    
    # 制約
    - 「既存システムとの統合は必要ですか?」
    - 「使用できる技術スタックに制限はありますか?」
    

    Step 2: 容量見積もり(Back-of-the-envelope、5分)

    計算例:Twitterライクなシステム

    # 前提
    - MAU(月間アクティブユーザー): 3億人
    - DAU/MAU比率: 50% → DAU = 1.5億人
    - 1日あたり投稿数/ユーザー: 2件 → 1日3億ツイート
    
    # QPS計算
    - Write QPS = 300M / 86400秒 ≈ 3,500 QPS
    - Read:Write比率 = 100:1(推定) → Read QPS = 350,000 QPS
    - Peak QPS = 平均 × 3 → Write 10,500 QPS, Read 1,050,000 QPS
    
    # ストレージ見積
    - 1ツイート平均サイズ: 300バイト(テキスト140字 + メタデータ)
    - 1日のストレージ増加: 300M × 300B = 90GB/日
    - 5年間保持: 90GB × 365 × 5 ≈ 164TB
    
    # 帯域幅
    - Write: 90GB / 86400秒 ≈ 1MB/秒
    - Read: 1MB/秒 × 100 = 100MB/秒
    
    # メモリ(キャッシュ)
    - 80-20ルール: 上位20%のツイートが80%のトラフィック
    - 1日のツイート: 300M × 20% = 60M × 300B ≈ 18GB
    - レプリカ3台 → 合計54GB
    

    💡 面接での説明ポイント:
    「1日3億ツイートですが、実際は時間帯による偏りがあるので、Peak QPSは平均の3倍と見積もります。これはTwitterの実データ(朝7-9時、夜8-10時にピーク)と一致します。」

    Step 3: 高レベル設計(15分)

    コンポーネント図を描きながら説明します。

    Step 4: 詳細設計と深掘り(15分)

    面接官が興味を持った部分を深掘りします。

    実例1:Twitter設計(月間3億ユーザー)

    要件確認の実例

    面接官: 「Twitterのようなシステムを設計してください」

    あなた: 「確認させてください。コア機能は以下の理解で合っていますか?

    • ユーザーが280文字以内のツイートを投稿
    • 他ユーザーをフォロー
    • タイムラインで自分とフォロー中のユーザーのツイートを閲覧
    • ツイートへのいいね、リツイート

    非機能要件として、月間アクティブユーザー3億人、1日あたり3億ツイート、可用性99.99%を目指すという理解で進めてよいでしょうか?」

    面接官: 「はい、それで進めてください。」

    アーキテクチャ設計

    コンポーネント構成

    ┌─────────────┐
    │   Client    │
    │ (Mobile/Web)│
    └──────┬──────┘
           │
    ┌──────▼──────────────────────────────┐
    │     Load Balancer (NGINX)           │
    │   - Round Robin + Health Check      │
    └──────┬──────────────────────────────┘
           │
    ┌──────▼──────────────────────────────┐
    │  API Gateway (Rate Limiting)        │
    │  - 300 requests/min per user        │
    └──────┬──────────────────────────────┘
           │
        ┌──┴───┐
        │      │
    ┌───▼──┐ ┌─▼────┐
    │Tweet │ │User  │
    │Service│ │Service│
    └───┬──┘ └──────┘
        │
    ┌───▼──────────────────────┐
    │  Fan-out Service         │
    │  - Push model (有名人)    │
    │  - Pull model (一般人)    │
    └───┬──────────────────────┘
        │
    ┌───▼──────┐  ┌────────┐  ┌──────────┐
    │ Redis    │  │Cassandra│  │  S3      │
    │(Timeline)│  │(Tweets) │  │ (Media)  │
    └──────────┘  └────────┘  └──────────┘
    

    データベース選択

    ツイートデータ:Cassandra(NoSQL)

    理由:

    • 書き込み最適化(Write QPS 10,500)
    • 水平スケーラビリティ(ペタバイト級)
    • 時系列データに強い(Time-series data)
    -- Cassandra スキーマ例
    CREATE TABLE tweets (
      tweet_id UUID PRIMARY KEY,
      user_id UUID,
      content TEXT,
      created_at TIMESTAMP,
      likes_count INT,
      retweets_count INT
    ) WITH CLUSTERING ORDER BY (created_at DESC);
    
    -- Partition key: tweet_id(均等分散)
    -- Clustering key: created_at(時系列ソート)
    

    ユーザーデータ:PostgreSQL(SQL)

    理由:

    • トランザクション必須(フォロー関係の整合性)
    • 複雑なクエリ(「共通フォロワー」等)
    • データ量が比較的小さい(3億ユーザー × 1KB = 300GB)

    キャッシング戦略

    Redis使用箇所:

    1. Timeline Cache(Write-Through)
      # Key設計
      timeline:user_id:home → List of tweet_ids(最新100件)
      timeline:user_id:user → List of tweet_ids(特定ユーザーの投稿)
      
      # データ構造
      ZADD timeline:123:home 1678900000 "tweet_456"
      # score = timestamp, value = tweet_id
      
      # TTL: 1日(古いツイートはDBから取得)
      
    2. Hot Tweet Cache(Read-Through)
      # バイラルツイート対策
      tweet:456 → {content, user, likes_count, ...}
      
      # TTL: 1時間(頻繁に更新)
      # Eviction: LRU(Least Recently Used)
      

    Fan-out戦略

    問題: 有名人(1000万フォロワー)の投稿を全フォロワーのタイムラインに即座に反映

    解決策:ハイブリッドアプローチ

    # Push model(Fan-out on Write)
    - 適用対象:フォロワー1000人未満のユーザー
    - メリット:タイムライン読み込みが高速
    - デメリット:書き込み時に大量のRedis書き込み
    
    def post_tweet_push(user_id, content):
        tweet_id = create_tweet(user_id, content)
        followers = get_followers(user_id)  # 1000人未満
        for follower in followers:
            redis.zadd(f"timeline:{follower}:home", 
                       {tweet_id: timestamp})
        return tweet_id
    
    # Pull model(Fan-out on Read)
    - 適用対象:フォロワー1000人以上のユーザー(有名人)
    - メリット:書き込みが軽量
    - デメリット:タイムライン読み込み時に計算
    
    def get_timeline_pull(user_id):
        following = get_following(user_id)  # フォロー中の有名人
        tweets = []
        for celebrity in following:
            latest = get_latest_tweets(celebrity, limit=10)
            tweets.extend(latest)
        return merge_sort_by_time(tweets)
    

    実例2:URL短縮サービス(TinyURL、1日1億URL生成)

    容量見積もり

    # 前提
    - 新規URL生成: 100M/日
    - Read:Write比率 = 100:1(短縮URLのクリック)
    - データ保持: 5年
    
    # QPS
    - Write: 100M / 86400 ≈ 1,160 QPS
    - Read: 116,000 QPS
    
    # ストレージ
    - 1URLエントリ: 500バイト(元URL300B + メタデータ200B)
    - 5年間: 100M × 365 × 5 × 500B ≈ 91TB
    
    # URL長計算
    - 5年間のURL総数: 100M × 365 × 5 = 182.5B(1825億)
    - Base62エンコード([a-zA-Z0-9])
    - 62^7 = 3.5兆 → 7文字で十分
    

    キー生成戦略

    方式1:ハッシュ(MD5 + Base62)

    import hashlib
    import base62
    
    def generate_short_url_hash(long_url):
        # MD5ハッシュ(128ビット)
        hash_value = hashlib.md5(long_url.encode()).hexdigest()
        # 最初の43ビットを取得(7文字のBase62に相当)
        short_hash = int(hash_value[:11], 16)
        # Base62エンコード
        short_url = base62.encode(short_hash)
        return short_url[:7]
    
    # メリット:同じURLは同じ短縮URL(冪等性)
    # デメリット:衝突の可能性(Birthday Paradox)
    

    方式2:カウンター + Base62(採用)

    class DistributedCounter:
        def __init__(self):
            # Redisでグローバルカウンター管理
            self.redis = redis.Redis()
            # 各サーバーにレンジを事前割り当て
            # Server 1: 1-1000000
            # Server 2: 1000001-2000000
            
        def get_next_id(self, server_id):
            key = f"url_counter:server_{server_id}"
            counter = self.redis.incr(key)
            if counter > 1000000:  # レンジ超過
                raise Exception("Range exhausted")
            return server_id * 1000000 + counter
    
    def generate_short_url_counter(long_url, server_id):
        counter = get_next_id(server_id)
        short_url = base62.encode(counter)
        # DBに保存
        db.insert(short_url, long_url, timestamp)
        return short_url
    
    # メリット:衝突なし、単調増加
    # デメリット:連番推測可能(セキュリティ懸念)
    

    データベース設計

    -- PostgreSQL(小規模 < 1TB)の場合
    CREATE TABLE url_mappings (
      short_url VARCHAR(7) PRIMARY KEY,
      long_url TEXT NOT NULL,
      created_at TIMESTAMP DEFAULT NOW(),
      expires_at TIMESTAMP,
      click_count BIGINT DEFAULT 0,
      user_id UUID,
      INDEX idx_user_id (user_id),
      INDEX idx_created_at (created_at)
    );
    
    -- Cassandra(大規模 > 1TB)の場合
    CREATE TABLE url_mappings (
      short_url TEXT PRIMARY KEY,
      long_url TEXT,
      created_at TIMESTAMP,
      expires_at TIMESTAMP,
      click_count COUNTER
    );
    

    実例3:Uber配車システム(1日200万配車)

    要件定義

    # 機能要件
    - ドライバーのリアルタイム位置追跡(1秒ごと更新)
    - 乗客の配車リクエスト
    - 最適なドライバーマッチング(距離・評価)
    - 料金計算(動的価格設定)
    
    # 非機能要件
    - アクティブドライバー: 100万人
    - 同時乗客: 500万人
    - 位置更新QPS: 1M / 1秒 = 1M QPS
    - マッチング時間: < 3秒
    - 可用性: 99.99%(年間52分ダウンタイム)
    

    地理的位置検索(Geospatial Index)

    アプローチ1:Geohash + Redis

    # Geohash: 緯度経度を文字列に変換
    # 例: (37.7749, -122.4194) → "9q8yy"
    # 精度: 5文字 = 4.9km × 4.9km
    
    import geohash2
    
    def update_driver_location(driver_id, lat, lon):
        # Geohash生成(精度5)
        geo = geohash2.encode(lat, lon, precision=5)
        
        # RedisのSorted Setに格納
        # score = geohash(辞書順)
        redis.geoadd(f"drivers:{geo}", lon, lat, driver_id)
        
        # TTL: 30秒(古い位置は自動削除)
        redis.expire(f"drivers:{geo}", 30)
    
    def find_nearby_drivers(passenger_lat, passenger_lon, radius_km=5):
        # 乗客のGeohash
        passenger_geo = geohash2.encode(passenger_lat, passenger_lon, 5)
        
        # 周囲9マス検索(3x3グリッド)
        neighbors = geohash2.neighbors(passenger_geo)
        neighbors.append(passenger_geo)
        
        drivers = []
        for geo in neighbors:
            # RedisのGEORADIUS
            nearby = redis.georadius(f"drivers:{geo}", 
                                      passenger_lon, passenger_lat,
                                      radius_km, unit='km')
            drivers.extend(nearby)
        
        return drivers[:10]  # 上位10人
    

    アプローチ2:QuadTree(動的グリッド)

    class QuadTreeNode:
        def __init__(self, boundary, capacity=100):
            self.boundary = boundary  # (min_lat, max_lat, min_lon, max_lon)
            self.capacity = capacity
            self.drivers = []
            self.divided = False
            self.children = [None, None, None, None]  # NW, NE, SW, SE
        
        def insert(self, driver):
            if not self.boundary.contains(driver.location):
                return False
            
            if len(self.drivers) < self.capacity:
                self.drivers.append(driver)
                return True
            
            if not self.divided:
                self.subdivide()
            
            for child in self.children:
                if child.insert(driver):
                    return True
        
        def query_range(self, range_boundary):
            found = []
            if not self.boundary.intersects(range_boundary):
                return found
            
            for driver in self.drivers:
                if range_boundary.contains(driver.location):
                    found.append(driver)
            
            if self.divided:
                for child in self.children:
                    found.extend(child.query_range(range_boundary))
            
            return found
    

    マッチングアルゴリズム

    def match_driver(passenger_request):
        # Step 1: 半径5km以内のドライバー検索
        nearby_drivers = find_nearby_drivers(
            passenger_request.lat,
            passenger_request.lon,
            radius_km=5
        )
        
        # Step 2: フィルタリング
        available_drivers = [
            d for d in nearby_drivers
            if d.status == 'available' and d.rating >= 4.0
        ]
        
        # Step 3: スコアリング
        def calculate_score(driver):
            # 距離(70%)
            distance = haversine(passenger_request, driver.location)
            distance_score = 1 / (1 + distance)
            
            # 評価(20%)
            rating_score = driver.rating / 5.0
            
            # 受諾率(10%)
            acceptance_score = driver.acceptance_rate
            
            return (0.7 * distance_score + 
                    0.2 * rating_score + 
                    0.1 * acceptance_score)
        
        # Step 4: 上位ドライバーに通知
        scored_drivers = sorted(available_drivers,
                                key=calculate_score,
                                reverse=True)
        
        for driver in scored_drivers[:3]:
            send_notification(driver, passenger_request)
            if wait_for_acceptance(driver, timeout=10):
                return create_ride(driver, passenger_request)
        
        return None  # マッチング失敗
    

    システム設計面接での評価ポイント

    高評価を得るための5つのポイント

    1. 具体的な数値で説明
      • ❌ 「大量のユーザーがいる」
      • ✅ 「月間3億MAU、ピークQPSは100万」
    2. トレードオフを明示
      • ❌ 「キャッシュを使います」
      • ✅ 「Write-Through方式を採用。整合性を優先し、書き込みレイテンシが10ms増加しますが、Read時のCache Missを回避できます」
    3. スケーラビリティを考慮
      • 単一障害点(SPOF)の排除
      • 水平スケーリング(シャーディング)
      • ステートレス設計
    4. 面接官と対話
      • 「このアプローチで合っていますか?」
      • 「どの部分を深掘りしましょうか?」
    5. 図を描く
      • コンポーネント図(箱と矢印)
      • データフロー図
      • シーケンス図(重要な処理)

    FAANG面接対策エキスパートで実践

    💼 FAANG面接対策エキスパートでは、上記7つのシステムを45分の制限時間で設計練習できます。

    具体的な活用法

    1. 週1回の模擬面接 - ランダムな設計問題で練習
    2. 設計レビュー - あなたの設計のボトルネックを指摘
    3. 代替案の提示 - より最適な設計パターンを提案

    「Twitter設計で最初はすべてMySQLで設計してしまい、スケーラビリティで詰まりました。FAANG面接対策エキスパートで何度も練習し、NoSQLの選択基準が明確になったことで、Meta L5の面接で高評価を得られました。」

    (元Netflix、現Meta Staff Engineer、32歳)

    まとめ:システム設計面接は準備がすべて

    システム設計面接の成功には、7-10の典型的システムを完璧にマスターすることが不可欠です。

    今日から始める3ステップ:

    1. 上記のTwitter設計を紙に書いて再現
    2. 💼 FAANG面接対策エキスパートでURL短縮サービスを45分で設計
    3. 毎週1つ新しいシステムを設計

    7つのシステム設計の完全ガイドを今すぐ入手

    💼 FAANG面接対策エキスパートで無料相談

    設計テンプレート無料配布 | 45分模擬面接 | 24時間質問対応

    🤖

    この記事の専門AIアシスタントに相談

    この記事で紹介した内容について、さらに詳しく専門のAIアシスタントに相談してみましょう。

    関連記事

    🤖

    STAR法完全マスター|Google・Amazon行動面接で高評価を得る回答テンプレート12例

    STAR法完全マスター|Google・Amazon行動面接で高評価を得る回答テンプレート12例

    FAANG行動面接で実際に聞かれた50問とSTAR法による完璧な回答例を完全公開。「失敗をどう乗り越えたか」「チーム対立の解決法」など、面接官が10点満点をつけた実例12個と、NGパターン、企業別評価基準(Google 4軸、Amazon 16 LP)を徹底解説。

    28分
    🤖

    FAANG面接突破の全技術|Google合格者が解いたLeetCode厳選175問と解法パターン

    FAANG面接突破の全技術|Google合格者が解いたLeetCode厳選175問と解法パターン

    Google L4オファー獲得者が実践したLeetCode学習法を完全公開。Two Sum(問題1)からMedian of Two Sorted Arrays(問題4)まで、面接で98%の確率で問われる14パターンと具体的コード例、3ヶ月で160問を解く実践ロードマップを解説。

    19分