記某百億級mongodb叢集資料過期效能最佳化實踐

y123456yzzyz發表於2021-07-06

記某百億級mongodb 叢集資料過期效能最佳化實踐

某百億級mongodb 業務只儲存近期 7 天的資料,由於資料量大、流量高,資料過期刪除點比較集中,同時不能錯峰方式解決問題,因此如何利用最小物理成本來滿足業務需求就成為了本叢集效能最佳化的難點。

透過幾輪和業務配合調優,包括儲存引擎調優、資料刪除方式調優、業務錯峰讀寫等,最終完美解決了業務痛點,達到ms 級業務讀寫訪問。

關於作者

前滴滴出行技術專家,現任OPPO文件資料庫 mongodb負責人,負責 oppo千萬級峰值 TPS/十萬億級資料量文件資料庫 mongodb核心研發及運維工作,一直專注於分散式快取、高效能服務端、資料庫、中介軟體等相關研發。後續持續分享《 MongoDB核心原始碼設計、效能最佳化、最佳運維實踐》, Github賬號地址:y123456yz

1.  業務背景

線上某業務資料量約100 億,白天為寫流量高峰期,峰值寫約 14W/s ,如下圖所示:

   業務每天白天生產資料,同時凌晨批次拉取過去幾天資料做大資料分析,整個叢集只儲存最近七天資料。單條資料約800 位元組,如下所示:

1.   {  
2.         "_id" : ObjectId("608592008bd3dad61675b491"),  
3.         "deviceSn" : "xxxxxxxxxxxxx",  
4.         "itemType" : 0,  
5.         "module" : "xxxxxx",  
6.         "userId" : "xxxxx",  
7.         "callTimes" : NumberLong(2),  
8.         "capacityAdd" : NumberLong(0),  
9.         "capacityDelete" : ”xxxxxxxx”,  
10.         "capacityDownload" :”xxxxxxxxxxxx”,  
11.         "capacityModify" : ”xxxxxxxxxxxx”,  
12.         "createTime" : NumberLong("1619366400003"),  
13.         "expireAt" : ISODate("2021-05-02T22:53:45.497Z"),  
14.         "numAdd" : NumberLong(2),  
15.         "numDelete" : NumberLong(345),  
16.         "numDownload" : NumberLong(43),  
17.         "numModify" : NumberLong(3),  
18.         "osVersion" : "xxxx",  
19.         "reversedUserId" : "xxxxx",  
20.         "updateTime" : NumberLong("1619366402106")  
21. }

1.  mongodb 資源評估及部署架構

透過和業務對接梳理,該叢集規模及業務需求總結如下:

①  資料量百億級

②  單條資料800 位元組, 100 億條預計 7.5T 資料

③  讀寫分離

④  所有資料只保留七天

2.1 mongodb 資源評估

     分片數及儲存節點套餐規格選定評估過程如下:

記憶體評估

我司都是容器化部署,以以網經驗來看,mongodb 對記憶體消耗不高,歷史百億級以上 mongodb 叢集單個容器最大記憶體基本上都是 64Gb ,因此記憶體規格確定為 64G

分片評估

     務流量峰值10W/s 多,預計需要 3 個分片支援讀寫。

磁碟評估

    100 億資料 7.5T ,由於 mongodb 預設有高壓縮,預計真實磁碟佔用 2.5~3T 左右。三個分片,一個分片剛好 1T

CPU 規格評估

由於容器排程套餐化限制,因此CPU 只能限定為 16CPU( 實際上用不了這麼多 CPU)

mongos 代理及 config server 規格評估

此外,由於分片叢集還有mongos 代理和 config server 複製集,因此還需要評估 mongos 代理和 config server 節點規格。由於 config server 只主要儲存路由相關後設資料,因此對磁碟、 CUP MEM 消耗都很低; mongos 代理只做路由轉發只消耗 CPU ,因此對記憶體和磁碟消耗都不高。最終,為了最大化節省成本,我們決定讓一個代理和一個 config server 複用同一個容器,容器規格如下:

8CPU/8G 記憶體 /50G 磁碟,一個代理和一個 config server 節點複用同一個容器。

分片及儲存節點規格總結: 4 分片 /16CPU 64G 記憶體、 1T 磁碟。

mongos config server 規格總結: 8CPU/8G 記憶體 /50G 磁碟

3.2 叢集部署架構

   該業務資料不是很重要,為了節省成本,因此我們採用2+1 模式部署,也就是: 2mongod+1arbiter 模式,同城機房部署,部署架構圖如下圖所示:

考慮到資料重要性不高,透過2mongod+1arbiter 模式即可滿足使用者要求,同時可以最大化節省成本。

4. 效能最佳化過程

該叢集最佳化過程按照如下兩個步驟最佳化:業務使用叢集前的效能最佳化、業務使用過程中的效能最佳化。

業務提前建好查詢對應的最優索引,同時建立過期索引:

db.xxx.createIndex( { "createTime": 1 }, { expireAfterSeconds: 604800} )

4.1 業務使用叢集前的效能最佳化

和業務溝通確定,業務每條資料都攜帶有一個裝置標識userId ,同時業務查詢更新等都是根據 userId 維度查詢該裝置下面的單條或者一批資料,因此片建選擇 userId

分片方式

為了充分雜湊資料到3 個分片,因此選擇 hash 分片方式,這樣資料可以最大化雜湊,同時可以滿足同一個 userId 資料落到同一個分片,保證查詢效率。

預分片

mongodb 如果分片片建為 hashed 分片,則可以提前做預分片,這樣就可以保證資料寫進來的時候比較均衡的寫入多個分片。預分片的好處可以規避非預分片情況下的 chunk 遷移問題,最大化提升寫入效能。

sh.shardCollection("xxx_xx.xxx", { userId :"hashed"}, false, { numInitialChunks: 8192} )

就近讀

客戶端增加secondaryPreferred 配置,優先讀從節點。

禁用enableMajorityReadConcern

禁用該功能後ReadConcern majority 將會報錯, ReadConcern majority 功能注意是避免髒讀,和業務溝通業務沒該需求,因此可以直接關閉。

mongodb 預設使能了 enableMajorityReadConcern ,該功能開啟對效能有一定影響,參考:

MongoDB readConcern 原理解析

儲存引擎cacheSize 規格選擇

單個容器規格:16CPU 64G 記憶體、 7T 磁碟,考慮到全量遷移過程中對記憶體壓力,記憶體碎片等壓力會比較大,為了避免 OOM ,設定 cacheSize=42G

5.2 業務使用過程中遇到的問題及效能最佳化

5.2.1 第一輪最佳化:儲存引擎最佳化

業務高峰期主要是資料寫入和更新,記憶體髒資料較多,當髒資料比例達到一定比例後使用者讀寫請求對應執行緒將會阻塞,使用者執行緒也會去淘汰記憶體中的髒資料page ,最終寫效能下降明顯。

wiredtiger 儲存引擎 cache 淘汰策略相關的幾個配置如下 :

    由於業務全量遷移資料是持續性的大流量寫,而不是突發性的大流量寫,因此eviction_target eviction_trigger eviction_dirty_target eviction_dirty_trigger 幾個配置用處不大,這幾個引數閥值只是在短時間突發流量情況下調整才有用。

   但是,在持續性長時間大流量寫的情況下,我們可以透過提高wiredtiger 儲存引擎後臺執行緒數來解決髒資料比例過高引起的使用者請求阻塞問題,淘汰髒資料的任務最終交由 evict 模組後臺執行緒來完成。

   全量大流量持續性寫儲存引擎最佳化如下:

db.adminCommand( { setParameter : 1, "wiredTigerEngineRuntimeConfig" : "eviction=(threads_min=4, threads_max=20)"})

5.2.2 第一輪最佳化後存在的問題

經過儲存引擎後臺執行緒數的調優後,資料寫入和更新瓶頸解決,寫入和更新過程時延都很平滑。但是隨著一週後開始資料過期,業務寫開始大量抖動,如下所示:

從上圖可以看出平均時延極端情況下甚至達到了幾百ms ,這啥業務完全接受不了的。透過 mongostat 監控發現如下現象:

主節點mongostat 監控統計

從上面的監控可以看出,三個分片的每個主節點只有4000 左右的寫更新操作 ( 注意:實際上還有 4 萬左右的 delete 操作,由於主節點過期 delete 不會統計,因此只能透過從節點檢視,詳見後面分析,實際單個分片主節點寫和刪除操作 4.4W/s) ,寫流量很低。但是,監控中的髒資料比例持續性的超過 20% ,超過 20% 後業務的請求就需要進行髒資料淘汰,最終造成業務請求阻塞抖動。

透過前面的分析可以看出,業務正常峰值寫入3 個分片差不多 10W/s 。一星期後,七天前的資料需要過期,這時候過期刪除的 ops 也需要 delete 刪除 10w/S ,由於這時候新資料同樣按照 10w/s 寫入,因此叢集就需要支援 20w/s ops 操作才能支援。

顯然,3 個分片支援不了持續性的 20w/s 左右的 ops 操作,因此如何不擴容情況下支撐業務需求將是一大難點。

為何ttl 過期主節點沒用 delete 統計

上圖為mongodb 單機模組架構圖,主節點預設啟用一個 TTLMonitor 執行緒,藉助查詢引起模組實時掃描過期索引,然後把滿足條件的資料刪除。整個刪除過程沒有走 command 命令處理模組,而命令計數操作只會在 command 模組計數,因此主節點的過期刪除不會有 delete 操作。

   命令處理模組處理流程及其計數統計詳見:mongodb核心原始碼模組化設計與實現專欄

   更多mongodb 模組化原始碼設計實現詳見:

 

5.3 第二輪最佳化 ( 過期刪除放凌晨低峰期 )- 不可行

從前面的分析可以看出,我們三個分片支撐不了持續性20W/S 的更新和刪除操作,因此我們考慮業務改造使用方式,把過期刪除確定到凌晨低峰期。

但是業務上線後出現其他的問題,由於業務凌晨會持續性的批次拉取分析過去幾天的資料,如果過期和批次讀資料疊加到一起,嚴重影響業務查詢效率。最終,該方案不可行,如果不擴容,則需要其他方案。

5.4 第三輪方案最佳化 ( 天維度建表,過期刪表 )

為了不增加成本,同時3 個分片又支撐不了 20W/s 的讀寫刪除等導致,為了儘量透過 3 個分片不擴容條件下來滿足使用者需求,因此轉變方式,透過刪表的方式來避免過期,具體實現如下:

①  業務改造程式碼,以天為單位建表,每天的資料存到對應的表中。

②  建表後業務啟用預分片功能,確保資料均衡分佈到三個分片。

③  業務儲存8 天資料,第九天凌晨刪除第一天的表

     該方案業務時延統計對應收益如下:

如上圖所示,透過過期方式的最佳化,最終問題徹底解決,並且讀寫時延控制在0.5ms-2ms

6 最佳化總結

透過前面的一系列最佳化,最終沒有擴容,並且解決了業務過期和大量資料寫入更新引起的時延抖動問題,總體收益如下:

最佳化前時延:經常幾百ms 抖動

最佳化後時延:0.5-2ms


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69984922/viewspace-2779880/,如需轉載,請註明出處,否則將追究法律責任。

相關文章