案例:UGC 影音平台
UGC 是 User-Generated Content(使用者產生的內容)。使用者上傳的影片要轉成多種解析度,再透過 CDN(內容傳遞網路)播放出去:上傳和觀看兩條路徑的規模差了好幾個數量級。
下面一列是上傳管線:檔案直接進物件儲存、經佇列交給轉檔機、寫回各種解析度。上面一列是觀看路徑:幾乎全部由 CDN 從那些檔案送出,中間的 API 只發中繼資料和網址。兩條路的流量差了好幾個數量級。選一個流程一步步看;點方塊可以進到那個元件的主題。
type Rendition = { name: string; kbps: number };type Video = { status: string; ready: Rendition[] }; const LADDER: Rendition[] = [ // lowest first, so a video is playable soonest { name: "360p", kbps: 600 }, { name: "480p", kbps: 1000 }, { name: "720p", kbps: 2500 }, { name: "1080p", kbps: 5000 },]; class VideoService { private meta = new Map<string, Video>(); // metadata database private jobs: { id: string; r: Rendition }[] = []; // transcoding queue private store = new Map<string, number>(); // object storage: key -> bytes private edge = new Map<string, number>(); // CDN edge, oldest first constructor(private edgeCapacity: number) {} requestUpload(id: string): string { this.meta.set(id, { status: "uploading", ready: [] }); return `https://storage.example/raw/${id}?signature=...`; } onUploaded(id: string): void { this.meta.get(id)!.status = "processing"; for (const r of LADDER) this.jobs.push({ id, r }); } runNextJob(seconds: number): void { const { id, r } = this.jobs.shift()!; this.store.set(`v/${id}/${r.name}`, seconds * r.kbps * 125); const video = this.meta.get(id)!; video.ready.push(r); if (video.ready.length === LADDER.length) video.status = "ready"; } playback(id: string, bandwidthKbps: number): string { const ready = this.meta.get(id)!.ready; let pick = ready[0]; for (const r of ready) if (r.kbps <= bandwidthKbps * 0.8) pick = r; return `v/${id}/${pick.name}`; } fetch(key: string): "hit" | "miss" { const cached = this.edge.get(key); if (cached !== undefined) { this.edge.delete(key); this.edge.set(key, cached); return "hit"; } const bytes = this.store.get(key)!; this.edge.set(key, bytes); if (this.edge.size > this.edgeCapacity) { this.edge.delete(this.edge.keys().next().value!); } return "miss"; }}平均要約 126 台轉檔機才跟得上,尖峰時是三倍。用 160 台時,佇列最多積了 2,404 個工作,一半的影片在 11.9 分鐘 內可以播放。工作嚴格照順序做,新影片的 360p 得排在別人的 1080p 後面。試試讓最低解析度優先。在邊緣,只放 5% 的影片就接住 62% 的觀看:總共 263 Gbps 的流量,只有 99 Gbps 回到原站。
p50/p95 是第 50/95 百分位(percentile)。每部上傳影片是一個樣本:從上傳到第一個解析度可播放,或到全部解析度轉檔完成的時間。把各自的時間排好,取 50%、95% 位置的值,不統計觀眾的播放請求。
假設:以選定的速率上傳 120 分鐘,第 50 到 70 分鐘變成三倍;影片長度是平均 6 分鐘的指數分布;每個解析度一個工作,每秒影片要 0.1(360p)、0.15(480p)、0.3(720p)、0.5(1080p)秒的機器時間。同時有 100,000 人在看,依頻寬挑解析度;20,000 部影片依 Zipf 熱度觀看,邊緣用 LRU。固定亂數種子,可以重現。
模型假設與範圍
- 這是可重現的教學模型;延遲、容量、故障率與工作負載是設定或樣本,不能直接當作正式系統的效能承諾。
- 上傳/轉碼佇列與播放快取採固定資源及種子流量;未執行真實編碼,也未納入頻寬競爭、DRM 與 ABR 播放器。
什麼時候用
- 上傳不經過應用伺服器:發預簽網址讓用戶端直接傳進物件儲存,API 只管中繼資料。幾 GB 的檔案佔住 API 連線,整個服務都會被拖慢。
- 用佇列接轉檔:上傳量會暴增,轉檔機的數量卻是固定的。佇列讓尖峰只是排隊,而且可以決定誰先做。
- 觀看交給 CDN:熱度極度集中,邊緣只要放少量熱門影片就能接住大部分流量。原站只服務冷門的長尾。
和其他主題的關係
- 延伸閱讀
- 案例:動態牆
時間與空間複雜度(Big O)
| 操作 | 平均 | 最差 |
|---|---|---|
| 取得上傳網址 檔案本身不經過 API | O(1) | O(1) |
| 轉檔一部影片 L 是影片長度、R 是解析度數;各解析度可以平行 | O(L × R) | O(L × R) |
| 開始觀看(中繼資料) 快取命中;沒命中多一次資料庫查詢 | O(1) | O(1) |
| 下載一個片段 多半是邊緣命中;沒命中多一次回源 | O(1) | O(1) |
空間:O(L × Σ bitrate),每部影片的每種解析度各存一份,原始檔另外保留
Big O 實測:n 變大時步數怎麼長
數的是:上傳 n 部影片後的總量(四種解析度,GB 與機器小時)
| Big O | n = 100 | n = 1,000 | n = 10,000 | 成長倍數:實測(理論) | |
|---|---|---|---|---|---|
| 儲存(GB) | O(n) | 41.7 | 385 | 4,055 | ×97 (×100) |
| 轉檔(機器小時) | O(n) | 10.7 | 98.6 | 1,040 | ×97 (×100) |
兩者都跟著上傳量線性成長,而且永遠不會變少:影片一旦上傳就要一直存著。觀看量則和上傳量無關,取決於熱門程度,那是 CDN 的事。
和其他做法比
| 每小時影片的儲存 | 轉檔機器時間(每秒影片) | 平均送出位元率 | 會卡頓的觀眾 | |
|---|---|---|---|---|
| 四種解析度 | 4.1 GB | 1.05 s | 2.63 Mbps | 2% |
| 兩種(360p、720p) | 1.4 GB | 0.40 s | 1.74 Mbps | 2% |
| 只有 1080p | 2.3 GB | 0.50 s | 5.00 Mbps | 60% |
一萬名觀眾的頻寬取對數常態分布(中位數 4 Mbps)。只存 1080p 最省轉檔,但六成的觀眾頻寬不夠、會一直卡;多存幾種解析度,用儲存和轉檔時間換來幾乎人人都能順暢播放。最低一檔還是有觀眾跟不上,那是頻寬不到 0.6 Mbps 的人。
真實世界裡的它
- YouTube、TikTok、Twitch 的錄影都把影片轉成多種解析度,用 HLS(HTTP Live Streaming)或 DASH(Dynamic Adaptive Streaming over HTTP)切成小片段,由 CDN 依觀眾頻寬送出。
- AWS S3 預簽網址或 Google Cloud Storage 的簽章網址,就是「直接上傳」那一步。
- Netflix 會在離峰時把熱門影片預先放進各地的快取設備(Open Connect),而不是等第一個人來看。
取捨與陷阱
- 佇列照順序做會讓新影片等很久:一部新影片的 360p 排在幾千個 1080p 後面。讓每部影片的最低解析度優先,創作者幾乎馬上就能看到。
- 轉檔要能重做:轉到一半機器掛掉,工作要能從佇列重新取出,而且重做一次結果一樣(冪等),不然會留下壞掉的檔案。
- 儲存只會增加:每部影片、每種解析度都永久保存。冷門影片可以移到便宜的冷儲存,或刪掉很少人看的高解析度版本。