將ApsaraDB for MongoDB分區叢集執行個體升級到6.0版本(小版本7.0.1及以上)或更高版本後,通過sh.status()查看分區資訊時,會發現各Shard上的Chunk數量明顯不均衡,甚至每個Shard只有1個Chunk。本文說明這一現象的原因,並介紹如何正確判斷資料均衡情況。
問題現象
使用ApsaraDB for MongoDB分區叢集執行個體時,部分使用者在將執行個體升級到6.0版本(小版本7.0.1及以上)後,通過sh.status()查看分區資訊,發現各Shard上的Chunk數量不均衡,與升級前每個Shard上Chunk數量大致相等的情況明顯不同。常見的現象包括:
-
6.0版本(小版本7.0.1起):各Shard上的Chunk數量差異較大,不再像舊版本那樣大致相等。
-
7.0及以上版本:各Shard上可能只有1個Chunk。
通常會產生以下疑問:
-
資料是不是沒有均衡分布?
-
Balancer是不是沒有正常工作?
-
是不是升級出了什麼問題?
這是升級後的正常行為,資料仍然是均衡分布的,無需擔心。新版本不再以Chunk數量作為均衡標準,而是以實際資料大小為依據。
原因說明
舊版本行為(6.0版本小版本7.0.1之前)
在ApsaraDB for MongoDB6.0版本(小版本7.0.1)之前,分區叢集的資料均衡機制如下:
-
每個Chunk預設最大64 MB。
-
當資料寫入使Chunk超過64 MB時,會自動分裂(AutoSplit)為多個小Chunk。
-
Balancer定期檢測各Shard上的Chunk數量,發現數量差異超過閾值後執行遷移(
moveChunk)。 -
最終各Shard上會有大量Chunk,且數量大致相等。
這種機制下,您通過sh.status()能看到每個Shard上有多個Chunk,可以直觀地感受到資料是均衡分布的。
6.0版本變化(小版本7.0.1起,對應社區版6.0.3)
ApsaraDB for MongoDB從6.0版本(小版本7.0.1)開始,對應社區版MongoDB 6.0.3的Balancer改進,進行了以下重大變更:
-
取消自動分裂(AutoSplit):Chunk寫滿後不再自動分裂,僅在遷移前進行必要的分割。
-
均衡判斷標準改變:從基於Chunk數量改為基於實際資料大小。
-
遷移閾值調整:當不同分區上同一集合的資料大小差異超過3倍ChunkSize(ApsaraDB for MongoDB6.0版本起預設ChunkSize為128 MB,即差異超過384 MB)時觸發遷移。
-
術語變更:Chunk概念弱化,改稱為Range;
moveChunk被moveRange取代。 -
集合分區前不再需要執行
sh.enableSharding()命令。
影響:由於取消了AutoSplit且不再以Chunk數量為均衡依據,各Shard上的Chunk數量可能不相等,這是正常現象。更多資訊,請參見sh.enableAutoSplit()。
7.0版本新增:AutoMerger自動合并
從MongoDB 7.0開始,引入了AutoMerger功能:
-
Balancer會自動將同一Shard上可合并的連續Chunk合并在一起。
-
AutoMerger作為均衡操作的一部分在後台運行,預設啟用。
-
每隔
autoMergerIntervalSecs秒進行一次自動合并。 -
如果設定了均衡視窗,AutoMerger僅在該視窗期間運行。
影響:在7.0及以上版本中,由於AutoMerger的存在,各Shard上的Chunk最終可能合并為1個Chunk。這同樣是正常行為。更多資訊,請參見AutoMerger。
為什麼做這些改變?
舊版本基於Chunk數量做均衡判斷存在以下問題:
|
問題 |
說明 |
|
Jumbo Chunk |
分區鍵區分度不足時,同一Chunk中所有文檔的分區鍵相同,Chunk無法分割,導致各Shard的Chunk數量相同但磁碟空間使用不同。 |
|
寫入傾斜 |
範圍分區鍵含遞增屬性時,新資料集中寫入一個Shard。 |
|
資源消耗 |
頻繁的AutoSplit和moveChunk消耗大量CPU資源,影響業務效能。 |
新版本基於實際資料大小做均衡判斷,更加準確地反映資料分布情況,同時減少了不必要的Chunk分裂和遷移操作。
版本對應關係
|
ApsaraDB for MongoDB本 |
對應社區版 |
Balancer行為 |
|
4.4版本 |
MongoDB 4.4 |
舊行為:AutoSplit + Chunk數量均衡 |
|
5.0版本 |
MongoDB 5.0 |
舊行為:AutoSplit + Chunk數量均衡 |
|
6.0版本(小版本7.0.0) |
MongoDB 6.0.0 |
舊行為:AutoSplit + Chunk數量均衡 |
|
6.0版本(小版本7.0.1起) |
MongoDB 6.0.3 |
取消AutoSplit + 資料大小均衡(Chunk數量可能不均衡) |
|
7.0及以上版本 |
MongoDB 7.0+ |
取消AutoSplit + 資料大小均衡 + AutoMerger自動合并Chunk |
版本前後對比
|
對比項 |
6.0版本(小版本7.0.1之前) |
6.0版本(小版本7.0.1起) |
7.0及以上版本 |
|
自動分裂(AutoSplit) |
開啟,Chunk寫滿自動分裂 |
關閉 |
關閉 |
|
自動合并(AutoMerger) |
無 |
無 |
開啟,自動合并Chunk |
|
均衡判斷依據 |
Chunk數量 |
實際資料大小 |
實際資料大小 |
|
各Shard的Chunk數量 |
多個,數量大致相等 |
不均衡,數量可能不相等 |
可能合并為1個 |
|
遷移閾值 |
Chunk數量差異≥1(4.2+) |
資料大小差異超過3倍ChunkSize |
資料大小差異超過3倍ChunkSize |
|
|
分區前必須執行 |
6.0+可跳過 |
可跳過 |
|
查看均衡方式 |
|
|
|
實測資料
以下為使用YCSB工具分別向4.4、6.0、8.0版本的ApsaraDB for MongoDB分區叢集執行個體各插入5000萬條資料後的實測結果。
4.4版本(舊行為)
執行sh.status(),重點關注輸出中的chunkMetadata欄位——每個Shard上有435個Chunk,數量相等,這是舊版本AutoSplit自動分裂的結果:
mongos> sh.status()
shardingVersion
{ _id: 1, clusterId: ObjectId('69fbfda6dec76b373f5eda81') }
---
shards
[
{
_id: 'd-uf6d*****04f784',
host: 'mgset-100239890/10.**.**.1*9:3005,20.**.**.1*9:3002',
state: 1
},
{
_id: 'd-uf6f*****39f74',
host: 'mgset-100***91/20.**.**.**7:3002,20.**.**.**8:3002',
state: 1
}
]
---
active mongoses
[ { '4.4.30': 2 } ]
---
autosplit
{ 'Currently enabled': 'yes' }
---
balancer
{
'Currently enabled': 'yes',
'Failed balancer rounds in last 5 attempts': 0,
'Currently running': 'no',
'Migration Results for the last 24 hours': { '947': 'Success' }
}
---
databases
[
{
database: {
_id: 'ycsb',
primary: 'd-uf6f*****39f74',
partitioned: true
},
collections: {
'ycsb.test1': {
shardKey: { _id: 'hashed' },
unique: false,
balancing: true,
chunkMetadata: [
{ shard: 'd-uf6d*****04f784', nChunks: 435 },
{ shard: 'd-uf6f*****39f74', nChunks: 435 }
],
chunks: [
'too many chunks to print, use verbose if you want to force print'
],
tags: []
}
}
}
]
執行db.test1.getShardDistribution(),關注每個Shard的data(資料大小)和chunks(Chunk數量)欄位:
mongos> use ycsb
switched to db ycsb
mongos> db.test1.getShardDistribution()
Shard d-uf6d*****04f784 at mgset-100239890/10.**.**.1*9:3005,20.**.**.1*9:3002
{
data: '27.2GiB',
docs: 25014674,
chunks: 435,
'estimated data per chunk': '64.04MiB',
'estimated docs per chunk': 57504
}
---
Shard d-uf6f*****39f74 at mgset-100239891/20.**.**.**7:3002,20.**.**.**8:3002
{
data: '36.23GiB',
docs: 33313293,
chunks: 435,
'estimated data per chunk': '85.29MiB',
'estimated docs per chunk': 76582
}
---
Totals
{
data: '63.44GiB',
docs: 58327967,
chunks: 870,
'Shard d-uf6d*****04f784': [
'42.88 % data',
'42.88 % docs in cluster',
'1KiB avg obj size on shard'
],
'Shard d-uf6f*****39f74': [
'57.11 % data',
'57.11 % docs in cluster',
'1KiB avg obj size on shard'
]
}
6.0版本(小版本7.0.1起)
執行sh.status(),關注chunkMetadata欄位——各Shard上的Chunk數量明顯不均衡(216 vs 1):
mongos> sh.status()
shardingVersion
{ _id: 1, clusterId: ObjectId('69fbfdc420578638757c1d06') }
---
shards
[
{
_id: 'd-uf610*****d8904',
host: 'mgset-100239905/20.**.**.**1:3002,20.**.**.**3:3002',
state: 1,
topologyTime: Timestamp({ t: 1778122310, i: 2 })
},
{
_id: 'd-uf6aa*****67314',
host: 'mgset-100239903/20.**.**.**5:20.**.**.**6:3002',
state: 1,
topologyTime: Timestamp({ t: 1778122310, i: 9 })
}
]
---
active mongoses
[ { '6.0.27': 2 } ]
---
autosplit
{ 'Currently enabled': 'yes' }
---
balancer
{
'Currently running': 'no',
'Currently enabled': 'yes',
'Failed balancer rounds in last 5 attempts': 0,
'Migration Results for the last 24 hours': { '216': 'Success' }
}
---
databases
[
{
database: {
_id: 'ycsb',
primary: 'd-uf6aa*****67314',
partitioned: false,
version: {
uuid: UUID('4ee45231-9585-4dba-8f34-f624860e1a79'),
timestamp: Timestamp({ t: 1778122930, i: 1 }),
lastMod: 1
}
},
collections: {
'ycsb.test2': {
shardKey: { _id: 'hashed' },
unique: false,
balancing: true,
allowMigrations: true,
chunkMetadata: [
{ shard: 'd-uf610*****d8904', nChunks: 216 },
{ shard: 'd-uf6aa*****67314', nChunks: 1 }
],
chunks: [
'too many chunks to print, use verbose if you want to force print'
],
tags: []
}
}
}
]
執行db.test2.getShardDistribution(),關注每個Shard的data佔比——雖然Chunk數量差異巨大(216 vs 1),但資料仍然均衡分布(50.31% vs 49.68%):
mongos> use ycsb
switched to db ycsb
mongos> db.test2.getShardDistribution()
Shard d-uf6aa*****67314 at mgset-100239903/20.**.**.**5:20.**.**.**6:3002
{
data: '27.36GiB',
docs: 32285618,
chunks: 1,
'estimated data per chunk': '27.36GiB',
'estimated docs per chunk': 32285618
}
---
Shard d-uf610*****d8904 at mgset-100239905/20.**.**.**1:3002,20.**.**.**3:3002
{
data: '27.01GiB',
docs: 24841944,
chunks: 216,
'estimated data per chunk': '128.09MiB',
'estimated docs per chunk': 115009
}
---
Totals
{
data: '54.38GiB',
docs: 57127562,
chunks: 217,
'Shard d-uf6aa*****67314': [
'50.31 % data',
'56.51 % docs in cluster',
'1KiB avg obj size on shard'
],
'Shard d-uf610*****d8904': [
'49.68 % data',
'43.48 % docs in cluster',
'1KiB avg obj size on shard'
]
}
8.0版本(含AutoMerger)
遷移剛完成時,執行sh.status(),關注chunkMetadata欄位——各Shard上仍有多個Chunk(55 vs 1):
mongos> sh.status()
shardingVersion
{ _id: 1, clusterId: ObjectId('69fbfdbdd7b0e3d9551dd833') }
---
shards
[
{
_id: 'd-uf65a*****ac94',
host: 'mgset-100239911/20.**.**.**7:3000,20.**.**.**6:3002',
state: 1,
topologyTime: Timestamp({ t: 1778122320, i: 8 }),
replSetConfigVersion: Long('-1')
},
{
_id: 'd-uf6a8*****c9b14',
host: 'mgset-100239912/20.**.**.**8:3003,20.**.**.**9:3003',
state: 1,
topologyTime: Timestamp({ t: 1778122319, i: 10 }),
replSetConfigVersion: Long('-1')
}
]
---
active mongoses
[ { '8.0.18': 2 } ]
---
autosplit
{ 'Currently enabled': 'yes' }
---
balancer
{
'Currently enabled': 'yes',
'Failed balancer rounds in last 5 attempts': 0,
'Currently running': 'no',
'Migration Results for the last 24 hours': { '216': 'Success' }
}
---
databases
[
{
database: {
_id: 'ycsb',
primary: 'd-uf6a8*****c9b14',
version: {
uuid: UUID('98799357-048e-45bc-a472-387165e0fe74'),
timestamp: Timestamp({ t: 1778122931, i: 2 }),
lastMod: 1
}
},
collections: {
'ycsb.test2': {
shardKey: { _id: 'hashed' },
unique: false,
balancing: true,
allowMigrations: true,
chunkMetadata: [
{ shard: 'd-uf65a*****ac94', nChunks: 55 },
{ shard: 'd-uf6a8*****c9b14', nChunks: 1 }
],
chunks: [
'too many chunks to print, use verbose if you want to force print'
],
tags: []
}
}
}
]
等待AutoMerger自動合并完成後,再次執行sh.status(),關注chunkMetadata欄位——每個Shard上的Chunk已自動合并為1個:
mongos> sh.status()
shardingVersion
{ _id: 1, clusterId: ObjectId('69fbfdbdd7b0e3d9551dd833') }
---
shards
[
{
_id: 'd-uf65a*****ac94',
host: 'mgset-100239911/20.**.**.**7:3000,20.**.**.**6:3002',
state: 1,
topologyTime: Timestamp({ t: 1778122320, i: 8 }),
replSetConfigVersion: Long('-1')
},
{
_id: 'd-uf6a8*****c9b14',
host: 'mgset-100239912/20.**.**.**8:3003,20.**.**.**9:3003',
state: 1,
topologyTime: Timestamp({ t: 1778122319, i: 10 }),
replSetConfigVersion: Long('-1')
}
]
---
active mongoses
[ { '8.0.18': 2 } ]
---
autosplit
{ 'Currently enabled': 'yes' }
---
balancer
{
'Currently enabled': 'yes',
'Failed balancer rounds in last 5 attempts': 0,
'Currently running': 'no',
'Migration Results for the last 24 hours': { '216': 'Success' }
}
---
databases
[
{
database: {
_id: 'ycsb',
primary: 'd-uf6a8*****c9b14',
version: {
uuid: UUID('98799357-048e-45bc-a472-387165e0fe74'),
timestamp: Timestamp({ t: 1778122931, i: 2 }),
lastMod: 1
}
},
collections: {
'ycsb.test2': {
shardKey: { _id: 'hashed' },
unique: false,
balancing: true,
allowMigrations: true,
chunkMetadata: [
{ shard: 'd-uf65a*****ac94', nChunks: 1 },
{ shard: 'd-uf6a8*****c9b14', nChunks: 1 }
],
chunks: [
{ min: { _id: MinKey() }, max: { _id: Long('-58615400192952365') }, 'on shard': 'd-uf65a*****ac94', 'last modified': Timestamp({ t: 217, i: 2 }) },
{ min: { _id: Long('-58615400192952365') }, max: { _id: MaxKey() }, 'on shard': 'd-uf6a8*****c9b14', 'last modified': Timestamp({ t: 217, i: 1 }) }
],
tags: []
}
}
}
]
執行db.test2.getShardDistribution(),關注每個Shard的data佔比——儘管每個Shard只有1個Chunk,資料仍然均衡分布(49.67% vs 50.32%):
mongos> use ycsb
switched to db ycsb
mongos> db.test2.getShardDistribution()
Shard d-uf65a*****ac94 at mgset-100239911/20.**.**.**7:3000,20.**.**.**6:3002
{
data: '27.01GiB',
docs: 24841944,
chunks: 1,
'estimated data per chunk': '27.01GiB',
'estimated docs per chunk': 24841944
}
---
Shard d-uf6a8*****c9b14 at mgset-100239912/20.**.**.**8:3003,20.**.**.**9:3003
{
data: '27.37GiB',
docs: 35898246,
chunks: 1,
'estimated data per chunk': '27.37GiB',
'estimated docs per chunk': 35898246
}
---
Totals
{
data: '54.39GiB',
docs: 60740190,
chunks: 2,
'Shard d-uf65a*****ac94': [
'49.67 % data',
'40.89 % docs in cluster',
'1KiB avg obj size on shard'
],
'Shard d-uf6a8*****c9b14': [
'50.32 % data',
'59.1 % docs in cluster',
'1KiB avg obj size on shard'
]
}
結論:三個版本的實測資料表明,無論Chunk數量如何變化(870→217→2),資料始終均衡分布在各Shard上。Chunk數量的差異僅是內部管理機制的變化,不影響實際資料分布和業務。
如何正確判斷資料是否均衡
升級到6.0版本(小版本7.0.1)及以上版本後,請使用以下方式判斷資料均衡情況:
推薦方式:getShardDistribution()
串連到mongos後執行以下命令:
use <your_database>
db.<your_collection>.getShardDistribution()
該命令會返回每個Shard上該集合的資料大小、文檔數量和佔比,能真實反映資料分布情況。
輔助方式:db.stats()
use <your_database>
db.stats()
可以查看資料庫在各Shard節點的整體資料存放區情況。
不再推薦:sh.status()看Chunk數量
sh.status()仍然可以執行,但Chunk數量不再反映資料均衡情況。看到各Shard的Chunk數量不相等或只有1個Chunk都是正常的,不代表資料分布有問題。
常見問題
Q:升級後Chunk數量不均衡或變為1,資料會丟失嗎?
A:不會。Chunk只是MongoDB內部管理資料的邏輯單元,Chunk數量的變化不影響實際資料。資料仍然完整儲存在各Shard上。
Q:需要手動執行split命令來分割Chunk嗎?
A:不需要。新版本的Balancer會在需要遷移資料時自動進行必要的分割,無需手動幹預。
Q:升級後Balancer還在工作嗎?
A:是的。Balancer仍在正常工作,只是判斷標準從Chunk數量改為了資料大小。當各分區間的資料大小差異超過閾值時,Balancer會自動執行資料移轉。
Q:6.0和7.0版本升級後的Chunk表現有什麼不同?
A:6.0版本(小版本7.0.1起)取消了AutoSplit並改用資料大小均衡,各Shard的Chunk數量可能不均衡。7.0版本在此基礎上新增了AutoMerger,會自動合并Chunk,最終每個Shard可能只有1個Chunk。兩種情況下資料都是均衡分布的。
Q:遷移過程中看到多個Chunk是正常的嗎?
A:是正常的。在資料移轉期間,Balancer會對資料進行必要的分割,因此會臨時出現多個Chunk。對於7.0及以上版本,遷移完成後AutoMerger會自動合并這些Chunk;對於6.0版本,這些Chunk可能會保持不變,但不影響資料均衡。
Q:這個變化影響我的業務嗎?
A:不影響。這個改變對業務是透明的,實際上新版本減少了不必要的Chunk分裂和遷移操作,效能反而更好。