方格精選

從你的 Node.js 專案裡找出 Memory leak,及早發現、及早治療!

更新於 發佈於 閱讀時間約 10 分鐘

前陣子,公司已經上線的產品持續出現效能問題,服務運作一段時間後會慢得讓人難以忍受,甚至超過 node 記憶體上限而直接中止,當時專案還沒實裝監測系統,只能邊調整邊測試,搞得團隊焦頭爛額。這時同事講出明燈般的一句話:「該不會是傳說中的 Memory leak 吧?」

經過工程師們同心協力,總算是解決了這個問題,看到記憶體使用圖不再步步高升的那一刻,真的很感動 ⋯⋯ 這篇文章希望能留下記錄,並說明:

  • memory leak 的起因
  • 如何找出造成 memory leak 的程式碼
  • 我們遇到的實例,與解決方式



小心!Memory leak 離我們並不遙遠!

Memory leak(記憶體流失),是指程式運行的過程中,不再使用的記憶體空間沒有正常被釋放,持續佔用空間而造成的記憶體浪費。如果這種狀況不斷發生,就會使可用的記憶體越來越少,而降低電腦的效能,最後可能導致程式崩潰

在 JavaScript 裡,記憶體回收的工作是交由自動化的 Garbage Collection 來完成,它就像記憶體裡的清道夫,會判斷不再使用的記憶體並將其回收。

那很好啊!代表我不需要擔心記憶體流失的問題了,對吧?

raw-image

並不是這樣的。Garbage collection 的簡單流程是:定期從根物件 (root,在瀏覽器中是 window,node 則是 global) 開始往下探詢每一個子節點,並清除沒有被探詢到、或是沒有被探詢物件參考的物件,也就是所謂「無法到達的物件 (unreachable objects)」,而在程式中,要製造出一個「可被探詢到的垃圾資料」簡直輕而易舉。

raw-image

舉例來說,我們寫一個簡單的 Server:

raw-image

Code

每次收到請求,都會使 requests 的資料量成長,而 request 又存在於 global scope,屬於可從根探詢到的程式碼,因此它不會被清除,如果 requests 是沒有用的資料,就會造成記憶體空間的浪費,甚至隨著請求數增多而用盡記憶體空間。

其它像是 timerclosure 的誤用也會造成 memory leak,我們這邊先不詳述。畢竟,要直接從整個專案的程式碼中翻找出 memory leak 簡直是大海撈針 ⋯⋯ 我覺得更有效率的方式應該是使用工具來找出不斷增長的資料,縮小範圍後再修正造成問題的程式碼

raw-image



使用 DevTool 找出病灶!

Chrome 提供的 DevTool 除了能監測運行在瀏覽器中的程式外,也能監測運行在本地端的 Node.js 程式。如果你使用 Node.js 運行 API Server,或是你的前端使用像 Next.js 的 SSR 框架(它會常駐一個 Node Server),就能使用 Devtool 監測 Heap 的使用狀況。

我們先以上面的簡單 Server 為例,使用 --inspect flag 來啟動它。

node --inspect app.js

在 console 裡,你會看到 Node.js 已經幫我們打開一個監聽 9229 port 的 debugger。

raw-image

接著,打開你的 Chrome 並輸入 chrome://inspect ,你應該能夠在 Remote Target 裡找到 app.js,按下 inspect 來打開 DevTool。

raw-image

切換到 Memory 頁籤,我們會在下方看到目前運行中的 VM instance,按下 Take snapshot,它就會幫我們分析此刻的 heap 使用狀況,並將結果儲存在左側邊欄。

raw-image
raw-image

從這張圖中,我們可以看到:

  • Constructor:此物件的建構子 (或 DevTool 幫我們進行的分類)。
  • Distance:從根 (root) 開始探訪的深度。
  • Shallow Size:物件本身佔用的記憶體量(bytes),通常 shallow size 很大的都是 String 或是裝著 Primitive Type Data 的陣列,如果是物件裡存著 reference 則不會被算到 shallow size 裡面。
  • Retained Size:物件本身佔用的記憶體空間,加上依賴此物件的所有資料所佔用的記憶體量(bytes)。文件裡有一句淺顯易懂的解釋:你刪除這個物件後,他總共會釋放的記憶體量,因此在查找 memory leak 問題時,我們會以這個欄位為判斷點。

點開左邊的箭頭,就能繼續往下探詢,相當方便。我們先將物件以 Retained Size 排序,沒意外的話佔用最多的應該是 (compiled code),因為我們引用了一些套件,這些物件也會被存在 Heap 中。

接下來,我們就要手動重現 memory leak



使用 K6 進行壓力測試

如果你的專案是 API 或 SSR Server,就可以用發送大量請求的方式來重現 memory leak,這邊我推薦使用 K6,也可以直接使用你熟悉的壓力測試服務。

我們先寫一個簡單的測試。一般來說,測試單一 Endpoint 可能無法重現問題,你可能需要將 API Server 所有提供的(或是你懷疑的) Endpoints 測試一遍,就能知道是哪一段程式碼造成問題。

raw-image

Code


接著從終端機啟動壓力測試,使用下面的參數:

  • duration:執行測試的時間
  • vus:同時測試的虛擬使用者數量 (virtual users)
k6 run --duration 2m --vus 100 request.js
raw-image

等它跑完。

raw-image

回來觀察 DevTool 的變化

每次跑完壓力測試,就回到 DevTool 快照一下,如果你發現 heap 不斷增長且沒有回到正常數字的話,那八成是抓到兇手了

raw-image

觀察兩個 snapshot 之間的變化,在這個例子中不難發現是 global scope 底下的資料不斷成長。實際狀況下,leak 的記憶體量可能很少,你得發送更多請求數才能看出端倪。點開之後發現 DevTool 連變數名稱都告訴你了,接下來,只需要把問題修正,就能順利解決 memory leak 問題了。

raw-image
raw-image
raw-image



我們實際遇到的狀況


看到這裡,你可能心想:

「我是一個有紀律的程式設計師,才不會隨便污染 global scope!」

「我用的套件都是精挑細選,至少都 10,000 stars 欸,這些套件應該不會隨便就出現 memory leak 問題吧?」

沒想到呀沒想到,就真的被我們遇見了來自套件的 memory leak。下圖是經過 V6 壓測兩分鐘前後的 snapshot,可以看到 instance 這項資料成長了近一倍,且隨著請求數而增長,不會自動被回收。

raw-image
raw-image

從圖中我們看見資料型態 Map,以及子元素的資料型態 HashArrayMapNode,這些都來自 immutable.js 套件。而在我們專案中,相依 immutable 的套件只有 Facebook 開源的文本編輯器 draft.js

經過搜尋,發現我們不是第一個遇到此問題的人,早在 2020 就有人回報這個issue,trace code 就會發現 draft.js 有兩項 global scope 資料是沒有主動回收的:

DraftEntity.js

在這個檔案中,我們首先看到一個宣告在 global scope 的資料 instance。

raw-image


接著,從這個模組的 __add 方法,我們看見 instance 資料量會不斷增長。

raw-image


但它並沒有提供一個清除 instance 的方法,還好有一個 setter 可以讓我們利用,只要將它初始化就能順利觸發 JavaScript 的回收機制,把所有子節點佔用的記憶體都釋放掉。

Entity.__loadWithEntities(Map());


CharacterMetadata.js

這裡 我們一樣看到不斷增長的 global scope 物件 pool

raw-image


比較麻煩的是,這項資料連個 setter 都沒有,我們只能手動增加清除方法將其初始化:

raw-image


老實說,如果 draft.js 是單純在瀏覽器上執行,應該是不會有問題的,因為使用上並不會一直開新的文本編輯器 instance。只是我們的 Next.js Server 在收到請求並編譯時,就意外地將初始化的資料留一份在 Server 端了(而且也完全用不到這項資料)。隨著使用者在網站上打開文本編輯器,記憶體空間也逐漸變少,最後導致程式崩潰。

修正完程式碼後,終於讓記憶體使用量回歸正常,看看下面這張圖,真的是讓人法喜充滿啊!如果你也因 memory leak 困擾的話,就用 DevTool 來找出病灶吧,希望這篇記錄有幫助到大家,謝謝一起苦惱的隊友們 XD。

raw-image



延伸閱讀










留言
avatar-img
留言分享你的想法!
Ofa Hsueh-avatar-img
2021/08/15
原來戰犯是 draft 啊!
avatar-img
桌遊拌飯 Boardgame Podcast
119會員
58內容數
五位玩家用聲音跟你聊桌遊。 近期開始努力經營 YouTube, 希望我們的內容可以為你帶來歡樂, 歡迎一同入席,享受遊戲。
2025/03/09
德國政治迎來大轉折。基民盟主席梅爾茨與社民黨達成共識,將推動 5,000 億歐元特別預算並修憲,將佔 GDP 1% 以上的國防支出排除於債務煞車限制之外。此舉違背基民盟選前承諾,也挑戰德國長期奉行的財政紀律。
Thumbnail
2025/03/09
德國政治迎來大轉折。基民盟主席梅爾茨與社民黨達成共識,將推動 5,000 億歐元特別預算並修憲,將佔 GDP 1% 以上的國防支出排除於債務煞車限制之外。此舉違背基民盟選前承諾,也挑戰德國長期奉行的財政紀律。
Thumbnail
2025/02/17
昨晚讀完《臺灣漫遊錄》,書中描寫日治時期,作家青山千鶴子受總督府邀請來臺,與本島通譯王千鶴同行,透過美食與旅行建立微妙情感。
Thumbnail
2025/02/17
昨晚讀完《臺灣漫遊錄》,書中描寫日治時期,作家青山千鶴子受總督府邀請來臺,與本島通譯王千鶴同行,透過美食與旅行建立微妙情感。
Thumbnail
2025/02/03
在德國朋友家過了聖誕節,餐點是波蘭特色,祝福的傳統也是波蘭式的。
Thumbnail
2025/02/03
在德國朋友家過了聖誕節,餐點是波蘭特色,祝福的傳統也是波蘭式的。
Thumbnail
看更多
你可能也想看
Thumbnail
家中修繕或裝潢想要找各種小零件時,直接上網採買可以省去不少煩惱~看看Sylvia這回為了工地買了些什麼吧~
Thumbnail
家中修繕或裝潢想要找各種小零件時,直接上網採買可以省去不少煩惱~看看Sylvia這回為了工地買了些什麼吧~
Thumbnail
👜簡單生活,從整理包包開始!我的三款愛用包+隨身小物清單開箱,一起來看看我每天都帶些什麼吧🌿✨
Thumbnail
👜簡單生活,從整理包包開始!我的三款愛用包+隨身小物清單開箱,一起來看看我每天都帶些什麼吧🌿✨
Thumbnail
創作者營運專員/經理(Operations Specialist/Manager)將負責對平台成長及收入至關重要的 Partnership 夥伴創作者開發及營運。你將發揮對知識與內容變現、影響力變現的精準判斷力,找到你心中的潛力新星或有聲量的中大型創作者加入 vocus。
Thumbnail
創作者營運專員/經理(Operations Specialist/Manager)將負責對平台成長及收入至關重要的 Partnership 夥伴創作者開發及營運。你將發揮對知識與內容變現、影響力變現的精準判斷力,找到你心中的潛力新星或有聲量的中大型創作者加入 vocus。
Thumbnail
逐一檢視它們,我好像看見自己多年來到底累積了什麽。
Thumbnail
逐一檢視它們,我好像看見自己多年來到底累積了什麽。
Thumbnail
「元件削減」刪除系統中的特定元件,把此元件的有用功能轉移到系統其它剩餘的元件或是超系統(周遭環境中)的元件,實現成本節省、製程簡化、易用易修、提升系統整體效益的目標。本文中大學旅館系的師資削減案例,可突顯了削減對成本控制的實際情況。元件削減常用在專利迴避方面;因元件少於別人專利,可以迴避對方專利。
Thumbnail
「元件削減」刪除系統中的特定元件,把此元件的有用功能轉移到系統其它剩餘的元件或是超系統(周遭環境中)的元件,實現成本節省、製程簡化、易用易修、提升系統整體效益的目標。本文中大學旅館系的師資削減案例,可突顯了削減對成本控制的實際情況。元件削減常用在專利迴避方面;因元件少於別人專利,可以迴避對方專利。
Thumbnail
上次我分享了「整理信箱的技巧」,我們生長在物資充裕跟資訊爆炸的年代,更應該要學會斷捨離的真諦。
Thumbnail
上次我分享了「整理信箱的技巧」,我們生長在物資充裕跟資訊爆炸的年代,更應該要學會斷捨離的真諦。
Thumbnail
整理前,先找出根本亂源 一文中我們已經認識了亂的狀態可能造成的原因。我們快速複習一下: 不良習慣 害怕浪費 沒有系統 失控購物 囤積行為 接下來我們來一一破解,如何根除這些亂源。要記得達成最終目的往往不是一蹴可幾,而是需要反覆的練習。
Thumbnail
整理前,先找出根本亂源 一文中我們已經認識了亂的狀態可能造成的原因。我們快速複習一下: 不良習慣 害怕浪費 沒有系統 失控購物 囤積行為 接下來我們來一一破解,如何根除這些亂源。要記得達成最終目的往往不是一蹴可幾,而是需要反覆的練習。
Thumbnail
文件管理中常见的问题 缺少秩序 缺少安全 文件管理的第二个问题便是缺少安全性。我们的文件很容易由于各种误删等操作被删除。其中,可能拥有各种宝贵数据。本着数据无价的理念,我们便又找人协助,或者自己动手,使用一些数据恢复工具尝试找回丢失的数据。 缺少分享 让文件管理嵌入使用场景 参考文献
Thumbnail
文件管理中常见的问题 缺少秩序 缺少安全 文件管理的第二个问题便是缺少安全性。我们的文件很容易由于各种误删等操作被删除。其中,可能拥有各种宝贵数据。本着数据无价的理念,我们便又找人协助,或者自己动手,使用一些数据恢复工具尝试找回丢失的数据。 缺少分享 让文件管理嵌入使用场景 参考文献
Thumbnail
為什麼家裡東西總是堆積如山?東西為什麼會自己無性生殖般變成大量囤積物? 明明知道用不到,卻又不想丟,寧可把珍貴的生活空間用來存放老舊的囤積物,不論是捨不得或是懶得整理所造成的囤積狀態,不斷拖延下去使得生活品質日漸下降,也在不知不覺間影響到個人的心理狀態。 上週看到日本節目稱充滿雜物的房子為「垃圾屋」
Thumbnail
為什麼家裡東西總是堆積如山?東西為什麼會自己無性生殖般變成大量囤積物? 明明知道用不到,卻又不想丟,寧可把珍貴的生活空間用來存放老舊的囤積物,不論是捨不得或是懶得整理所造成的囤積狀態,不斷拖延下去使得生活品質日漸下降,也在不知不覺間影響到個人的心理狀態。 上週看到日本節目稱充滿雜物的房子為「垃圾屋」
Thumbnail
隨著時間的流逝,電腦裡無用的檔案會越來越多,就算是每天只是上網、收信與打字,電腦還是會不斷的產生暫存檔。這些暫存檔累積的時間一久,就會變成電腦速度變慢與不穩定的潛在殺手。
Thumbnail
隨著時間的流逝,電腦裡無用的檔案會越來越多,就算是每天只是上網、收信與打字,電腦還是會不斷的產生暫存檔。這些暫存檔累積的時間一久,就會變成電腦速度變慢與不穩定的潛在殺手。
Thumbnail
對於電腦空間不足,你是否也感覺很頭痛呢? 其實電腦用久了就會產生很多無用的垃圾檔案,只要把它刪除就能清理出很多空間 在這個文章中將教你最實用的清理垃圾方法,讓你的電腦再也不被垃圾占滿
Thumbnail
對於電腦空間不足,你是否也感覺很頭痛呢? 其實電腦用久了就會產生很多無用的垃圾檔案,只要把它刪除就能清理出很多空間 在這個文章中將教你最實用的清理垃圾方法,讓你的電腦再也不被垃圾占滿
Thumbnail
工程師的專注力是非常寶貴的資源,和產出能力息息相關,如果因為儲存空間的不足導致必須時常費心去清除零碎檔案釋放空間,對工程師本人和產品本身都是極大的損失。所幸網路上找得到許多清除垃圾檔案的方法,在這邊我就重點擷取幾個對 iOS 工程師而言比較有感的方式。
Thumbnail
工程師的專注力是非常寶貴的資源,和產出能力息息相關,如果因為儲存空間的不足導致必須時常費心去清除零碎檔案釋放空間,對工程師本人和產品本身都是極大的損失。所幸網路上找得到許多清除垃圾檔案的方法,在這邊我就重點擷取幾個對 iOS 工程師而言比較有感的方式。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News