一張 5 MiB 的海報先被解開了:圖片服務的記憶體,是一頁被排好規則的介面
從一張 5 MiB 圖片撐爆 Go 服務的記憶體事故看起,觀察一套圖片處理服務的資源規則如何被設計成一頁可閱讀的介面。
這篇文章回顧的原始事件發表於 2026 年 10 月 8 日,作者是掘金上一位從前端轉向 Go 後端的開發者。文章的起點是一場具體的故障:一個高併發圖片壓縮服務的進程被作業系統強制終止,原因是記憶體耗盡(OOM)。運營同事上傳了一張 5 MiB 的活動海報,配備數 GB 記憶體的伺服器隨即崩潰。作者當時已在程式碼裡寫了限流,同一時間最多允許兩張圖壓縮,其餘排隊,但記憶體仍然被喫光了。
這篇文章本身寫得像一篇工程懸案,但換一個角度看,它其實在描述一套資源規則如何被設計、被誤讀、再被重新設計。這正是介面設計的老問題,只是介面從螢幕換成了記憶體與磁碟。
5 MiB 是包裝,137 MiB 才是內容
第一個被拆穿的直覺,是「檔案多大,記憶體就喫多大」。5 MiB 只是圖片在磁碟上的壓縮體積。一旦進入全量分析、高精度色彩轉換,或轉交下遊編碼器需要完整幀緩衝的階段,圖像處理管線就得把這個壓縮包裹拆開,在記憶體裡按 8 位元 RGBA 格式展開成完整的像素矩陣。
作者給了一組可以按計算機驗證的數字:一張 6000 乘 6000 像素的海報,裸像素矩陣是 6000 乘 6000 乘 4 位元組,約 137 MiB。這是基礎帳,色彩轉換、中間圖與編碼工作區還會再把峯值往上推,單張大圖的記憶體峯值可以放大到數百 MiB 甚至 1 到 2 GB。
來源也誠實地標出了邊界:像 libvips 這類現代圖像庫支援按需分塊的流式處理,並不會無腦展開完整像素。麻煩出在需要全域資訊的環節。這一段的價值在於它把「直覺尺寸」與「實際佔用」之間的換算關係攤開來,讓讀者第一次能對帳。這種把隱藏成本換算成可驗證數字的做法,本身就是一種排版。
限流為什麼不夠:被漏排的前後頁
第二個被拆穿的直覺更有意思。作者原以為限制同時壓縮兩張圖就夠了,但真正拖垮服務的事件大多發生在壓縮那兩秒鐘之外:慢速網路讓連線與請求名額被吊住半分鐘;五十張圖傳完後在佇列裡等待,光是原始檔堆在記憶體就喫掉上百 MiB;壓完的結果遇到下載極慢的用戶,成品大檔滯留在伺服端緩衝區;更隱蔽的是用戶傳到一半取消,半截的暫存檔爛在磁碟裡,累積到把磁碟塞爆。
換句話說,作者原本的限流只管了流程中最昂貴的一格,漏排了前後所有頁面。這和介面設計裡只優化首屏、不管進場與退場的錯誤是同一種結構:資源帳被切在錯誤的邊界上。
加工店的隱喻:三種名額,三種邊界
文章的解法用了一個相當完整的隱喻,把 Go 圖片服務比作一家圖片加工店,掌櫃手裡捏著三樣資源。第一是接待號,也就是請求準入名額:只要請求還在店內,不論處於上傳、排隊、壓縮或回傳哪個階段,都佔一個號,滿了就不再放人進來。第二是機器位,也就是處理槽:普通小件佔一臺,遇到數千萬像素的巨幅大件,必須等兩臺機器全騰空,讓它獨佔包場。第三是置物架,也就是磁碟暫存區:邊收邊往架上碼,收貨前先翻帳本確認空間夠不夠。
這個隱喻的設計感在於它把三種性質不同的資源(連線名額、運算槽、磁碟空間)各自畫出了明確的邊界與計量單位,而且誠實承認了「大件要包場」這種非線性的資源曲線。多數限流設計的失敗,正在於假設每個請求的資源消耗是均等的;這套規則沒有這樣假設。
值得一提的是,這種「隱藏的真實成本與表面尺寸脫鉤」的結構,在前端效能領域也有對應的案例。我們先前分析過一張 40KB 的 Banner 救不回 4 秒的 LCP,瀏覽器判定最大渲染元素的規則同樣反直覺:眼睛看到的大圖,和機器認定的最大元素,經常不是同一個。工程直覺與系統實際計費規則之間的落差,是跨端共通的設計課題。
這篇文章作為一種介面
從寫作本身看,這篇掘金文章的排版策略也值得記一筆。它把抽象的記憶體治理拆成三個懸案再逐一解開,每個懸案先給直覺、再給反例、最後給可計算的數字,並且全程用一個加工店的空間隱喻來承載技術規則。讀者記不住信號量的參數,但記得住「接待號、機器位、置物架」。
這裡的收束觀點是:OOM 不是記憶體不夠,是資源規則沒有被完整設計。原作者的限流之所以失效,是因為他把服務的生命週期切得太短,只算了加工那一格的帳。當一張 5 MiB 的海報能展開成 137 MiB 的像素矩陣,工程師真正要設計的已經不是壓縮演算法,而是一套讓每個請求從進門到清場全程可計量、可拒絕、可回收的治理介面。這套介面長在記憶體與磁碟裡,但它的設計原則,和任何一頁被排好的畫面沒有兩樣。