vatt'ghern jaskier's ballads
本文 1 個互動圖表在手機上以重點摘要呈現,互動版請以桌面瀏覽器開啟。

Meta 把 Instagram Live 的直播上傳換成 Media over QUIC,這一階段的成績單只有一個詞:持平。文章寫的是「Achieving parity was the target for this phase」,目標就是追上跑了近十年的 RUSH,沒有要贏它。

Instagram Live 上傳換成 MoQ

Meta 工程部落格交代,Instagram Live 現在用開放標準傳輸協定 Media over QUIC(MoQ)處理直播上傳,也就是 ingest,範圍是 iOS 與 Android。這篇文章要回答的不是 MoQ 能不能傳影音,而是在 Meta 的規模下,把一條生產中的傳輸換掉,要先立什麼目標、拆成哪幾步、碰到哪些營運問題。下面照元件拆開:MoQ 的資料模型、同一條 track 上的延遲、這次上線用的設定參數與 edge 翻譯層、三個營運上的坑,最後看文章給的結果能撐起多少結論。這是廠商自己寫的工程文章,數字很少,我會把「文章寫了什麼」和「我的推測」分開標。

Track 與 relay:中間那一層不看內容

MoQ 的核心抽象是 Track。文章的定義是「an addressable stream of media objects」,一條可以定址的媒體物件串流。發佈端把 object 推上去,relay 負責轉手。文章對 relay 的描述裡最關鍵的一句,是它「cache and forward objects without parsing the media payload」,快取、轉發 object,但不解析媒體內容。我的解讀是,relay 手上能用的是 object 外層的 metadata,不是影音內容本身。

這個設計往下推有一個合理的推測,文章沒有這樣寫:relay 既然不解析 payload,日後換編碼或容器格式時,中間這一層大概不必跟著改。反過來說,relay 想做的每一個決策都得靠 metadata 表達,metadata 設計得好不好,就決定了 relay 能聰明到哪裡。

底下的傳輸是 QUIC。文章列出的好處有兩項:獨立的 stream 可以各自往前,不會有 TCP 那種跨 stream 的 head-of-line blocking;連線建立更快,並且支援 connection migration。直播上傳的情境裡,手機常在 Wi-Fi 與行動網路之間換手,connection migration 看起來最貼近這個場景,這是我的推測。文章只是列出這項性質,沒有說它在 Instagram Live 上實際帶來多少改善。

relay 能做的事,是這套設計最有特色的地方。文章說 relay 可以依 metadata「drop, prioritize, or replay objects」,丟棄、排優先順序或重送,並主張這讓壅塞時的行為比 byte-stream 協定更好。這是文章對協定設計的主張,不是 Instagram Live 的量測結果,兩者要分開看。位元組串流上的中間節點只看得到一串不透明的 byte,前面一段掉了,後面全部得等;MoQ 把「哪些東西可以丟、哪些要先送」變成 object 層級、靠 metadata 做的決定。這段對比是我的解讀。

adaptive bitrate 也寫進協定本身。文章的說法是這套邏輯「built directly on native MoQ constructs rather than reconstructed at higher layers」,直接建在 MoQ 原生的構件上,不必在更高層重新拼出來;頻寬變動時,可以拿解析度換連續性。這裡文章沒有展開細節,我也不替它補。

同一條 track,兩種延遲

文章用一個例子說明 track 的另一個性質:「Two subscribers to the same track can run at different latencies at the same time」,同一條 track 的兩個訂閱者可以同時用不同的延遲。例子是互動參與者約 100 ms,封存或緩衝式服務則是數秒。兩種需求不必各開一條管線,差別落在訂閱者那一端。

下面的模擬是我做的示意模型,不是 Meta 的實作或量測。規則只有一條:publisher 每 200 ms 送出一個 object,基本傳送延遲固定 60 ms,訂閱者在「擷取時間加上它的延遲目標」之前收到就算準時,晚到就算誤點。選好兩位訂閱者的延遲目標與停頓長度,按「卡住上行」,看誰先出現缺口。200 ms、60 ms 與選單裡的數值都是我設的模型參數;從文章來的只有一件事,就是兩位訂閱者可以同時用不同的延遲。

選延遲目標,按「卡住上行」· 2 位訂閱者 × 3 種停頓

準時播出誤點已到、待播出還沒到

A:準時 0/誤點 0 B:準時 0/誤點 0

示意模型,參數由我設定(200 ms 一個 object、基本延遲 60 ms),不是 Meta 的資料。準時的判準是「到達時間不晚於擷取時間加延遲目標」。

示意模型,參數由我設定(200 ms 一個 object、基本延遲 60 ms),不是 Meta 的資料

同一條 track 的兩位訂閱者延遲目標不同,上行停頓比目標短就被緩衝吃掉,比目標長就誤點,100 ms 的訂閱者最先出現缺口。

延遲目標在模型裡就是緩衝額度。100 ms 的訂閱者沒有餘裕吸收上行的停頓,上行一卡就開始誤點;數秒的訂閱者把停頓吃進緩衝裡,只要停頓比它的延遲目標短,就看不出任何異狀。真正的系統怎麼處理這種情況,文章沒有說,它只說 relay 可以丟、排優先順序或重送。模型裡的「誤點」是我用的最簡單判準,現實裡該丟、該等還是該重送,要由協定與 metadata 決定。

文章列的三個動詞,放進這個模型裡各有一個對應的讀法,這是我的解讀,文章沒有逐項對應。丟棄(drop)對應「這個 object 對某位訂閱者已經沒有用」:A 的期限過了,繼續送過去只是浪費頻寬,而同一個 object 對 B 可能還趕得上。排優先順序(prioritize)對應「期限近的先送」:兩位訂閱者的期限不同,relay 手上的 metadata 可以讓它知道誰比較急。重送(replay)對應「落後的訂閱者補課」:緩衝式服務有時間補。三個動作的共同前提,是 relay 能在不看內容的情況下做出判斷,這正是前面「不解析 payload」的用處。

還要記得這次上線的是 ingest:一個 publisher 往服務端送,這張圖裡的「兩位訂閱者」在這一階段並沒有用到。文章在這一階段沒有談多訂閱者,只說這次上線「becomes the foundation to extend MoQ to an end-to-end architecture for live video delivery and playback」,之後要往端到端的直播傳遞與播放延伸。我的判斷是,每位訂閱者各自設定延遲的好處,要到播放端才會兌現,這是推測。

一個設定參數,加上 edge 的翻譯層

為什麼先拿 Instagram Live 開刀,文章給了三個理由,每個都用一句話開頭。第一句是「Instagram Live is our largest-scale test」,它是 Meta 規模最大的測試場。第二句是「RUSH was falling behind the open ecosystem」,RUSH 正在落後開放生態。第三句是「Metric parity is a prerequisite for everything else」,指標持平是其他一切的前提。

RUSH 是 Meta 自己的前一代協定。文章說它「proved that QUIC could carry live media at scale and was a predecessor to MoQ」,證明了 QUIC 可以在規模下承載直播,也是 MoQ 的前身,並且「stable and performant at scale for nearly a decade」,在規模下穩定而且有效能,已經近十年。把這幾句放在一起讀,這次遷移的動機不是舊的東西壞了,而是它落在開放標準的後面,這是我的歸納。我的解讀是,換到共用標準的好處是更多人能實作、更多人能測互通,代價是得先把自己既有的水準重做一遍,所以目標訂成持平才合理。

這也提醒一件容易看漏的事:對 Meta 來說,QUIC 本身不是新東西,RUSH 就已經是 QUIC 上的協定。這次換掉的是 QUIC 之上的那一層,也就是 track、object、relay 這套資料模型,以及它背後的標準。前面列的 QUIC 好處,因此不能全算成 MoQ 的功勞;哪些是 MoQ 新增的,文章沒有一項一項對照 RUSH。這個拆法是我的推論,依據只有文章說 RUSH 是 MoQ 的前身。

客戶端這邊,文章說 moxygen「provides publisher, subscriber, and relay roles」,涵蓋 publisher、subscriber 與 relay 三種角色,並且「integrates with our QUIC stack」,接上 Meta 自己的 QUIC stack。切換靠的是一個旋鈕:「A single config parameter chooses MoQ or RUSH for a given broadcast」,單一設定參數就能替每一場直播選 MoQ 或 RUSH。

伺服器這邊的做法是不動後端。edge 上有一層翻譯,文章寫「a translation layer converts MoQ frames to the RUSH format the existing backend already understands」,把 MoQ frame 轉成既有後端本來就看得懂的 RUSH 格式,並說「Nothing downstream changes while we swap the client transport」,換客戶端傳輸的期間,下游什麼都不用改。我的解讀是,這把風險壓在最小的範圍,出問題時要查的只有客戶端到 edge 這一段。代價文章沒有提:多出來的那一次轉換要花多少處理成本,文章沒有數字。

持平之所以量得出來,我推測和翻譯層有關。兩條路徑進到同一個後端,後端收到的都是 RUSH 格式,下游的指標與流程不變,MoQ 與 RUSH 就能在同一套指標上對照;要是後端也換了,比較的基準就跟著動。文章只說了下游不變、指標持平這兩件事,兩者之間的這層關係是我接起來的。

誰走 MoQ 由伺服器決定。文章說 stream URL generation「routes experiment-eligible broadcasts to MoQ-capable endpoints based on experiment-group membership」,依實驗分組把符合資格的直播導向支援 MoQ 的 endpoint。好處是分階段放量「doesn't depend on app-version adoption」,不必等使用者升級 app,實驗規模也「can be resized in minutes」,幾分鐘內就能調整。

把兩個機制疊在一起看,一場直播走不走 MoQ,要過兩關:伺服器端要先把這場直播劃進實驗分組,導向支援 MoQ 的 endpoint;客戶端的設定參數也要選 MoQ。文章沒有說兩關不一致時會怎樣,例如被導向 MoQ endpoint、參數卻選了 RUSH。合理的推測是這種組合會被刻意避免,但這只是推測。對放量來說,這個結構的好處是出事時有兩個地方可以關,壞處是兩邊的設定要保持一致,又多一件要驗證的事。

兩個把手分工不同:客戶端的設定參數決定這場直播用哪一種傳輸,伺服器端的分組決定把哪些直播導向哪裡。如果你也在計畫換 client transport,這是文章裡最容易搬走的部分:客戶端留著舊路徑,伺服器端控制放量速度,兩邊都能獨立回退。「兩邊都能獨立回退」是我的推論,文章只描述了兩個機制。下圖把這段路徑畫出來,按鈕切換一場直播的設定參數。RUSH 那條在文章裡沒有細述,我畫成不需要翻譯,依據是後端本來就看得懂 RUSH,這也是推論。

切換這場直播的 config 參數 · 2 條路徑

Stream URL generation 依實驗分組,挑支援 MoQ 的 endpoint 回傳 endpoint App 客戶端 iOS/Android moxygen + QUIC stack config:MoQ 或 RUSH Edge 翻譯層 MoQ frame 轉 RUSH 既有後端 本來就看得懂 RUSH 格式 MoQ RUSH 格式 RUSH(原本的路徑,圖上畫成不經翻譯,屬推論) 兩條路徑對後端來說,收到的都是 RUSH 格式

config = MoQ:客戶端用 MoQ 上傳,edge 的翻譯層把 MoQ frame 轉成 RUSH 格式,後端收到的和以前一樣。

路徑與元件名稱取自文章;RUSH 路徑是否經過 edge、經過時做什麼,文章沒有細述。

三個營運上的坑:草案、防火牆、看不見的封包

文章把上線過程的難處歸成三件事,每一件都對應 MoQ 或 QUIC 的一個性質。

第一件是規格還在動。文章寫「As an active IETF standard, the MoQ draft continued to evolve while we built」,MoQ 是進行中的 IETF 標準,草案在他們開發期間持續變動;處理方式是「pinned to specific draft versions, kept compatibility shims around for older clients」,釘住特定草案版本,並為舊的客戶端保留相容用的 shim。合理的推測是,每多一個 shim 就多一份要維護的相容債,而 app 版本的分布又讓舊客戶端不會很快消失。文章沒有說他們實際保留了幾個版本。

第二件是防火牆。文章的原句是「UDP/443 is still blocked or rate-limited on a non-trivial slice of enterprise and carrier networks」,企業與電信業者網路裡有不小的一塊,UDP/443 仍被擋或被限速。文章沒有給比例。QUIC 跑在 UDP 上,這些網路裡 QUIC 握手就走不通,所以不能只靠 QUIC。他們的選法是「pick the transport (TCP or QUIC) at the start of the live stream based on which handshake wins」,直播開始時看哪個握手先成功就用哪個。

這個選擇發生在直播開始的那一刻,文章的原話是「at the start of the live stream」。我讀到的內容裡,沒有交代直播進行到一半、網路從 Wi-Fi 換成行動網路時,傳輸選擇會不會重來。前面提過 connection migration 是 QUIC 的性質,但它處理的是同一條 QUIC 連線換網路,和「開場選 TCP 還是 QUIC」是兩件事,不能混在一起讀。以直播上傳的情境來說,這是我會想追問的缺口:開場時 QUIC 握手贏了,之後換到一個擋掉 UDP/443 的網路,連線會怎麼樣。文章沒有回答,我也不猜。

下圖把文章的幾句話排成一張決策圖。有一處我讀不出更多:文章在別處說,現階段 QUIC 被擋的網路是「lean on RUSH's existing TCP fallback as an expediency」,靠 RUSH 既有的 TCP fallback 湊合;而 MoQ 本身有標準化的 TCP 對映,文章舉的例子是 WebTransport over HTTP/2,以及還在發展的 QMUX。這兩段怎麼對上,文章的原話我沒有再讀到更多,所以圖上把 TCP 那條標成借用 RUSH 的 fallback,QMUX 的方向另外註記。文章的說法是「standardizing on QMUX removes that dependency so a blocked network no longer forces a second protocol」,標準化 QMUX 能去掉那個依賴,被擋的網路不必再強迫出現第二套協定。「那個依賴」指的大概是 RUSH 的 fallback,這是我的讀法。

直播開始 比較兩種握手 TCP 與 QUIC 誰先成功 QUIC 先成功 走 MoQ over QUIC TCP 先成功 UDP/443 被擋時只剩這條(推論) 現階段:QUIC 被擋的網路借用 RUSH 既有的 TCP fallback,文章稱之為權宜之計。 方向:標準化 QMUX,被擋的網路不再需要第二套協定。
方框內容出自文章的三句話,「UDP/443 被擋時只剩這條」是我的推論。

第三件是看不見。文章說「QUIC's encryption hides framing from middleboxes, and from our own packet captures」,QUIC 的加密把 framing 藏起來,中間設備看不到,連自己的 packet capture 也看不到。對應的做法是在 edge 內建「MoQ-aware tracing」,好讓團隊在不解密線路封包的情況下,看到 object 層級的事件,也就是文章說的「object-level events」。這件事的啟示偏通用:協定加密之後,可觀測性要搬到看得懂協定語意的那一層去做,抓封包這個老工具不再夠用。後一句是我的歸納。

這套追蹤放在 edge。換句話說,客戶端到 edge 之間發生的事,是從 edge 這一端觀察到的 object 事件去看;客戶端那一端看到了什麼,文章沒有提。edge 的觀測夠不夠用來分辨問題出在手機、網路還是 edge 自己,文章也沒有交代,我不替它補。

線路上,包含自家的 packet capture 加密 加密 加密 加密 加密 加密 加密 加密 framing 看不到,中間設備和自己的抓包都一樣 edge 上的 MoQ 追蹤 一條 track object object object object object object object object object 層級的事件,不必解密線路上的封包 示意:兩排方框代表同一串資料的兩種觀察角度,數量與內容不是實際資料。
左右兩種視角的差別直接取自文章的兩句話,方框數量只是版面需要。

把 MoQ 的 TCP 退路和 RUSH 的 TCP fallback 放在一起看,差別在於前者是標準的一部分,後者是 Meta 自己的東西。文章舉的標準對映有 WebTransport over HTTP/2,以及「emerging QMUX mapping」,還在發展中的 QMUX;既然還在發展,現在押它就是押一個尚未定案的規格,這又回到第一個坑:規格還在動。標準化的進度怎麼走,文章沒有給時程。

換傳輸的帳:這次付了什麼,還欠什麼

把文章散在各處的句子加總,這次遷移同時養著好幾樣東西。客戶端要同時留著 MoQ 與 RUSH 兩條路,由一個設定參數選一條;edge 上多了一層翻譯;舊的 app 版本要靠 shim 對上被釘住的草案版本;直播開場要比兩種握手;加密之後的觀測,要另外建一套懂 MoQ 的追蹤。這張清單是我從文章各段拼起來的,文章沒有把它們合成一筆帳,也沒有說各項的成本。

其中兩項看起來是過渡性質。翻譯層存在的理由是後端本來就看得懂 RUSH,後端如果有一天直接說 MoQ,它就沒有存在的理由;RUSH 的 TCP fallback 同理,文章稱它是權宜之計,並指出標準化 QMUX 的方向。合理的推測是,這兩項是這次放量選擇「不動下游」換來的暫時負債,該不該還、什麼時候還,文章沒有給時程。

另外兩項比較像長期成本。追著草案走,只要標準還沒定案,shim 就不會消失;加密後的可觀測性,需要有人持續維護一層看得懂協定語意的追蹤。這兩項不會因為遷移完成就自動消失,至少在標準穩定之前如此,這同樣是我的推測。

這樣看,「持平」這個目標的定價就清楚了:付了上面這些,換來的結果是沒有變差。划不划算,要看後面有沒有兌現。文章指出的兌現方向,是端到端的直播傳遞與播放,以及一個共用的開放標準。對自己的團隊,值得問的是換掉之後,我要維護的東西是變多還是變少。從文章現階段的描述看,答案比較像是先變多,之後才可能變少,這是我的讀法,文章沒有這樣總結。

如果你手上也有一條要換成新標準的長壽協定,這篇文章能搬走的是三件事的準備順序。一是先想好舊客戶端的相容期,文章的做法是釘版本加 shim。二是在新協定加密之前,就規畫好不靠抓封包也能看見的觀測點,文章是在 edge 補了一層。三是替「網路不放行」準備第二條路,文章靠握手比賽選傳輸,並借用既有的 TCP fallback。這個順序是我排的,文章三個坑本身沒有排序,也沒有說哪個先踩到。

持平的結果,和文章沒說的事

結果只有兩句。第一句是「The MoQ path matched RUSH across the streaming-quality indicators and reliability measures used for the rollout」,在這次放量用的串流品質指標與可靠度指標上,MoQ 路徑和 RUSH 持平。第二句是它證明了「we could replace a production transport without disrupting creators or viewers」,可以換掉一條生產中的傳輸,而不干擾創作者與觀眾。

這兩句要分開讀。第一句是量測結果,但只有結論。第二句是對影響範圍的主張,同時點名創作者與觀眾。這一階段換的只有上傳路徑,播放端文章放在下一步,所以觀眾沒受干擾,比較可能的意思是觀眾那一側本來就沒有改動。這是我的推論,文章沒有這樣拆。換句話說,這個結果最直接撐起的是創作者那一側的持平。

持平可以是很強的結果,因為換傳輸本來就容易讓某個指標變差;也可以是很弱的結果,因為它的強度取決於指標是哪些、容忍範圍多大、跑了多久。文章沒有列出指標名稱,沒有給 MoQ 與 RUSH 的差距數字,也沒有說樣本量;就我取得的文章內容而言,也沒看到百分比。這不是挑毛病,因為這一階段的目標本來就是持平,但讀者拿不到可以自己重算的材料。下圖把文章的證據分成三堆。

有數字或具名事實 都是範例或背景 只有定性結論 沒有可重算的材料 我讀到的範圍沒有 就取得的內容而言 約 100 ms 互動訂閱者的延遲範例 數秒 封存或緩衝服務的範例 近十年 RUSH 在生產上的壽命 與 RUSH 持平 品質與可靠度指標 UDP/443 被擋或限速 只說「不小的一塊」網路 實驗規模可調整 幾分鐘內,沒有放量數字 指標名稱與差距 MoQ 對 RUSH 的數值 百分比與樣本量 沒看到這類數字 翻譯層的成本 edge 多一次轉換的開銷
分類是我的整理。右欄是「就我取得的文章內容而言沒有」,不等於原文一定沒有。

如果你要評估自己的遷移,這篇文章給的用法是結構,不是數字:先把「持平」定義成一組指標加容忍範圍,再用 server-side 分組放量,每一階段都以同一組指標對照舊路徑。文章的做法與這個順序一致,一邊寫指標持平是前提,一邊用實驗分組控制放量,但指標清單要你自己訂。

要把這套做法搬到別處,還得看清楚哪些是 Meta 自己的前提。文章寫的是「our QUIC stack」,edge 與後端也都是自家的,翻譯層能存在,是因為他們同時控制客戶端、edge 與後端。如果你的後端是第三方服務,沒辦法在中間插一層翻譯,這種「下游不動」的做法就不一定走得通。這是我的推論,文章沒有討論別人的情境。

文章的後續方向有三條。第一條是端到端,把 MoQ 從 ingest 延伸到直播的傳遞與播放。第二條是補上 TCP fallback 的缺口,前面引過的 QMUX 說法就屬於這條。第三條是即時互動 AI,文章把它放在後續方向裡,在這一節寫到「A single low-latency, bidirectional transport that carries signaling and media together is a natural fit」,一條同時承載 signaling 與媒體、低延遲而且雙向的傳輸是很自然的選擇。這一條沒有上線成果可看,讀它的方式是作者對下一步的判斷。

What this enables:按文章的說法,換掉客戶端傳輸的這一階段,下游不用改,換來的是一個已經在 Instagram Live 上跑過持平測試的 MoQ 起點,可以往端到端的直播傳遞與播放延伸。