網誌

一個龐大嘅對話點樣可以令我哋嘅 API 停頓

九月二十八日,大約喺一次發布後 17 分鐘,我哋兩部正式環境 API 伺服器同時耗盡記憶體,並重新啟動。之後大約五分鐘,負載平衡器冇任何健康嘅伺服器可以將流量送去。我哋第一個修正係將 API 嘅記憶體加倍。真正嘅原因係當日稍後發現嘅:開啟某一個特定對話時,API 會喺一個請求入面載入 53 MB 嘅訊息。而家對話歷史會以頁面形式載入,頁面大小按容量同數量兩方面限制,而嗰個對話會以 5 MB 嘅第一頁開啟,而唔係崩潰。

發生咗咩事

我哋嘅 API 以容器形式運行喺兩部伺服器上,前面有一個負載平衡器。每個容器嘅記憶體上限係 384 MiB。平時佔用 130 至 140 MiB,所以呢個上限睇落好似有好大空間。

兩個容器同時撞到呢個上限,並以「JavaScript heap out of memory」崩潰。Heap 係 Node.js 程序用嚟處理物件嘅記憶體。因為兩部伺服器同時失效,就再冇地方可以路由請求。對於一個有兩部伺服器嘅架構嚟講,呢種係最壞嘅故障:冗餘只有喺故障唔會同時發生時先有用。

第一個修正:多啲空間

時間點指向發布。發布之後,被發布中斷嘅代理回合會被恢復,並重新儲存佢哋嘅記錄,而呢陣突如其來嘅工作量,好似係記憶體暴增嘅明顯來源。

所以第一個改動係將 API 嘅上限由 384 MiB 提升到 768 MiB。我哋當日就喺兩部伺服器上即時套用,並將設定 commit 到部署設定入面,確保下一次部署唔會悄悄將 384 改返回去。手動喺伺服器上做嘅修正,如果下一次部署又將佢還原,就係同一個故障再次出現嘅常見原因。

增加記憶體係一個合理嘅安全餘裕,但並唔係一個解釋。

真正原因:一個請求,71 MB

到當日稍後,我哋用一份該對話嘅副本,以生產環境嘅舊記憶體上限重現咗崩潰。副本單獨就令 API 崩潰,完全冇涉及任何部署。

當你開啟一個項目,網頁應用會向 API 要求該對話嘅歷史。個端點會完整返回最新嘅 30 則訊息。對大部分對話嚟講,30 則訊息好細。但係對呢個對話,最新嘅 30 則訊息喺資料庫入面有 53 MB,回應就有 71 MB。代理嘅幾個回覆,每個有 4 至 10 MB,主要係子代理嘅記錄:主代理為咗並行處理任務其中一部分而啟動嘅幫手代理,佢哋嘅完整工作會連同回覆一齊儲存。

建立嗰一個回應,所用嘅 heap 比 API 擁有嘅更多。而且每次開啟或者重新整理該項目,都會重新送出呢個請求。一個對話就可以令佢所落到嘅任何一部伺服器崩潰,而由於頁面可以被重新整理,佢甚至可以觸及兩部伺服器。

「30 則訊息」一直都唔係一個真正嘅大小限制。佢只係一個按數量計嘅限制,而喺正常對話入面,恰好都係細數。

修正:頁面按大小以及數量限制

而家歷史嘅一頁係 30 則訊息,或者大約 4 MB 嘅訊息,以先到者為準。

流程分兩步:

  1. 先問大小。 API 向資料庫要求最新訊息嘅 ID 同儲存大小,但唔攞內容。呢個查詢無論訊息幾大都好快。
  2. 只載入放得落嘅嘢。 由最新到最舊逐條加入,直到下一則訊息會令該頁超過 4 MB 為止。然後先完整載入嗰啲資料列。

有兩條規則令呢個做法安全:

  • 最新嘅一則訊息無論幾大都一定會放入該頁。 如果一頁返回零則訊息,對話就會卡住,冇辦法向上捲去睇更早嘅訊息。
  • 冇任何嘢被截斷。 冇訊息會被截短或者丟棄。對話嘅其餘部分會透過「向上捲動查看較早訊息」控制項載入,每次一頁,而較早嘅訊息以前本身就係用呢個控制項。

我哋仲喺另一個地方發現咗同一種模式。當你停止一個回合,API 會檢查最近幾個回覆,睇有冇空嘅需要清理。佢以往會完整載入八則訊息去做呢個檢查。喺一個有幾 MB 回覆嘅對話入面,呢個就係同一個問題嘅細版本。而家佢只會問資料庫一個是或否嘅問題:呢則訊息有冇任何內容?而唔會載入內容本身。

點樣驗證

用嗰份對話副本,喺舊嘅 384 MiB 上限下運行:

  • 第一頁係 5 MB。以前同一個請求會令 API 崩潰。
  • 五個瀏覽器分頁同時開啟同一個對話,記憶體峰值為 154 MB。
  • 一路捲回去睇整個對話,會返回全部 190 則訊息,每則只出現一次,次序正確。

選頁邏輯亦有單元測試,包括一個用該對話最新訊息嘅真實大小建立嘅測試。第一頁會返回一則訊息,並表示仲有更多,而下一頁會由其後一則開始。

我哋學到嘅嘢

  • 數量唔等於大小限制。 任何返回「最新 N 個」而該事物可以無限增長嘅端點,都需要一個位元組預算。數據遲早會找到個缺口。
  • 最明顯嘅嫌疑人未必係真正原因。 部署係一個真實嘅觸發因素,而增加記憶體亦確實值得。但崩潰喺冇部署嘅情況下都能重現;如果冇重現,我哋就會停在增加記憶體嗰一步。
  • 兩部伺服器會因為收到相同請求而一齊失效。 冗餘可以防止一部伺服器死機,但防止唔到一個會擊倒任何被觸及伺服器嘅請求,尤其係用戶好自然會靠重新整理頁面去重試嘅請求。
  • 喺設定入面修正,而唔單止喺機器上修正。 記憶體增加嘅改動當日就 commit 入部署設定,所以下一次部署都保留咗。

呢個係我哋近期第二個關於代理輸出嘅記憶體故事。第一個係一個狀態標籤排隊咗 690 MB 嘅字串。如果你喺 Mythex 用子代理,可以喺文件入面了解佢哋點樣運作。

Mythex · Pricing · 用 AI 建造任何嘢 · 網站及應用程式範本 · Mythex 比較 · AI 應用程式開發詞彙表 · 免費 AI 應用程式開發工具 · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(台灣)