當「閒置」其實不閒置:Linux 核心的一項最佳化如何變成 QUIC 缺陷 (★ 101 分)
Cloudflare 的文章追蹤了其開源 QUIC(常用於 HTTP/3 的 UDP 基礎傳輸協定;UDP 是 User Datagram Protocol,使用者資料包協定)實作 quiche 中的一個 CUBIC 缺陷。CUBIC 是 Linux 預設的壅塞控制演算法(CCA, Congestion Control Algorithm),用壅塞視窗(cwnd, congestion window)限制同一時間已送出但尚未確認的資料量,並在偵測到封包遺失時降低傳送速率、網路穩定時逐步升高。Cloudflare 在本機 HTTP/3 下載測試中設定 10 MB 檔案、RTT(Round-Trip Time,往返延遲)10 ms、前 2 秒隨機遺失 30% 封包,之後完全停止遺失;下載原本應在 4 到 5 秒完成,卻有約 60% 在 10 秒逾時前無法完成。
透過 qlog(QUIC 連線記錄格式)分析後,團隊發現封包遺失在第 2 秒後已歸零,但 cwnd 仍卡在 2700 位元組,也就是兩個完整封包;壅塞狀態在恢復期與壅塞避免期之間於約 6.7 秒內切換 999 次,週期約 14 ms,接近連線 RTT。這表示問題不是網路仍在掉包,而是每輪 ACK(Acknowledgement,接收端確認封包)到達後,bytes_in_flight 會降到 0,伺服器再送出下一批兩個封包,讓 CUBIC 狀態機在 ACK 節奏下自我觸發。相同情境改用 Reno(另一種以封包遺失判斷壅塞的控制演算法)時成功率為 100%,也確認這是 CUBIC 特有問題。
根本原因來自 2017 年 Linux 核心為 CUBIC 加入的「閒置期間調整」:應用程式停止傳送一段時間後,不應讓 CUBIC 的 epoch(成長曲線的時間基準)累積過大的時間差,否則 cwnd 會被不合理地拉高;正確作法是把時間基準往後平移。quiche 在 2020 年移植這段邏輯時,少了 Linux 隨後補上的保護;又因 QUIC 在使用者空間執行,沒有 TCP(Transmission Control Protocol,傳輸控制協定)核心層的傳送開始事件,只能在送封包時用 bytes_in_flight == 0 判斷是否閒置。當 cwnd 降到兩個封包的底限,每個 ACK 都會讓 bytes_in_flight 短暫歸零,但這其實是壅塞受限而非閒置;舊邏輯把 now - last_sent_time、約一個 RTT 的間隔誤算成閒置時間,把恢復起點推到未來,使 CUBIC 認定自己一直在恢復期並跳過 cwnd 成長。
修正方式是新增 last_ack_time,ACK 到達時更新它;送封包且 bytes_in_flight 為 0 時,改以 last_ack_time 與 last_sent_time 中較晚者作為真正的閒置起點,避免把整個 RTT 當成閒置時間。修正後,恢復邊界不再追著下一次送出時間跑,cwnd 會沿著預期的 CUBIC 曲線成長,100 次測試恢復全數成功,下載約 4 到 5 秒完成。Cloudflare 的結論是,「閒置」在小視窗與 ACK 節奏下不容易定義,最小 cwnd 是容易被忽略的角落案例;這次排查花了大量 qlog 儀表化與視覺化分析,但最後只改了很少的程式碼。文章也提到 quiche 持續用模組化設計調校 BBRv3(Bottleneck Bandwidth and Round-trip propagation time v3,估算瓶頸頻寬與延遲的模型式壅塞控制)。
Hacker News 討論的主要反應偏向工程流程檢討:高分留言認為這更像是「把 Linux 核心程式碼搬進 quiche,卻沒有完整理解並追上後續修補」的教訓;也有人指出,自行維護使用者空間實作時,必須更密切追蹤上游核心變更,因為這類實作的審視者通常少於 Linux 核心。另有讀者質疑 Cloudflare 為何 CUBIC 仍是 quiche 預設,認為在資料中心大頻寬環境下,CUBIC 從嚴重壅塞恢復太慢,BBR 類方法可能更合適;但也有人補充 quiche 並不是從既有 Linux QUIC 改寫而來,因為 quiche 約 2018 年就開始,Linux 核心 QUIC 實作到 2025 年才出現,較早的核心層 QUIC 例子主要是 Microsoft 的 MsQuic。其他討論則指出原文未定義 CCA、部分段落有明顯 AI 風格,並提出若短時間反覆退避,演算法是否應自動降低退避幅度與成長速度,以追求更高總傳輸量。
👥 12 則討論、評論 💬
https://news.ycombinator.com/item?id=48116064