背景
一般來說,除了由于secondary延遲可能造成查詢secondary節點資料不準以外,關于count的準确性問題,在MongoDB4.0官方文檔中有這麼一段話
On a sharded cluster, db.collection.count() without a query predicate can result in an inaccurate count iforphaned documents exist or if a chunk migration is in progress.
To avoid these situations, on a sharded cluster, use the db.collection.aggregate() method
而MongoDB3.6官方文檔卻是這麼描述的
On a sharded cluster, db.collection.count() can result in an inaccurate count if orphaned documents exist or if a chunk migration is in progress.
也就是說,MongoDB4.0分片叢集模式下,針對不帶謂詞條件的全表count操作的傳回結果是不準确的,主要包括以下兩種場景。在MongoDB4.0以前的版本,即使不帶謂詞條件,在以下兩種場景下count值也不準。
1 存在孤立文檔
2 mongo分片叢集内部正在進行move chunk操作
本文主要針對這兩種場景,分析count不準的原因和規避措施
orphaned documents導緻count不準
孤立文檔定義和産生原因
孤立文檔是由于move chunk期間程序異常關閉造成的遷移失敗或清理遷移後的源端chunk失敗造成的,使得這部分記錄在源端和目标端都存在,而在mongo分片叢集的定義中,一個文檔必須且隻能屬于一個chunk和shard。
顯而易見,孤立文檔可能導緻count不準,如果孤立文檔量太大,還會造成占用額外的磁盤存儲資源。
一般來說,movechunk操作大概有以下步驟
- 負載均衡器向源端分片發送movechunk指令
- 源端分片開始在内部遷移資料塊,在整個遷移期間,源端接收所有的通路請求,包括讀和寫
- 在目标端分片建立對應的索引
- 目标端分片開始接收從源端copy過來的chunk中的資料
- 當目标端接收完該chunk的最後一條文檔後,目标端分片開啟一個同步程序來接收chunk遷移期間在源端産生的增量資料
- 當所有增量資料也同步完成後,源端分片開始連接配接config資料庫修改中繼資料,也就是修改該chunk的所屬分片
- 修改好config中繼資料後,源端分片開始删除之前遷移的chunk資料
MongoDB·最佳實踐·count不準原因分析背景orphaned documents導緻count不準孤立文檔定義和産生原因現象模拟和描述規避和消除orphaned documents方法move chunk期間count不準現象描述原因分析改進措施和規避方法
mongodb在設計實作上,真正從源端向目标端copy資料的過程是串行的,也就是隻能逐個chunk遷移,但是出于遷移效率上的考慮,最後一步的清理源端分片殘留資料的操作是異步的,也就說當修改完config中繼資料後,馬上可以進入下一個chunk的遷移,并不需要等待源端分片清理完成。
清理源端分片舊chunk資料的操作放在一個隊列中,在某些場景下它可能由于清理緩慢造成堆積,如果這時primary節點crash,就會産生孤立文檔。
現象模拟和描述
從chunk遷移過程可以看出,假如遷移過程失敗,我們尚不得知它是否會清理目标端資料,理論上也會造成目标分片上的孤立文檔,不過由于movechunk串行處理,即使有,最多也就一個chunk塊有問題;但如果最後一步的清理源端舊chunk資料失敗,則必然會在源端造成孤立文檔,而且最差的情況下可能會産生大量chunk的孤立文檔。
是以要模拟出孤立文檔很簡單,隻需要在大量movechunk期間強制殺掉主節點mongod程序即可,比如在已有一個shard的情況下,添加另外一個shard到分片叢集中,這時必然會涉及到大量的movechunk操作,本文就是采用這種方式。
//添加sharding前,确認sh.isBalancerRunning()為false,因為movechunk期間count本來也不準
mongos> db.user.count({_id:{$gte:0}})
43937296
mongos> db.user.count()
43937296
//添加分片過程中kill -9 mongod程序,重新拉起各個分片,
mongos> db.user.count({_id:{$gte:0}})
43937296
mongos> db.user.count()
51028273
mongos> db.user.aggregate([{ $count:"myCount"}])
{ "myCount" : 43937296 }
從上面可以看到,隻有不帶謂詞條件的全表count才會存在結果不準的現象,因為在這種情況下,count結果值直接從表和chunk的中繼資料資訊擷取,在分布叢集模式下就是挨個去各個分片的chunk擷取該表存取的count值,然後做一個累加傳回,由于孤立文檔的存在就造成傳回結果大于準确結果。上個案例中一個産生了7090977個孤立文檔。
規避和消除orphaned documents方法
從一方面說,減少孤立文檔産生的數量,預設情況下,清理源端分片資料是異步調用的,但也可以通過指令設定成同步調用,也就是設定以後假如primary節點crash,最多隻有一個chunk可能産生孤立,但并不推薦,意義也不大。設定方法為
use config
db.settings.update( { "_id" : "balancer" },{ $set : { "_waitForDelete" : true } },{ upsert : true })
從另一個方面考慮,假如産生了孤立文檔,mongodb提供了清理分片上所有孤立文檔的方法,在每一個sharding節點上執行,方法如下
var nextKey = { };
var result;
while ( nextKey != null ) {
result = db.adminCommand( { cleanupOrphaned: "test.user", startingFromKey: nextKey } );
if (result.ok != 1)
print("Unable to complete at this time: failure or timeout.")
printjson(result);
nextKey = result.stoppedAtKey;
}
move chunk期間count不準
現象描述
通過mongod日志或者sh.isBalancerRunning()指令可以确認,該表處于move chunk階段;
為了友善觀察,我們将_waitForDelete設定為1,即遷移chunk完成後立即删除源端分片資料再進入下一次地chunk遷移,可以觀察到count結果值首先有一個快速的增長過程,然後是一個相對緩慢的減少過程;
每個chunk遷移循環上述過程,直到sh.isBalancerRunning()為OFF後,穩定在一個準确值。
原因分析
- 在move chunk過程中,如果move chunk沒有完成,這時資料在源端和目标端分片上都存在
- 這時在這個分片上執行無謂詞條件的count時,源端和目标端上未遷移完成的chunk的資料都納入了統計,是以會看到結果值會有一個上升
- 當copy data結束并修改中繼資料後,在源分片上開始清理資料,是以到了這個階段,count值會逐漸減少
- count值減少的過程相對比較緩慢,應該是由于清理源端分片資料花的時間要比copy資料更長
在move chunk過程中,如果是非count操作,普通的query肯定無法容忍這種錯誤的,因為根據之前的遷移過程分析,在movechunk的copy data期間,源端接收所有的通路請求;在修改中繼資料後,delete源端資料期間,目标端接受所有的通路請求。也就是說,普通的query會去判斷查詢需要的chunk确實屬于且隻屬于一個shard,完全遵循config server中的中繼資料,是以它的查詢結果是準确的。
如果是MongoDB4.0以前的版本,count操作即使帶了謂詞條件結果值也會不準。這是因為在4.0版本以前帶謂詞條件的count操作原理和普通的query不同,它并不會去檢查周遊到的chunk确實隻屬于一個shard,而4.0以後的版本,其原理就和普通query一樣了,杜絕了結果值不準的情況。
從設計哲學上分析,既然4.0版本也沒有保證不帶謂詞條件的count準确性,可以認為是一種性能與效率上的折衷,因為在這種count場景下,大部分業務并不需要非常精準的count結果,而更強調"fast count"理念,即不用周遊資料,直接從中繼資料層面傳回結果值;當然你需要準确的count值,也完全可以用aggregate方法代替,是以不能認為這是一個bug,如果說有待優化的點,可能隻是兩種count方法在指令展示上不夠相容,容易引起誤解。
改進措施和規避方法
1 同時追求效率和準确性,可以設定負載均衡視窗,在視窗以外禁止move chunk
2 強調資料準确性的場景,使用db.collection.aggregate()方法代替count
3 針對帶謂詞條件的count操作,将mongo版本更新到4.0以上
4 針對出現大量孤立文檔的情況,做孤立文檔清理