10日で覚えるSystem DesignDay 9: 動画配信とファイルストレージの設計

Day 9: 動画配信とファイルストレージの設計

今日学ぶこと

  • 動画アップロードパイプライン
  • トランスコーディングとエンコーディング
  • アダプティブビットレートストリーミング(HLS/DASH)
  • CDNによる動画配信
  • サムネイル生成
  • ファイルチャンキングと重複排除
  • 同期コンフリクト解決
  • ブロックストレージとオブジェクトストレージの比較

Part 1: 動画配信システム(YouTube型)

全体アーキテクチャ

flowchart TB
    Creator["動画クリエイター"]
    Viewer["視聴者"]

    subgraph Upload["アップロードパイプライン"]
        UpAPI["Upload API"]
        Store["Original Storage\n(S3)"]
        Queue["Transcoding Queue\n(Kafka)"]
        Trans["Transcoding Service"]
        Thumb["Thumbnail Generator"]
    end

    subgraph Delivery["配信システム"]
        Meta["Metadata Service"]
        CDN["CDN\n(CloudFront)"]
        Search["Search Service\n(Elasticsearch)"]
    end

    subgraph Storage["ストレージ"]
        MetaDB["Metadata DB\n(MySQL)"]
        VideoStore["Transcoded Videos\n(S3)"]
    end

    Creator --> UpAPI --> Store --> Queue
    Queue --> Trans --> VideoStore
    Queue --> Thumb --> VideoStore
    Trans --> MetaDB
    Viewer --> CDN
    CDN --> VideoStore
    Viewer --> Meta --> MetaDB
    Viewer --> Search

    style Upload fill:#3b82f6,color:#fff
    style Delivery fill:#22c55e,color:#fff
    style Storage fill:#8b5cf6,color:#fff

動画アップロードパイプライン

flowchart LR
    Upload["1. アップロード"]
    Validate["2. バリデーション"]
    Store["3. 原本保存\n(S3)"]
    Queue["4. キューイング"]
    Transcode["5. トランスコーディング"]
    Thumb["6. サムネイル\n生成"]
    Ready["7. 公開準備完了"]

    Upload --> Validate --> Store --> Queue
    Queue --> Transcode --> Ready
    Queue --> Thumb --> Ready

    style Upload fill:#3b82f6,color:#fff
    style Transcode fill:#f59e0b,color:#fff
    style Ready fill:#22c55e,color:#fff

アップロードの最適化:

手法 説明
分割アップロード 大きなファイルをチャンクに分割して並列送信
レジューム 中断時に途中から再開(Content-Range ヘッダー)
プリサインURL S3への直接アップロード(APIサーバー経由不要)
クライアント側圧縮 アップロード前に軽量圧縮
sequenceDiagram
    participant C as クライアント
    participant API as API Server
    participant S3 as S3

    C->>API: アップロード開始リクエスト
    API->>S3: Pre-signed URLを生成
    API->>C: Pre-signed URL返却
    C->>S3: 動画を直接アップロード(チャンク分割)
    S3->>API: アップロード完了通知(S3 Event)
    API->>API: トランスコーディングキューに追加

動画トランスコーディング

異なるデバイスやネットワーク速度に対応するため、複数の解像度・フォーマットに変換します。

解像度 ビットレート 用途
360p 0.5 Mbps 低速回線、モバイル
480p 1 Mbps 標準画質
720p 2.5 Mbps HD
1080p 5 Mbps Full HD
4K 15 Mbps Ultra HD
flowchart TB
    Original["原本動画\n(4K, 10GB)"]

    subgraph Transcode["トランスコーディング(並列処理)"]
        T1["360p (50MB)"]
        T2["480p (100MB)"]
        T3["720p (250MB)"]
        T4["1080p (500MB)"]
        T5["4K (2GB)"]
    end

    subgraph Audio["音声処理"]
        A1["AAC 128kbps"]
        A2["AAC 256kbps"]
    end

    Original --> Transcode
    Original --> Audio

    style Transcode fill:#f59e0b,color:#fff
    style Audio fill:#8b5cf6,color:#fff

トランスコーディングの実装:

  • FFmpeg: オープンソースの動画変換ツール
  • 並列処理: 各解像度を別々のワーカーで同時に処理
  • DAG(有向非巡回グラフ)パイプライン: 依存関係に基づいて処理順序を管理

アダプティブビットレートストリーミング

ネットワーク状況に応じて自動的に画質を切り替えます。

sequenceDiagram
    participant P as プレイヤー
    participant CDN as CDN

    P->>CDN: マニフェストファイルを要求
    CDN->>P: .m3u8 (HLS) or .mpd (DASH)
    Note over P: ネットワーク速度を測定

    loop セグメントごとに
        alt 高速回線
            P->>CDN: 1080pセグメントを要求
        else 回線低下
            P->>CDN: 480pセグメントに切り替え
        else 回線回復
            P->>CDN: 720pセグメントに切り替え
        end
    end
プロトコル 開発元 特徴
HLS (HTTP Live Streaming) Apple 最も広くサポート、.m3u8マニフェスト
DASH (Dynamic Adaptive Streaming over HTTP) MPEG オープン標準、.mpdマニフェスト

仕組み:

  1. 動画を数秒のセグメントに分割
  2. 各セグメントを複数の解像度で用意
  3. プレイヤーがネットワーク帯域を監視
  4. 次のセグメントを適切な解像度で取得

CDNによる動画配信

flowchart TB
    Origin["Origin Server\n(S3)"]

    subgraph CDN["CDN(グローバル)"]
        Edge1["Edge Server\n東京"]
        Edge2["Edge Server\nニューヨーク"]
        Edge3["Edge Server\nロンドン"]
    end

    User1["日本のユーザー"]
    User2["米国のユーザー"]
    User3["欧州のユーザー"]

    Origin -->|"コンテンツ配布"| Edge1
    Origin -->|"コンテンツ配布"| Edge2
    Origin -->|"コンテンツ配布"| Edge3

    User1 -->|"低レイテンシ"| Edge1
    User2 -->|"低レイテンシ"| Edge2
    User3 -->|"低レイテンシ"| Edge3

    style CDN fill:#22c55e,color:#fff
    style Origin fill:#8b5cf6,color:#fff

CDN最適化:

手法 説明
Push型 人気動画を事前にエッジにプッシュ
Pull型 初回アクセス時にオリジンから取得しキャッシュ
プリフェッチ 次のセグメントを先読みして配信
地理的ルーティング ユーザーに最も近いエッジサーバーにルーティング

サムネイル生成

flowchart LR
    Video["原本動画"]
    Extract["フレーム抽出\n(複数の時点)"]
    Resize["リサイズ\n(複数サイズ)"]
    Select["最適フレーム選択\n(ML/Manual)"]
    Store2["S3 + CDN"]

    Video --> Extract --> Resize --> Select --> Store2

    style Extract fill:#f59e0b,color:#fff
    style Select fill:#8b5cf6,color:#fff
  • 動画の複数時点からフレームを抽出
  • 複数サイズ(小、中、大)を生成
  • MLモデルで「良い」サムネイルを自動選択(オプション)

Part 2: 分散ファイルストレージ(Google Drive / Dropbox型)

全体アーキテクチャ

flowchart TB
    Client["デスクトップクライアント"]
    Web["Webブラウザ"]
    Mobile["モバイルアプリ"]

    subgraph API["APIレイヤー"]
        FileAPI["File API"]
        SyncAPI["Sync API"]
        ShareAPI["Share API"]
    end

    subgraph Core["コアサービス"]
        Meta["Metadata Service"]
        Chunk["Chunking Service"]
        Sync["Sync Service"]
        Notify["Notification Service"]
    end

    subgraph Storage["ストレージ"]
        MetaDB2["Metadata DB\n(PostgreSQL)"]
        BlockStore["Block Storage\n(S3)"]
        MQ2["Message Queue\n(Kafka)"]
    end

    Client --> API
    Web --> API
    Mobile --> API
    API --> Core
    Meta --> MetaDB2
    Chunk --> BlockStore
    Sync --> MQ2
    Notify --> MQ2

    style API fill:#3b82f6,color:#fff
    style Core fill:#22c55e,color:#fff
    style Storage fill:#8b5cf6,color:#fff

ファイルチャンキングと重複排除

大きなファイルをブロック(チャンク)に分割し、ブロック単位で管理します。

flowchart TB
    File["ファイル (100MB)"]

    subgraph Chunking["チャンキング"]
        C1["Block 1\n(4MB)\nhash: abc123"]
        C2["Block 2\n(4MB)\nhash: def456"]
        C3["Block 3\n(4MB)\nhash: abc123"]
        C4["Block N\n..."]
    end

    subgraph Dedup["重複排除"]
        Store3["Block abc123\n(1コピーのみ保存)"]
        Store4["Block def456"]
    end

    File --> Chunking
    C1 --> Store3
    C2 --> Store4
    C3 -->|"同じハッシュ\n→ 保存不要"| Store3

    style Chunking fill:#f59e0b,color:#fff
    style Dedup fill:#22c55e,color:#fff

チャンキングのメリット:

メリット 説明
差分同期 変更されたブロックのみをアップロード
重複排除 同じ内容のブロックは1つだけ保存
並列転送 複数ブロックを同時にアップロード/ダウンロード
レジューム 中断時に未完了のブロックから再開
帯域削減 変更箇所のみ転送

同期コンフリクト

複数のデバイスから同じファイルを同時に編集した場合の解決策。

sequenceDiagram
    participant A as デバイスA
    participant S as サーバー
    participant B as デバイスB

    Note over A,B: ファイルver.1が存在
    A->>S: ver.1を編集 → ver.2Aとして保存
    B->>S: ver.1を編集 → ver.2B(コンフリクト!)
    S->>S: コンフリクト検出
    S->>B: コンフリクト通知
    Note over B: ユーザーが選択:<br/>1. Aの変更を採用<br/>2. Bの変更を採用<br/>3. 両方を保持(コピー作成)

コンフリクト解決戦略:

戦略 説明 使用例
Last Write Wins 最後の書き込みが勝つ シンプルだがデータ損失の可能性
ユーザーに委任 コンフリクトを通知し選択させる Dropbox
自動マージ 変更を自動的にマージ Google Docs(OT/CRDT)
バージョニング 全バージョンを保持 Git

メタデータサービス

-- File metadata schema
CREATE TABLE files (
    id          UUID PRIMARY KEY,
    user_id     UUID NOT NULL,
    file_name   VARCHAR(255) NOT NULL,
    file_size   BIGINT NOT NULL,
    mime_type   VARCHAR(100),
    parent_id   UUID REFERENCES files(id),  -- folder hierarchy
    is_folder   BOOLEAN DEFAULT FALSE,
    version     INT DEFAULT 1,
    created_at  TIMESTAMP DEFAULT NOW(),
    updated_at  TIMESTAMP DEFAULT NOW(),
    deleted_at  TIMESTAMP  -- soft delete
);

-- Block mapping
CREATE TABLE file_blocks (
    file_id     UUID REFERENCES files(id),
    block_order INT,
    block_hash  VARCHAR(64),  -- SHA-256 hash
    block_size  INT,
    PRIMARY KEY (file_id, block_order)
);

-- Block storage reference
CREATE TABLE blocks (
    block_hash  VARCHAR(64) PRIMARY KEY,
    storage_url TEXT NOT NULL,
    ref_count   INT DEFAULT 1  -- reference counting for dedup
);

ブロックストレージ vs オブジェクトストレージ

特徴 ブロックストレージ オブジェクトストレージ
アクセス単位 固定サイズブロック オブジェクト全体
パフォーマンス 高速(低レイテンシ) 中速(スループット重視)
スケーラビリティ 限定的 無制限に近い
コスト 高い 安い
適用 データベース、OS ファイル、メディア、バックアップ
EBS, SAN S3, GCS, Azure Blob
flowchart LR
    subgraph Block["ブロックストレージ"]
        direction TB
        B1["Block 1"]
        B2["Block 2"]
        B3["Block 3"]
        B1 --- B2 --- B3
    end
    subgraph Object["オブジェクトストレージ"]
        direction TB
        O1["Object A\n(メタデータ + データ)"]
        O2["Object B\n(メタデータ + データ)"]
        O3["Object C\n(メタデータ + データ)"]
    end
    style Block fill:#f59e0b,color:#fff
    style Object fill:#8b5cf6,color:#fff

ファイルストレージサービスでは: メタデータDBにはブロックストレージ(EBS)上のPostgreSQLを使い、ファイル本体のチャンクはオブジェクトストレージ(S3)に保存するのが一般的です。


同期の仕組み

flowchart TB
    subgraph Client2["デスクトップクライアント"]
        Watcher["File Watcher"]
        LocalDB["Local DB\n(SQLite)"]
        SyncEngine["Sync Engine"]
    end

    subgraph Server["サーバー"]
        SyncAPI2["Sync API"]
        NotifySvc["Notification\n(WebSocket)"]
        MetaSvc["Metadata Service"]
    end

    Watcher -->|"ファイル変更検出"| SyncEngine
    SyncEngine -->|"差分アップロード"| SyncAPI2
    SyncEngine <--> LocalDB
    NotifySvc -->|"他デバイスからの\n変更通知"| SyncEngine
    SyncAPI2 <--> MetaSvc

    style Client2 fill:#3b82f6,color:#fff
    style Server fill:#22c55e,color:#fff

同期フロー:

  1. File Watcherがローカルファイルの変更を検出
  2. 変更されたブロックのみを特定(ハッシュ比較)
  3. 新しいブロックをS3にアップロード
  4. メタデータを更新
  5. 他のデバイスにWebSocketで変更を通知
  6. 他のデバイスが差分をダウンロード

まとめ

今日のポイント一覧

トピック 重要ポイント
動画アップロード Pre-signed URLでS3に直接アップロード
トランスコーディング 並列処理で複数解像度に変換
アダプティブストリーミング HLS/DASHでネットワーク状況に適応
CDN エッジサーバーで低レイテンシ配信
ファイルチャンキング ブロック単位で差分同期と重複排除
同期コンフリクト バージョン管理 + ユーザーに委任が安全
ストレージ選択 メタデータはRDB、ファイル本体はオブジェクトストレージ

面接で使えるキーフレーズ

  • 「動画はPre-signed URLでS3に直接アップロードし、APIサーバーの負荷を軽減します」
  • 「HLSでアダプティブビットレートストリーミングを実現し、ネットワーク品質に応じて画質を動的に調整します」
  • 「ファイルをチャンク分割し、ハッシュで重複排除することでストレージコストと転送量を最小化します」

練習問題

基礎レベル

  1. HLSとDASHの違いを説明し、どちらを選ぶかその理由を述べてください
  2. ファイルチャンキングのメリットを5つ挙げてください

中級レベル

  1. 1日100万本の動画アップロードを処理するトランスコーディングパイプラインを設計してください。処理の優先順位付け(有料ユーザー優先等)も考慮してください
  2. 3台のデバイスが同時にオフラインで同じファイルを編集した場合の同期フローを設計してください

チャレンジ

  1. 画像ホスティングサービス(Imgur型)を設計してください。画像アップロード、リサイズ(複数サイズ自動生成)、CDN配信、NSFW検出、帯域制限を含めてください。1日1000万枚のアップロードを処理できるスケーラビリティを考慮してください

参考リンク


次回予告

Day 10: ECサイトとレートリミッターの設計 — 最終日は、ECプラットフォームの設計(在庫管理、決済、分散トランザクション)とレートリミッターの詳細設計に取り組みます。そして10日間の総まとめです!