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マニフェスト |
仕組み:
- 動画を数秒のセグメントに分割
- 各セグメントを複数の解像度で用意
- プレイヤーがネットワーク帯域を監視
- 次のセグメントを適切な解像度で取得
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
同期フロー:
- File Watcherがローカルファイルの変更を検出
- 変更されたブロックのみを特定(ハッシュ比較)
- 新しいブロックをS3にアップロード
- メタデータを更新
- 他のデバイスにWebSocketで変更を通知
- 他のデバイスが差分をダウンロード
まとめ
今日のポイント一覧
| トピック | 重要ポイント |
|---|---|
| 動画アップロード | Pre-signed URLでS3に直接アップロード |
| トランスコーディング | 並列処理で複数解像度に変換 |
| アダプティブストリーミング | HLS/DASHでネットワーク状況に適応 |
| CDN | エッジサーバーで低レイテンシ配信 |
| ファイルチャンキング | ブロック単位で差分同期と重複排除 |
| 同期コンフリクト | バージョン管理 + ユーザーに委任が安全 |
| ストレージ選択 | メタデータはRDB、ファイル本体はオブジェクトストレージ |
面接で使えるキーフレーズ
- 「動画はPre-signed URLでS3に直接アップロードし、APIサーバーの負荷を軽減します」
- 「HLSでアダプティブビットレートストリーミングを実現し、ネットワーク品質に応じて画質を動的に調整します」
- 「ファイルをチャンク分割し、ハッシュで重複排除することでストレージコストと転送量を最小化します」
練習問題
基礎レベル
- HLSとDASHの違いを説明し、どちらを選ぶかその理由を述べてください
- ファイルチャンキングのメリットを5つ挙げてください
中級レベル
- 1日100万本の動画アップロードを処理するトランスコーディングパイプラインを設計してください。処理の優先順位付け(有料ユーザー優先等)も考慮してください
- 3台のデバイスが同時にオフラインで同じファイルを編集した場合の同期フローを設計してください
チャレンジ
- 画像ホスティングサービス(Imgur型)を設計してください。画像アップロード、リサイズ(複数サイズ自動生成)、CDN配信、NSFW検出、帯域制限を含めてください。1日1000万枚のアップロードを処理できるスケーラビリティを考慮してください
参考リンク
- YouTube System Design
- Dropbox Architecture
- HLS Specification (Apple)
- AWS S3 Pre-signed URLs
- Designing Data-Intensive Applications (Martin Kleppmann)
次回予告
Day 10: ECサイトとレートリミッターの設計 — 最終日は、ECプラットフォームの設計(在庫管理、決済、分散トランザクション)とレートリミッターの詳細設計に取り組みます。そして10日間の総まとめです!