YouTube 影音平台 (Design YouTube)
設計 YouTube:使用者能 上傳 數十 GB 的大型影片、並能在 任意裝置、任意頻寬 下 低延遲串流 觀看。規模 ~1M 上傳/day、~100M 觀看/day,NFR 明確 可用性優先於一致性(AP)。
招牌取捨:影片 bytes 永遠不經過我們的 service——上傳時 client 拿 presigned URL 直傳 S3(multipart 可續傳),觀看時靠 manifest file + 多 format segments 做 adaptive bitrate streaming(ABR),從 S3/CDN 直接拉片段。代價:複雜度被攤到 client(ABR 邏輯、multipart 進度) 與 離線轉碼 pipeline(DAG)。其餘決勝點:metadata 用水平分割 NoSQL(Cassandra,partition key = video_id)、post-processing DAG(切段 + 轉碼 + 產 manifest)、CDN 把 segments 推到 edge。
配速(35 min 預算):~4 min 釐清需求 → ~4 min 估算 → ~3 min API → ~5 min 資料模型/儲存 → ~6 min 高階架構(上傳 flow + ABR 觀看 flow)→ ~9 min deep dive(轉碼 DAG / 可續傳 multipart / scale + CDN)→ ~3 min 擴展瓶頸 → ~1 min 收尾。開場先一句話複述題目、宣告配速、把「轉碼怎麼跑、上傳怎麼續傳」標記為待會 deep dive——宣告配速本身是高分動作。
1. 釐清需求與範圍 (Clarify & Scope ~4 min)
開場後先框邊界,別急著畫 transcoding pipeline(hard 題最常見死因:一上來就秀 ffmpeg 細節卻沒先講端到端 flow)。
| 釐清問題 | 為何要問 / 影響到哪個決策 | 本題假設(依來源) |
|---|---|---|
| 核心功能就 上傳 + 觀看 嗎? | 圈定範圍,避免發散 | 是:upload + stream,其餘 out of scope |
| 搜尋 / 留言 / 推薦要做嗎? | 直接砍掉一堆子系統 | 不在範圍(search / comment / recommendation) |
| 單支影片多大? | 決定 upload / storage 策略 | 可達數十 GB(10s of GBs) |
| 一致性 vs 可用性怎麼權衡? | 決定 DB 選型與 replication 立場 | 可用性優先(availability over consistency, AP) |
| 規模多大? | 驅動估算與 sharding | ~1M 上傳/day、~100M 觀看/day |
| 串流體驗的硬約束? | 決定要不要 ABR / CDN | 低頻寬下仍要低延遲串流 |
「轉碼怎麼跑、上傳大檔怎麼不爆 我先標記,等 deep dive 展開——現在先把上傳當成『client 拿到一個 URL 直傳 S3』、觀看當成『client 讀 manifest 再拉 segments』的 black box,先把端到端 flow 串起來。」把最重的兩塊推遲,保住敘事連貫。
2. 功能性與非功能性需求 (FR / NFR 與設計北極星)
-
功能性需求 (FR)
- 使用者 上傳影片(upload videos)。
- 使用者 串流觀看影片(stream videos)。
- (Out of scope:search / comment / recommended videos。)
-
非功能性需求 (NFR)
- 高可用性,可用性優先於一致性(AP)。
- 支援 大型影片(單支可達數十 GB)的上傳與串流。
- 低頻寬下仍低延遲 的串流體驗。
- 水平擴展:~1M 上傳/day、~100M 觀看/day。
設計北極星(NFR 直接導出後續所有選型的判準):
| NFR | 導出的設計北極星 | 後續對映選型 |
|---|---|---|
| 數十 GB 大檔上傳 | bytes 不可經過 app server;要能斷點續傳 | presigned URL 直傳 S3 + multipart upload |
| 低頻寬低延遲串流 | 不可一次下載整支;要能依網速切換品質 | 分段 + 多 format + manifest → ABR |
| ~1M 上傳/day metadata | metadata 要可水平分割 | NoSQL(Cassandra),partition key=video_id |
| 可用性優先(AP) | 單點不可拖垮全站;可容忍短暫不一致 | AP 系 NoSQL + 多副本 + CDN |
| 全球低延遲 | 靜態 segments 要就近分發 | CDN edge 快取 |
「這是一個 大檔 blob + 離線轉碼 + 邊緣分發 的系統」——記住這三個詞,後面每個選型都能從它推回去。核心資料平面(影片 bytes)刻意繞開我們的 service。
3. 容量估算 (Back-of-Envelope ~4 min)
估算目的不是算到精確,而是 驅動 DB / 儲存 / 頻寬決策。回連 02-Distributed-Systems/05-Numbers-to-Know。
寫流量(upload QPS)
~1,000,000 uploads/day ÷ 86,400 s ≈ 11.6 uploads/sec (平均;實際有 burst)
讀流量(watch QPS)
~100,000,000 watches/day ÷ 86,400 s ≈ 1,157 watches/sec
write:read ≈ 1:100(與上傳對比)→ 讀重於寫
Metadata 儲存
~1M videos/day × 365 ≈ 365M 筆/year
→ 單表年增數億筆 → 必須可水平分割(Cassandra)
影片 bytes 儲存與頻寬(量級感)
單支可達數十 GB;每支還要存「多種 format × 多段 segments」→ 一支膨脹成數份
→ 總量 PB 級 → 只能放 blob storage(S3),不可能進 DB
→ 觀看是 GB 級 bytes × 1,157/sec 的出口頻寬 → 必須 CDN 卸載,否則 origin 頻寬爆
下表 QPS / 筆數皆為 推導值(由「每日次數 ÷ 86,400」「每日筆數 × 365」算出;來源僅給 ~1M 上傳/day、~100M 觀看/day、單支數十 GB 等輸入,未給精確 QPS)。
| 維度 | 數字 | 對後續決策的意義 |
|---|---|---|
| Upload QPS | ≈ 11.6 /sec(推導值) | 寫不重,但 每次都是 GB 級 bytes → 重點在 throughput |
| Watch QPS | ≈ 1,157 /sec(推導值) | 讀重,且 每次拉 GB 級 → 出口頻寬是真瓶頸 → CDN |
| Metadata 年增 | ≈ 365M 筆/year(推導值) | 單表撐不住 → 水平分割 NoSQL(Cassandra) |
| 影片總量 | PB 級(量級判斷) | 只能 blob storage,DB 只存指標 metadata |
YouTube 的瓶頸 不是 QPS,而是 bytes/sec 與 PB 級儲存。這推回兩個招牌結論:(1) bytes 走 S3+CDN,不走我們的 service;(2) metadata 與 video data 物理分離。
4. 用到的積木 / Building Blocks Used
架構即目錄:先給導覽圖,再展開。下表每個決策都對映既有 concept note(無 alias 連結);本表即「本案例練到哪些積木」的單一事實來源(single source of truth)。
| 設計決策 | 積木 | 在本題的作用(≤1 行) |
|---|---|---|
| 容量估算驅動選型 | 02-Distributed-Systems/05-Numbers-to-Know | 算出 PB 級 bytes / 365M 筆/year,決定 blob+CDN 與 NoSQL |
| 影片 bytes 不進 DB | 07-Caching-Storage/02-Blob-Storage | GB 級檔放 S3,DB 只存 manifest/segment 的位置 metadata |
| 大檔直傳 + 不經 app server | 10-Design-Patterns/04-Handling-Large-Blobs | presigned URL 讓 client 直傳 S3、multipart 可續傳 |
| metadata 水平分割 | 05-Database-Advanced/02-Sharding | 以 video_id 為 partition key 把資料均勻散開 |
| AP 系可水平擴展 DB | 06-Database-Tech/03-DynamoDB | Cassandra/Dynamo 類 NoSQL,AP、易水平擴展 |
| 觀看走 ABR 多 format | 10-Design-Patterns/05-Real-time-Updates | client 依網速即時切換 segment 品質(ABR) |
| 離線轉碼為長任務 | 10-Design-Patterns/03-Long-Running-Tasks | 上傳完非同步觸發 post-processing,不阻塞上傳 |
| 轉碼 pipeline 編排 | 09-Messaging-Coord/01-Data-Pipeline | 切段→轉碼→產 manifest 的 DAG,可平行、可重試 |
| 吸收上傳/轉碼 burst + autoscale | 09-Messaging-Coord/02-Queue | 內部 queue 緩衝高峰,queue 深度當 worker auto-scaling 訊號 |
| segments 就近分發 | 11-Infrastructure/05-CDN | 把 segments / manifest 推到 edge,低頻寬也低延遲 |
| stateless 服務水平擴展 | 02-Distributed-Systems/03-Scalability | Video Service 無狀態(只發 presigned URL / 查 metadata) |
| 流量分散 | 11-Infrastructure/02-Load-Balancer | LB 把請求平均分到多台 Video Service |
| hot video 讀放大 | 07-Caching-Storage/01-Caching | 熱門 metadata 加 cache layer、partition 加 replica |
5. API 設計 (API Design ~3 min)
關鍵:上傳 API 不收影片本體,只發 presigned URL(理據見第 8.2、10-Design-Patterns/04-Handling-Large-Blobs)。
| Method | Path | 用途 | 重點 |
|---|---|---|---|
POST |
/videos/presign_url |
請求上傳授權 | body 帶 VideoMetadata → 建 metadata 紀錄 → 回 presigned_url |
POST |
/videos/upload |
(原始直覺,已被取代) | bytes 不該經 service;改用上面的 presign → 直傳 S3 |
GET |
/videos/:videoId |
取得觀看所需資訊 | 回 VideoMetadata(內含指向 S3 上 manifest URL) |
觀看 API 不回整支影片,而是回 metadata(內含 manifest URL)。client 讀 manifest 後,自己去 S3/CDN 拉 segment URL——這替 ABR 與 CDN 留好伏筆,也讓 API 回應永遠輕量。源頭原始 POST /videos/upload 收 {Video, VideoMetadata} 的直覺被刻意改寫成 presign_url,正是招牌取捨的入口。
6. 資料模型與儲存選型 (Data Model & Storage Choice ~5 min)
核心在於 metadata 與 video data 物理分離:結構化的進 DB,GB 級 bytes 進 blob store。
VideoMetadata (NoSQL, e.g. Cassandra)
--------------------------------------------------
video_id STRING PK / partition key ← 均勻分散用
uploader_id STRING
title STRING
description TEXT
duration INT
status ENUM uploading | processing | complete ← 轉碼/上傳狀態機
upload_id STRING S3 multipart UploadId(續傳用)
chunks[] LIST 每個 part 的 {partNumber, etag, size, done} ← 續傳進度
manifest_url STRING 指向 S3 上的 primary manifest(觀看入口)
s3_location STRING 最終 object / segment 前綴位置
created_at TIMESTAMP
--------------------------------------------------
~1M 筆/day × 365 ≈ 365M 筆/year → 必須水平分割
S3 (Blob Storage) 上的物件(DB 不存 bytes,只存上面這些指標)
--------------------------------------------------
/{video_id}/raw ← 原始上傳檔(multipart 組成)
/{video_id}/{format}/seg_0001.ts ... ← 多 format × 多 segments
/{video_id}/primary.m3u8 ← primary manifest
/{video_id}/{format}/media.m3u8 ← 各 format 的 media manifest
--------------------------------------------------
儲存選型:
| 選項 | 適配本題嗎 | 理由 |
|---|---|---|
| NoSQL(Cassandra/Dynamo) | ✅ metadata 主選 | 365M 筆/year 要水平分割;NFR 要 AP;video_id 當 partition key 易均分 |
| 關聯式 DB(單機) | ❌ | 年增數億筆、要 AP 與水平擴展,單機關聯式撐不住 |
| Object Store(S3/GCS) | ✅ video data 主選 | 數十 GB 大檔 / PB 級總量,DB 存 BLOB 會毀掉查詢/備份/replication |
| 把影片塞 DB BLOB | ❌(反例) | 經驗法則:> 10MB 且不需 SQL 查詢就該放 blob store;100MB BLOB 會拖垮 DB |
→ 積木:DB 選型理據見 06-Database-Tech/03-DynamoDB、06-Database-Tech/01-Database;水平分割見 05-Database-Advanced/02-Sharding;bytes 走 07-Caching-Storage/02-Blob-Storage。
「metadata 與 video data 分離」不是可選優化,而是 本題的地基:DB 只存「指向 S3 的位置 + 狀態機」,所有重量都在 blob store。違反這點(把影片塞 DB)會在第 9 段所有瓶頸處同時爆。
7. 高階架構 (High-Level Design ~6 min)
把積木串成端到端流程。先看兩條核心 flow,再看 scaled 架構。
(A) 上傳 flow(upload:presigned URL + multipart)
Client
│ POST /videos/presign_url { VideoMetadata }
▼
Video Service (stateless)
│ 1. 在 metadata table INSERT 一筆(status=uploading)
│ 2. 用雲端憑證為該物件產生 presigned URL(multipart → 每個 part 一個 URL)
▼
回傳 presigned_url(s) ──────────────► Client
│ PUT part bytes 直傳 S3(繞過 service)
▼
S3 ──回 ETag──► Client ──回報 {partNumber, etag}──► Video Service
│ (更新 metadata chunk 狀態)
全部 part 完成 → Client 通知 service → CompleteMultipartUpload → 組成最終 object
│
▼ (上傳完成事件)
Video Processing Service(離線,非同步) ← 觸發 post-processing DAG
(B) 觀看 flow(watch:ABR)
Client
│ GET /videos/:videoId
▼
Video Service → 回 VideoMetadata(含 S3 上 primary manifest 的 URL)
│
▼
Client 下載 primary manifest(列出各 format 的 media manifest)
│ 依「目前網速 / 使用者設定」選一個 format
│ 從 media manifest 取第 1 段 segment URL → 拉 segment(走 CDN edge)
▼
邊播第 1 段、邊下載後續段
│ 偵測網速變差 → 改拉「更高壓縮 / 更低解析度」的 segment(避免卡頓)
│ 偵測網速變好 → 改拉更高畫質 segment
▼
持續串流(client 是主動方:ABR 決策在 client)
(C) Scaled 架構(端到端)
┌──────────────────────────────────────┐
觀看 segments / manifest ──────►│ CDN (edge) │ ← 11-Infrastructure/05-CDN
(全球就近、低頻寬低延遲) │ 快取 segments + manifest;miss 才回源 S3 │
└───────────────┬──────────────────────┘
│ (cache miss → origin)
Client ──────► Load Balancer ──► ┌─────────────────────────────────┐
(presign / 查 metadata) │ Video Service (stateless, N 台) │ ← 加機器即水平擴展
└──────────┬──────────────────────┘
│ 查/寫 metadata(hot 加 cache)
▼
┌────────────────────────────────────┐
│ VideoMetadata NoSQL (Cassandra) │
│ partition key = video_id(均勻散) │
│ hot video → 加 replica / 前置 cache │
└────────────────────────────────────┘
Client ──(presigned URL, 直傳/直拉 bytes)──► ┌──────────────┐
│ S3 (Blobs) │ raw / segments / manifests
└──────┬───────┘
│ (上傳完成事件)
▼
┌───────────────────────────────────────────────────────────┐
│ Video Processing Service │
│ internal Queue(吸收 burst;queue 深度 → worker autoscale)│ ← 09-Messaging-Coord/02-Queue
│ DAG orchestrator(如 Temporal):切段→轉碼→產 manifest │ ← 09-Messaging-Coord/01-Data-Pipeline
│ worker nodes(平行處理各 segment)→ 寫回 S3 → status=complete│
└───────────────────────────────────────────────────────────┘
關鍵 hand-off:控制平面(presign / metadata)走 LB→stateless Video Service→NoSQL;資料平面(GB 級 bytes)完全旁路,走 client↔S3↔CDN;轉碼是離線 pipeline,靠 queue 解耦上傳尖峰、靠 DAG 平行+重試。
8. 深入探討 (Deep Dives ~9 min)
通用機制(blob storage、queue、CDN、sharding 原理)只在 concept note 解釋;這裡只講「本題怎麼用、選哪個、為什麼」。任一機制超過 ~3 行就砍掉改連結。本題無 home note 的新材料(presigned URL、multipart+ETag、ABR、manifest、轉碼 DAG)才在此 inline 教學並登記到 new_concepts_introduced。
8.1 post-processing 如何運作以支援 ABR(轉碼 DAG)
問題:原始上傳檔無法直接串流;要產出「多 format × 多 segments + manifest」才能在各裝置、各網速播放。
為何是瓶頸:轉碼是 CPU 密集、單支可能跑很久 的工作;若同步做會阻塞上傳、若無編排則無法平行 + 重試。
- Adaptive Bitrate Streaming(ABR):影片切成多個 segments(每段數秒、可獨立播放),且每段存 多種 format(codec + container)。client 依即時網速選品質:網差→拉更高壓縮/低解析度段,網好→拉高畫質段。決策在 client,這讓低頻寬下也不卡頓。
- Manifest file:文字索引檔,告訴播放器「有哪些品質、每段 segment 的 URL 在哪」。本設計產出 一個 primary manifest + 多個 media manifest(每 format 一個,引用該 format 的 segments)。
- 轉碼為 DAG:步驟為 (1) 用 ffmpeg 類工具切段;(2) 各 segment 轉碼成多 container、處理 audio/字幕;(3) 建 manifest 引用各 format 的 segments;(4) 標記
status=complete。步驟相依單向、各 segment 可平行 → 建模為 DAG,用 Temporal 類 orchestrator 負責執行與重試。
| 方案 | 做法 | 優點 | 缺點 |
|---|---|---|---|
| A. 同步在上傳請求內轉碼 | 上傳完當場轉 | 簡單 | 阻塞上傳、無法平行、失敗整個重來 |
| B. 非同步 queue + DAG worker | 上傳完發事件入 queue → DAG worker 平行 | 不阻塞、可平行、可重試、可擴展 | 多一套編排基礎設施 |
選擇與理由:採 B。轉碼是典型 long-running task,用 internal queue 解耦上傳尖峰、用 DAG orchestrator 平行處理各 segment 並對失敗步驟重試;queue 深度還能當 worker auto-scaling 訊號。
→ 積木:10-Design-Patterns/03-Long-Running-Tasks、09-Messaging-Coord/01-Data-Pipeline、09-Messaging-Coord/02-Queue
因為步驟間是 單向相依但可平行:切段必須在轉碼前,但「各 segment 的轉碼」彼此獨立、可同時跑;「產 manifest」要等所有 format 段就緒。DAG 精準表達「誰依賴誰、誰能平行」,orchestrator 能據此排程與 對單一失敗節點重試,而非整條線從頭再來。
8.2 如何支援可續傳上傳(resumable multipart upload)
問題:單支可達數十 GB,上傳中途斷線是常態。
為何是瓶頸:若「上傳失敗就整支重來」,GB 級檔幾乎永遠傳不完;且 bytes 不能經過我們的 service(否則 service 變成 GB 級流量的瓶頸)。
- Presigned URL:server 用雲端憑證為特定 S3 物件產生的 帶臨時授權的網址,讓 client 在限定時間內 直接對 S3 上傳/下載,無需雲端帳號或金鑰。這是「bytes 旁路 service」的關鍵。
- Multipart upload + ETag:大檔切成 parts 逐段上傳;S3 為每個上傳成功的 part 回一個 ETag(類似 checksum 的版本標識)。client 把
{partNumber, etag}回報 server,server 記進 metadata 的 chunk 狀態。
流程(續傳關鍵在「server 記每個 chunk 狀態」):
1. server 呼叫 CreateMultipartUpload(這步打 S3)→ 取得 UploadId
2. server 本地為每個 part 產生 presigned URL(不需再打 S3)
3. 每個 part:client PUT 直傳 S3 → 讀回 ETag → POST {uploadId, partNumber, etag} 回 server
→ server(可 ListParts 比對)更新 metadata 中該 chunk = done
4. 全部完成 → client 通知 server → server CompleteMultipartUpload(所有 partNumber+eTag)
→ 組成最終 S3 object → 更新 metadata 的最終位置 + status
續傳怎麼做:client 中途停了,重新取 VideoMetadata → 看哪些 chunk 已 done → 跳過、只補未完成的 part。
→ 積木:10-Design-Patterns/04-Handling-Large-Blobs、07-Caching-Storage/02-Blob-Storage
那會讓 GB 級 bytes 兩次穿過我們的 service(client→service→S3),service 的頻寬與記憶體立刻成瓶頸、也難以水平擴展、且斷線要整段重傳。presigned URL 讓 client 直傳 S3,service 只發授權 URL + 記 chunk 狀態——這正是招牌取捨:把 bytes 推離 service。
8.3 如何擴展以支援大量上傳與觀看(scale + CDN)
問題:~100M 觀看/day(每次拉 GB 級)、~1M 上傳/day(每次 GB 級)、metadata 年增數億筆。
為何是瓶頸:真正的壓力是 bytes/sec 出口頻寬 與 metadata 寫入/熱點,而非單純 QPS。
| 元件 | 擴展手段 | 取捨 / 積木 |
|---|---|---|
| Video Service | stateless(只發 presign / 查 metadata)→ LB 後水平加機器 | 02-Distributed-Systems/03-Scalability、11-Infrastructure/02-Load-Balancer |
| VideoMetadata | 可水平擴展 NoSQL,video_id 當 partition key 均分;hot video → 加 partition replica 或前置 cache |
05-Database-Advanced/02-Sharding、07-Caching-Storage/01-Caching |
| Video Processing | internal queue 吸收 burst;以 queue 深度 auto-scale worker;DAG 平行 | 09-Messaging-Coord/02-Queue、10-Design-Patterns/03-Long-Running-Tasks |
| S3 | 原生高擴展、多區域;但 離 S3 機房遠的使用者 初載/串流會慢 | 07-Caching-Storage/02-Blob-Storage |
| CDN(招牌補強) | 把 segments / manifest(靜態) 推到 離使用者最近的 edge;hit 直接回、miss 回源 S3 後快取 | 大幅降延遲、卸載 origin 頻寬;11-Infrastructure/05-CDN |
選擇與理由:控制平面靠 stateless + LB 線性擴展,metadata 靠 partition key 均分 + hot key 用 cache/replica,轉碼靠 queue + autoscale 吸收尖峰,觀看頻寬靠 CDN edge 卸載——CDN 是觀看路徑的決定性補強:沒有它,origin(S3)的出口頻寬會被 GB 級觀看流量打爆,遠端使用者也達不到「低頻寬低延遲」的 NFR。
→ 積木:11-Infrastructure/05-CDN、07-Caching-Storage/01-Caching、09-Messaging-Coord/02-Queue
兩處:(1) metadata 的某個 partition(大量 GET /videos/:id 全打同一 partition)→ 增加該 partition 的 replica 或在 DB 前加 cache layer 擋 hot key;(2) 該影片 segments 的 origin 頻寬 → 靠 CDN 把段快取在各 edge,絕大多數觀看在 edge 完成、不回源。見 07-Caching-Storage/01-Caching、11-Infrastructure/05-CDN。
9. 擴展與瓶頸 (Scaling & Bottlenecks ~3 min)
主動找出系統會先在哪裂開並補強(訓練 drive 而非被動回答)。
| 瓶頸 / 風險 | 補強手段 | 積木 |
|---|---|---|
| 觀看出口頻寬(GB 級 × 1,157/sec)打爆 origin | CDN edge 快取 segments/manifest,絕大多數觀看不回源 S3 | 11-Infrastructure/05-CDN |
| Video Service 算力/連線不足 | stateless → LB 後水平加機器(bytes 已旁路,service 很輕) | 02-Distributed-Systems/03-Scalability、11-Infrastructure/02-Load-Balancer |
| metadata 寫入/查詢過熱 | partition key=video_id 均分;hot video 加 replica / cache | 05-Database-Advanced/02-Sharding、07-Caching-Storage/01-Caching |
| 上傳尖峰造成轉碼塞車 | internal queue 緩衝;queue 深度當 auto-scaling 訊號 動態加 worker | 09-Messaging-Coord/02-Queue |
| 大檔上傳中斷重來 | multipart + chunk 狀態 → 斷點續傳(跳過已完成 part) | 10-Design-Patterns/04-Handling-Large-Blobs |
| 轉碼步驟失敗 | DAG orchestrator 對單一節點重試,不整條重跑 | 09-Messaging-Coord/01-Data-Pipeline |
「系統會 先在觀看出口頻寬裂開(GB 級 bytes × 千級 QPS)→ 所以 CDN 是非選配;其次是 轉碼塞車 → 用 queue + autoscale 吸收。控制平面(service/metadata)反而很輕,因為 bytes 已經全程旁路。」先指出最可能裂開處再補強,比逐項羅列更有說服力。
10. 取捨總表與收尾 (Trade-off Recap & Wrap-up ~1 min)
| 決策點 | 選擇 | 放棄了什麼 | 為什麼值得 |
|---|---|---|---|
| 影片上傳路徑 | presigned URL 直傳 S3 + multipart | client 變複雜、要管 chunk 狀態 | bytes 旁路 service、可續傳、可擴展(招牌取捨) |
| 觀看路徑 | manifest + 多 format segments(ABR) | client 端 ABR 邏輯複雜 | 低頻寬不卡頓、各裝置可播 |
| 影片 bytes 儲存 | S3 blob store | 放 DB 的「單一系統」簡單 | 數十 GB / PB 級,DB 存 BLOB 會毀掉查詢/備份/replication |
| metadata 儲存 | 水平分割 NoSQL(Cassandra) | 關聯式的強一致 + join | 365M 筆/year + NFR 要 AP + 易水平擴展 |
| 轉碼 | 非同步 queue + DAG(Temporal 類) | 同步轉碼的簡單 | 不阻塞上傳、可平行、可重試、可 autoscale |
| 全球分發 | CDN edge | 直連 S3 的單純 | 卸載 origin 頻寬、就近低延遲(NFR 硬需求) |
| 一致性立場 | AP(可用性優先) | 強一致 | NFR 明訂可用性優先,影片場景可容忍短暫不一致 |
「整題的單一錨點是:影片 bytes 永遠不經過我們的 service——上傳用 presigned URL 直傳 S3、觀看用 manifest+多 format segments 從 CDN 拉。我們的 service 只做輕量的 發授權 URL + 查 metadata;重活全攤給 client(ABR、multipart 進度) 與 離線轉碼 DAG。metadata 走水平分割 NoSQL(AP),頻寬靠 CDN edge 卸載。瓶頸不是 QPS 而是 bytes/sec,所以 CDN 是非選配。」引用既有決策用連結,不重新推導。
後續追問演練 (Interviewer Follow-ups — "What if…")
因為單支可達 數十 GB。若經 service 中轉,GB 級 bytes 會兩次穿過 service(client→service→S3),service 頻寬/記憶體立刻成瓶頸、難水平擴展、斷線要整段重傳。改用 presigned URL 讓 client 直傳 S3,service 只發授權 URL 並記錄 chunk 狀態。見 10-Design-Patterns/04-Handling-Large-Blobs、07-Caching-Storage/02-Blob-Storage。
~1M 上傳/day → 一年 ~365M 筆(推導值),必須 水平分割;且 NFR 明訂 可用性優先(AP)。Cassandra/Dynamo 類 NoSQL 易水平擴展、AP,以 video_id 當 partition key 可把資料均勻散開。見 06-Database-Tech/03-DynamoDB、05-Database-Advanced/02-Sharding。
manifest 是 文字索引檔,列出有哪些品質、每段 segment 的 URL(primary manifest 指向各 format 的 media manifest)。ABR:client 讀 manifest,依即時網速選 format——網差拉高壓縮/低解析度段、網好拉高畫質段,決策在 client。前提是上傳後已 切段 + 存多 format。見 10-Design-Patterns/05-Real-time-Updates。
因為用 multipart upload:每個 part 上傳成功後 S3 回 ETag,client 回報 {partNumber, etag},server 記進 metadata 的 chunk 狀態。續傳時 client 重取 VideoMetadata → 找出已 done 的 chunks → 跳過、只補未完成的 part,全部完成再 CompleteMultipartUpload。見 10-Design-Patterns/04-Handling-Large-Blobs。
觀看出口頻寬先裂(GB 級 bytes × 暴增 QPS)。依序:(1) CDN 擴大覆蓋與快取命中,絕大多數段在 edge 完成、不回源;(2) metadata 熱點 partition 加 replica / cache;(3) Video Service 因 stateless 直接加機器;(4) 轉碼端用 queue 深度 auto-scale worker 吸收上傳尖峰。控制平面很輕(bytes 已旁路),真正壓力在頻寬。見 11-Infrastructure/05-CDN、07-Caching-Storage/01-Caching、09-Messaging-Coord/02-Queue。
兩處受壓:metadata 某 partition(GET 全打同一 partition)→ 加該 partition 的 replica 或前置 cache layer;segments origin 頻寬 → 靠 CDN 把段快取在各 edge,觀看絕大多數在 edge 完成、不回源 S3。見 07-Caching-Storage/01-Caching、11-Infrastructure/05-CDN。
不會。轉碼建模成 DAG 並由 orchestrator(如 Temporal)執行——各 segment 的轉碼是 可平行、彼此獨立 的節點,已完成的節點輸出已落 S3,orchestrator 只 重試失敗的節點,不整條 pipeline 重跑。配合 queue,job 不會因 worker 掛掉而遺失。見 09-Messaging-Coord/01-Data-Pipeline、09-Messaging-Coord/02-Queue。
影片場景下,短暫看到稍舊的 metadata(如剛改的標題還沒同步)遠比整站不可用可接受,所以選 AP。具體影響:metadata 用 AP 系 NoSQL(Cassandra/Dynamo)+ 多副本、靠 CDN/cache 提供可能稍舊但高可用的內容;不追求跨副本的強一致。對照 06-Database-Tech/03-DynamoDB。
presigned URL 帶 臨時授權、有過期時間,且由 server 用雲端憑證 針對特定物件(特定 key、特定操作) 簽發——拿到 URL 的人只能在限定時間內對 那一個物件 做 被授權的動作(如 PUT 某 part),無法觸及其他物件,也不需要雲端帳號/金鑰。配合 multipart 每個 part 各自簽發,授權粒度更細。(一般推論:仍應設短 TTL 並校驗 metadata 狀態機,避免重放。)
主動回想:白板重建 (Whiteboard from Scratch)
不看上文,照下面提示把整個設計重建一次。每格都該能 30 秒內口述。
常見陷阱 (Exam Traps for This Problem)
What:畫成 client → 我們的 API → S3 的中轉路徑。
Why:單支 數十 GB 的 bytes 兩次穿過 service,頻寬/記憶體立刻爆、難水平擴展、斷線整段重傳。
正確做法:service 只發 presigned URL,client 直傳 S3;multipart 切段續傳。見 10-Design-Patterns/04-Handling-Large-Blobs。
What:VideoMetadata 表加一個 video_blob 欄位塞整支影片。
Why:DB 擅長結構化查詢,不適合大型 BLOB;100MB+ BLOB 會嚴重拖垮查詢、備份、replication。經驗法則:> 10MB 且不需 SQL 查詢就放 blob store。
正確做法:bytes 放 S3,DB 只存 manifest/segment 的 位置 metadata。見 07-Caching-Storage/02-Blob-Storage。
What:GET /videos/:id 回傳整支影片檔讓 client 下載播放。
Why:那是「離線下載」而非串流——大檔下載完才能播、低頻寬等到天荒地老、無法依網速調品質。
正確做法:回 manifest URL,client 讀 manifest 後 分段、依網速 ABR 拉 segments。見 10-Design-Patterns/05-Real-time-Updates。
What:上傳完成後在同一條請求內跑 ffmpeg 轉碼。
Why:轉碼 CPU 密集、單支可能很久 → 阻塞上傳、無法平行、失敗整條重來、扛不住尖峰。
正確做法:上傳完發事件入 queue,由 DAG worker 非同步平行轉碼、可重試、可 autoscale。見 10-Design-Patterns/03-Long-Running-Tasks、09-Messaging-Coord/01-Data-Pipeline。
What:把 VideoMetadata 放單台 PostgreSQL 並要求跨副本強一致。
Why:年增 ~365M 筆 單機撐不住;NFR 明訂 可用性優先(AP),強一致與此衝突。
正確做法:用 水平分割 NoSQL(Cassandra/Dynamo),partition key=video_id,AP + 易擴展。見 06-Database-Tech/03-DynamoDB、05-Database-Advanced/02-Sharding。
What:觀看流量全打 origin(S3)。
Why:離 S3 機房遠的使用者初載/串流會慢;且 GB 級觀看流量會 打爆 origin 出口頻寬,違反「低頻寬低延遲」NFR。
正確做法:用 CDN 把 segments/manifest 快取在 離使用者最近的 edge,hit 直接回、miss 才回源。見 11-Infrastructure/05-CDN。
Related Notes
- 02-Distributed-Systems/05-Numbers-to-Know
- 02-Distributed-Systems/03-Scalability
- 05-Database-Advanced/02-Sharding
- 06-Database-Tech/03-DynamoDB
- 06-Database-Tech/01-Database
- 07-Caching-Storage/01-Caching
- 07-Caching-Storage/02-Blob-Storage
- 09-Messaging-Coord/01-Data-Pipeline
- 09-Messaging-Coord/02-Queue
- 10-Design-Patterns/03-Long-Running-Tasks
- 10-Design-Patterns/04-Handling-Large-Blobs
- 10-Design-Patterns/05-Real-time-Updates
- 11-Infrastructure/02-Load-Balancer
- 11-Infrastructure/05-CDN