為應對業務增長,向 MongoDB 分區叢集添加新分區是常見的水平擴容操作。但有時新分區添加後,資料和負載並未自動均衡,導致新分區近乎空閑,舊分區壓力依舊,擴容效果未達預期。本文提供一套系統的診斷流程,從均衡器狀態、資料分布特徵兩個層面,定位元據不均衡問題,提供可參考的解決方案,以恢複叢集的均衡狀態。
診斷思路
資料能否在新舊分區間均衡分布,核心依賴於 MongoDB 的內建均衡器(Balancer)。均衡器通過自動遷移資料區塊(Chunk)來工作。因此,排查資料不均衡問題,應圍繞均衡器的工作狀態及影響其遷移決策的因素展開。
推薦採用以下自上而下的分層排查邏輯:
檢查均衡器狀態:確認資料移轉的基礎——均衡器是否已啟用並處於活動狀態。
分析資料可遷移性:排查是否存在因集合自身屬性(如未分區、資料量未達均衡觸發閾值、含jumbo chunk)而導致均衡器無法遷移的資料。這是資料量不均的常見直接原因。
實施步驟
診斷均衡器(Balancer)狀態
目的:確認作為資料移轉核心組件的均衡器是否已啟用、正在運行,並配置了合理的使用中視窗。Chunk 的遷移完全依賴均衡器來完成,如果均衡器未正常工作,資料將無法從舊分區遷移到新分區。
1. 檢查均衡器是否已全域啟用
檢查方法
在
mongosh中執行sh.getBalancerState()返回false,則表示均衡器未啟用。解決方案
若均衡器未啟用,參考管理MongoDB均衡器Balancer開啟。
2. 檢查均衡器是否在使用中視窗內
均衡器僅在設定的使用中視窗期(Active Window)內執行 Chunk 遷移。如果目前時間不在使用中視窗內,均衡器將不會工作。
檢查方法
db.getSiblingDB("config").settings.find({_id:"balancer"})返回樣本:
{ _id: 'balancer', activeWindow: { start: '02:00', stop: '04:00' } }如果目前時間不在
activeWindow內,則均衡器不會工作或者系統判斷無需均衡。解決方案:修改使用中視窗
如果使用中視窗設定不當(例如過短或與業務高峰重疊),建議將視窗期設定為時間長度足夠的業務低峰時段。
例如,設定視窗期為每日淩晨 2 點到 6 點:
db.getSiblingDB("config").settings.updateOne( { _id: "balancer" }, { $set: { activeWindow : { start : "02:00", stop : "06:00" } } }, { upsert: true } )
3. 確認是否處於初始資料均衡過程
剛添加分區後,系統需要時間將存量資料從舊分區遷移至新分區,此期間資料不均衡是正常現象,資料均衡所需時間與資料量級相關。
檢查方法
通過
sh.isBalancerRunning()確認均衡進度。// 檢查均衡器是否正在運行遷移任務 sh.isBalancerRunning()
如果 sh.isBalancerRunning() 返回 true說明均衡進行中中。
解決方案
耐心等待均衡過程自然完成,可參考如何確認增加或刪除Shard節點的進度瞭解過程。
分析資料可遷移性
排查是否存在因集合自身屬性而無法被均衡器自動遷移的資料。
1. 檢查是否存在大量未分區集合
一個資料庫內可以同時存在分區集合和非分區集合。非分區集合的資料會完整存放在其主分區(Primary Shard)上,均衡器無法對其進行遷移。如果大型集合未被分區,其資料將永遠無法分布到新分區上。
檢查方法
使用以下指令碼統計指定資料庫中,未分區集合佔用的儲存空間。該指令碼會找出所有未分區的集合,並累加其儲存大小。
說明該指令碼會遍曆庫內所有集合并執行
stats()命令,可能對資料庫造成效能影響。為降低風險,指令碼內建了sleep(200)延時。如果庫內集合數量比較多,建議將sleep時間長度進一步提升到2000或者更高來避免可能的業務影響。// 指令碼:統計指定資料庫中未分區集合的儲存佔用 // 將 "myDB" 替換為需要檢查的資料庫名稱 var dbName = "myDB"; var unshardSize = 0; var totalSize = 0; var unshardedCollections = [ ]; db.getSiblingDB(dbName).getCollectionNames().forEach(function(collName) { var stats = db.getSiblingDB(dbName).getCollection(collName).stats(); // 根據 stats.sharded 欄位判斷並累加儲存大小 if (!stats.sharded) { unshardSize += (stats.storageSize || 0); unshardedCollections.push(dbName + "." + collName); } totalSize += (stats.storageSize || 0); // 每次查詢後短暫休眠,避免對資料庫造成過大壓力 sleep(200); }); print("--- " + dbName + " ---"); print("Unsharded Collections Count: " + unshardedCollections.length); print("--------------------"); print("Unsharded Storage Size (Bytes): " + unshardSize); print("Total Storage Size (Bytes): " + totalSize); print("--------------------"); print("Unsharded Collections List:"); printjson(unshardedCollections);解決方案:參考設定資料分區以充分利用Shard效能。
2. 檢查分區集合資料量是否過小(MongoDB 6.0+)
從 MongoDB 6.0.3開始。均衡器只在集合中分區間最巨量資料量差值達到一定閾值(預設為chunk size的3倍)後才開始遷移。如果一個分區集合的總資料量小於均衡閾值,均衡器不會對其進行處理,導致其資料集中在單個分區上。
檢查方法
使用
db.collection.getShardDistribution()查看集合在各分區上的分布。如果一個分區集合的資料大部分集中在單個分區上,且分區間最巨量資料量差值遠小於均衡觸發閾值,則可能屬於此情況。解決方案
對於這類小資料量的分區集合,可適當調小其
chunkSize,觸發 Chunk 拆分和遷移。// 樣本將 test_db.test_coll 集合的 chunkSize 調整為 16MB,請根據集合資料分布情況調整合適的chunkSize db.adminCommand({ configureCollectionBalancing: "test_db.test_coll", chunkSize: 16 // 單位為 MB,6.0+版本的預設值為128MB。 })等待遷移完成後可通過
db.collection.getShardDistribution()查看資料分布情況是否符合預期。
操作影響與風險
均衡效能開銷:資料均衡過程中的 Chunk 遷移(
moveChunk)和範圍刪除(RangeDeleter)會消耗源分區和目標分區的 CPU 及 I/O 資源。為避免影響線上業務,建議將均衡器使用中視窗設定在業務低峰期。ChunkSize 配置風險:在MongoDB 6.0之前的版本,將
chunkSize設定得過小,可能導致 Chunk 數量過多,增加中繼資料管理的開銷,並可能引發過於頻繁的均衡活動。應根據集合的總資料量大小和增長率合理設定。