跳到主要內容

系統設計

把資料結構放大到好幾台機器

主題 · 案例:UGC 影音平台

案例: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";
}
}
20
160
最低解析度優先
解析度
5%
可播放:p50
11.9 分鐘
可播放:p95
24.3 分鐘
全部轉完:p95
27.8 分鐘
佇列最多積壓
2,404
轉檔機忙碌率
83%
每天新增儲存
16.0 TB
CDN 命中率
62%
回源流量
99 Gbps

平均要約 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 On = 100n = 1,000n = 10,000成長倍數:實測(理論)
儲存(GB)O(n)41.73854,055×97 (×100)
轉檔(機器小時)O(n)10.798.61,040×97 (×100)

兩者都跟著上傳量線性成長,而且永遠不會變少:影片一旦上傳就要一直存著。觀看量則和上傳量無關,取決於熱門程度,那是 CDN 的事。

和其他做法比

每小時影片的儲存轉檔機器時間(每秒影片)平均送出位元率會卡頓的觀眾
四種解析度4.1 GB1.05 s2.63 Mbps2%
兩種(360p、720p)1.4 GB0.40 s1.74 Mbps2%
只有 1080p2.3 GB0.50 s5.00 Mbps60%

一萬名觀眾的頻寬取對數常態分布(中位數 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 後面。讓每部影片的最低解析度優先,創作者幾乎馬上就能看到。
  • 轉檔要能重做:轉到一半機器掛掉,工作要能從佇列重新取出,而且重做一次結果一樣(冪等),不然會留下壞掉的檔案。
  • 儲存只會增加:每部影片、每種解析度都永久保存。冷門影片可以移到便宜的冷儲存,或刪掉很少人看的高解析度版本。