全部產品
Search
文件中心

ApsaraDB for MongoDB:排查分區叢集添加新分區後資料不均衡的原因

更新時間:Nov 21, 2025

為應對業務增長,向 MongoDB 分區叢集添加新分區是常見的水平擴容操作。但有時新分區添加後,資料和負載並未自動均衡,導致新分區近乎空閑,舊分區壓力依舊,擴容效果未達預期。本文提供一套系統的診斷流程,從均衡器狀態、資料分布特徵兩個層面,定位元據不均衡問題,提供可參考的解決方案,以恢複叢集的均衡狀態。

診斷思路

資料能否在新舊分區間均衡分布,核心依賴於 MongoDB 的內建均衡器(Balancer)。均衡器通過自動遷移資料區塊(Chunk)來工作。因此,排查資料不均衡問題,應圍繞均衡器的工作狀態及影響其遷移決策的因素展開。

推薦採用以下自上而下的分層排查邏輯:

  1. 檢查均衡器狀態:確認資料移轉的基礎——均衡器是否已啟用並處於活動狀態。

  2. 分析資料可遷移性:排查是否存在因集合自身屬性(如未分區、資料量未達均衡觸發閾值、含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說明均衡進行中中。

分析資料可遷移性

排查是否存在因集合自身屬性而無法被均衡器自動遷移的資料。

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 數量過多,增加中繼資料管理的開銷,並可能引發過於頻繁的均衡活動。應根據集合的總資料量大小和增長率合理設定。