【Node Weekly】 How we 30x'd our Node parallelism by Evan Limanto

【Node Weekly】 How we 30x'd our Node parallelism by Evan Limanto

How We 30x-ed Our Node Parallelismnode

 

安全地提升生產節點服務的並行性的最佳方法是什麼?這是幾個月前個人團隊須要回答的問題。web

 

咱們爲咱們的銀行集成服務運行4000個節點容器(或「工人」)。該服務最初的設計使每一個工做人員一次只能處理一個請求。這種設計減小了意外阻塞事件循環的集成的影響,並容許咱們忽略不一樣集成中資源使用的可變性。可是,因爲咱們的總容量被限制在4000個併發請求,因此係統沒有適當地擴展。大多數請求都是網絡綁定的,因此若是咱們能找出如何安全地提升並行性,就能夠提升咱們的容量和成本。正則表達式

 

在咱們的研究中,咱們找不到一個好的劇原本描述節點服務從「無並行」到「大量並行」。所以,咱們制定了本身的計劃,它依賴於仔細的計劃、良好的工具和可觀察性,以及健康的調試。最終,咱們可以將並行度提升30倍,這至關於每一年節省約30萬美圓的成本。這篇文章將概述咱們如何提升節點工做人員的性能和效率,並描述咱們在這個過程當中吸收的教訓。promise

 

爲何咱們投資並行

若是咱們不使用並行性就走到了這一步,這可能會讓人驚訝——咱們是如何作到的?只有10%的Plaid的數據拉涉及到一個用戶誰在場,並將他們的賬戶連接到一個應用程序。其他的是在用戶不在場的狀況下進行的按期事務更新。經過在負載平衡層中添加邏輯,將用戶當前請求優先於事務更新,咱們能夠以犧牲事務新鮮度爲代價處理1000%或更多的API峯值。安全

 

雖然這種權宜之計已經奏效了很長時間,但咱們知道,有幾個痛點最終會影響咱們的服務可靠性:服務器

 

1. 來自咱們客戶的API請求峯值愈來愈大,咱們擔憂一個協調的流量峯值會耗盡咱們的工做人員的能力。websocket

2. 銀行請求的延遲峯值一樣致使咱們的工做人員容量降低。因爲銀行基礎設施的可變性,咱們對出站請求設置了保守的超時,完整的數據拉取可能須要幾分鐘。若是一家大型銀行的延遲時間猛增,就會有愈來愈多的員工被困在等待迴應。網絡

3. ECS的部署時間變得很是緩慢,即便咱們提升了部署速度,咱們也不想進一步增長集羣的大小。併發

咱們認爲提升並行性是消除應用程序瓶頸和提升服務可靠性的最佳方法。做爲反作用,咱們相信咱們能夠下降基礎設施成本,實施更好的可觀測性和監測,這兩項措施都將在將來帶來回報。socket

 

咱們如何可靠地推出更改

工具和可觀測性

咱們有一個自定義的負載平衡器,它將請求路由到咱們的節點工做器。每一個節點工做機運行一個gRPC服務器來處理請求,並使用Redis將其可用性通告回負載平衡器。這意味着添加並行性就像更改幾行邏輯同樣簡單:工人應該繼續公佈可用性,直處處理N個正在運行的請求(每一個請求都有本身的承諾),而不是在接到任務後當即報告不可用。

 

不過,事情沒那麼簡單。咱們的首要目標是在任何一次發佈中保持可靠性,咱們不能僅僅增長並行性。咱們預計此次發佈將特別危險:它將以難以預測的方式影響咱們的CPU使用率、內存使用率和任務延遲。因爲Node的V8運行時在事件循環上處理任務,咱們主要關心的是,咱們可能在事件循環上作了太多的工做,從而下降了吞吐量。

 

爲了下降這些風險,咱們確保在第一個並行工做人員部署到生產環境以前,已經準備好了如下工具和可觀察性:

 

1. 咱們現有的麋鹿堆棧有足夠的現有日誌進行特別的調查。

2. 咱們添加了一些普羅米修斯指標,包括:

  • 使用process.memoryUsage()的V8堆大小
  • 使用gc stats包的垃圾收集統計信息
  • Task latency statistics任務延遲統計信息,按銀行集成類型和並行級別分組,以可靠地測量並行性如何影響咱們的吞吐量。

3. 咱們建立了Grafana儀表板來測量並行性的效果。

4. 更改應用程序行爲而沒必要從新部署咱們的服務是很是關鍵的,所以咱們建立了一組啓動功能標誌來控制各類參數。以這種方式調整每一個工做人員的最大並行度,最終使咱們可以快速迭代並找到最佳並行度值——在一分鐘內。

5. 爲了瞭解應用程序各個部分的CPU時間分佈,咱們在生產服務中構建了flamegraphs。

  • 咱們使用了0x包,由於節點檢測很容易集成到咱們的服務中,生成的HTML可視化是可搜索的,而且提供了很好的詳細程度。
  • 咱們添加了一個分析模式,其中一個工做人員將開始啓用0x,並將產生的痕跡寫入S3退出。而後,咱們能夠從S3下載這些日誌,並在本地使用x查看它們—僅可視化。/flamegraph
  • 咱們一次只運行一個工人的配置文件。CPU分析提升了資源利用率並下降了性能,咱們但願將此影響隔離到單個工做進程。

 

啓動卷展欄

在這項初步工做完成以後,咱們爲咱們的「並行工做者」建立了一個新的ECS集羣。這些是使用SunCurkLy特徵標誌來動態設置它們的最大並行性的工做者。

 

咱們的推出計劃包括逐漸將愈來愈多的流量從舊集羣路由到新集羣,同時密切監視新集羣的性能。在每一個通訊量級別上,咱們將調整每一個工做機上的並行性,直到它儘量高,而不會致使任務延遲或其餘指標的任何降級。若是咱們發現問題,咱們能夠在幾秒鐘內動態地將流量路由回舊集羣。

 

正如所料,在這一過程當中出現了一些挑戰。咱們須要調查和解決這些棘手的問題,以便有意義地提升咱們的並行性。這纔是真正有趣的開始!

 

部署,調查,重複

增長節點的最大堆大小

當咱們開始推出過程時,咱們開始獲得警告,咱們的任務已經退出了一個非零代碼-一個吉祥的開始。咱們深刻基巴納發現了相關的log (associated log):

 

 

這讓咱們想起了過去咱們經歷過的內存泄漏,其中V8在吐出相似的錯誤信息後會意外地退出。它有不少意義:更大的任務並行性致使更高的內存使用率。

 

咱們假設從默認1.7GB增長節點最大堆大小可能會有所幫助。爲了解決這個問題,咱們開始運行最大堆大小設置爲6GB(--max old space size=6144命令行標誌)的節點,這是一個任意較高的值,仍然適合咱們的EC2實例。令咱們高興的是,這修復了生產中的「分配失敗」消息。

 

識別內存瓶頸

隨着內存分配問題的解決,咱們開始看到並行工做機上的任務吞吐量很低。咱們儀表盤上的一張圖表馬上就很顯眼。這是並行工做進程的每一個進程堆使用狀況:

 

 

 

其中一些線不斷增加直到它們達到最大堆尺寸-壞消息!

 

咱們在Prometheus中使用了系統度量來排除文件描述符或網絡套接字泄漏是根本緣由。咱們最好的猜想是,GC在舊對象上發生的頻率不夠,致使工做進程在處理更多任務時積累更多分配的對象。咱們假設這會下降咱們的吞吐量,以下所示:

 

  • 工人接收新任務並執行某些工做
  • 在執行任務時,一些對象被分配到堆上
  • 因爲(還沒有肯定)激發和忘記操做未完成,在任務完成後將維護對象引用
  • 垃圾收集變得更慢,由於V8須要掃描堆上的更多對象
  • 因爲V8實現了一個stop-the-world GC,新任務不可避免地將得到更少的CPU時間,從而下降工做線程的吞吐量

咱們搜索了咱們的代碼 for fire-and-forget operations,也被稱爲「浮動承諾」。這很簡單:咱們只是在代碼行中尋找禁用了無浮動承諾linter規則的代碼行。有一種方法特別吸引了咱們的注意。它生成了一個叫compressandploaddingpayload的調用,不用等待結果。這個調用彷佛很容易在任務完成後很長時間內繼續運行。

 

 

 

咱們想驗證咱們的假設,即這些浮動承諾(floating promises)是瓶頸的主要來源。假設咱們跳過了這些對系統正確性並不重要的調用,那麼任務延遲會獲得改善嗎?如下是臨時刪除對postTaskDebugging的調用後堆的使用狀況

 

 

 

答對 了!咱們的並行工做線程上的堆使用如今在很長一段時間內保持穩定。

 

彷佛有一個compressandploaddingpayload調用的「backlog」,隨着相關任務的完成,這個調用會慢慢創建起來。若是一個worker接收任務的速度超過了它修剪這個backlog的速度,那麼在內存中分配的任何對象都將永遠不會被垃圾收集,從而致使咱們在前面的圖中觀察到的堆飽和。

 

咱們開始懷疑是什麼讓這些浮動的承諾如此緩慢。咱們不肯意從代碼中永久刪除compressandploaddingpayload,由於這對於幫助工程師調試本地機器上的生產任務相當重要。從技術上講,咱們能夠在完成任務以前等待此調用,從而消除浮動承諾,從而解決問題。然而,這會給咱們正在處理的每一個任務增長一個很是重要的延遲量。

 

保留這個潛在的解決方案做爲備份計劃,咱們決定研究代碼優化。咱們怎麼能加快行動?

 

修復S3瓶頸

compressandploaddingpayload中的邏輯很容易理解。咱們壓縮調試數據,由於它包含網絡流量,因此可能會很是大。而後咱們將壓縮數據上傳到S3。

 

 

 

不認爲套接字是瓶頸,由於Node的默認HTTPS代理將maxSockets設置爲無窮大。然而,咱們最終在AWS節點文檔中發現了一些使人驚訝的東西:S3客戶端將maxSockets從無窮大減小到了50。不用說,這不是最直觀的行爲。

 

因爲咱們已經讓咱們的工人完成了50個併發任務,上傳步驟在等待S3的套接字時遇到了瓶頸。經過對S3客戶端初始化的如下更改,咱們改進了上載延遲:

 

 

加快JSON序列化

咱們的S3改進減緩了堆大小的增長,但並無徹底解決問題。另外一個明顯的罪魁禍首是:根據咱們的計時指標,前面代碼中的壓縮步驟有時會長達4分鐘。這比每一個任務平均4秒的延遲要長得多。不相信這一步會花這麼長時間,咱們決定運行本地基準測試並優化這段代碼。

 

壓縮包括三個步驟(使用節點流限制內存使用):JSON字符串化、zlib壓縮和base64編碼。咱們懷疑咱們使用的第三方字符串庫——bfj——多是問題所在。咱們編寫了一個腳本,在各類基於流的字符串庫上執行一些基準測試(請參見此處的代碼)。結果發現,這個包的名字是大友好JSON,它畢竟不是很友好。看看咱們從實驗中獲得的兩個結果:

 

 

結果使人吃驚。即便是最小的測試,bfj也比另外一個包JSONStream慢5倍左右。咱們很快用JSONStream替換了bfj,並當即觀察到性能的顯著提升。

 

減小垃圾收集時間

隨着咱們的記憶問題的解決,咱們開始關注在相同類型的銀行集成中,並行工做人員和單個工做人員之間的任務延遲比率。這是一個蘋果對蘋果的比較,咱們的平行工人是如何表現的,因此一個接近1的比率給了咱們信心,進一步推出交通的平行工人。在這一點上,咱們的Grafana儀表盤是這樣的:

 

 

 

注意,有些比率高達8:1,即便是在至關低的平均並行度下(此時大約爲30)。咱們知道,咱們的銀行集成沒有執行CPU密集型工做,咱們的容器也沒有受到任何其餘瓶頸的限制,咱們能夠想到。因爲沒有更多的線索,咱們尋找在線資源來優化節點的性能。儘管這類文章不多,但咱們偶然發現這篇博客文章詳細介紹了做者如何在一個節點進程上達到60萬個併發websocket鏈接。

 

尤爲是--nouse idle通知的特殊用法引發了咱們的注意。咱們的節點進程是否花費了太多時間來執行GC?方便的是,gc stats包讓咱們能夠看到垃圾收集的平均時間:

 

 

 

討論Node中不一樣類型GC的技術細節,但這是一個很好的參考資料。實質上,清理常常運行,以清理堆中節點「新空間」區域中的小對象。

 

所以,垃圾收集在咱們的節點進程上運行得太頻繁了。咱們能夠禁用V8垃圾收集並本身運行它嗎?有沒有辦法減小垃圾收集的頻率?原來前者是不可能的,但後者是!咱們能夠經過在節點中突破「半空間」的限制來增長新空間的大小(--max semi space size=1024命令行標誌)。這容許在V8運行清理以前分配更多的短時間對象,從而減小GC的頻率:

 

 

 

又一次勝利!增長新的空間大小致使垃圾回收時間急劇降低,從30%降低到2%。

 

優化CPU使用

通過這些工做,咱們對結果感到滿意。在並行工做機上運行的任務的延遲幾乎與在大約20個並行工做機上運行的任務的延遲相等。在咱們看來,咱們已經解決了全部的瓶頸問題,但咱們仍然不徹底知道生產中的哪些操做實際上在減緩咱們的速度。因爲咱們沒有懸而未決的成果,咱們決定調查分析咱們工人的CPU使用狀況。

 

咱們在一個並行工做器上生成了一個flamegraph,瞧,咱們獲得了一個整潔的交互式viz,咱們能夠在本地使用它。花邊新聞:它佔用磁盤60MB!下面是咱們在x火焰圖中搜索「logger」時看到的:

 

 

 

teal突出顯示的條表示至少15%的CPU時間用於生成工做日誌。咱們最終將這一時間減小了75%——儘管咱們是如何作到的,這是一個值得再發表一篇博文的話題。(提示:它涉及正則表達式和大量屬性枚舉。)

 

在這最後一點優化以後,咱們可以支持每一個工人最多30個並行任務,而不會影響任務延遲!

 

成果和教訓

遷移到並行工做器使咱們每一年的EC2實例開銷減小了大約30萬美圓,並大大簡化了咱們的體系結構。咱們如今在生產中運行的容器減小了大約30倍,並且咱們的系統對於來自客戶的外部請求延遲或API流量峯值的增長更加健壯。

 

咱們在並行化銀行集成服務時學到了不少:

 

1. 永遠不要低估系統低層次度量的重要性。在發佈過程當中可以監控GC和內存統計是很是重要的。

2. Flamegraphs火焰圖太棒了!如今咱們已經檢測了它們,咱們能夠輕鬆診斷系統中的其餘性能瓶頸。

3. 理解節點運行時使咱們可以編寫更好的應用程序代碼。例如,瞭解V8對象分配和垃圾收集模型是儘量重用對象的一個動機。有時須要直接與V8交互或使用節點命令行標誌來開發一個強大的心智模型。

4. 確保閱讀系統每一層的文檔!咱們信任maxSockets上的節點文檔,但在咱們發現AWS包重寫了默認節點行爲以前,咱們花了不少時間研究。每個基礎設施項目彷佛都有這樣一個「抓住」的時刻。

咱們但願這篇博客文章爲優化節點並行性和安全地推出基礎設施遷移提供了一些有用的策略。若是你對這類具備挑戰性的工程項目感興趣,咱們將招聘!

相關文章
相關標籤/搜索