MongoDB·最佳实践·count不准原因分析

  1. 云栖社区>
  2. 博客>
  3. 正文

MongoDB·最佳实践·count不准原因分析

游客4xkfitkz6m3ay 2019-06-03 13:36:13 浏览452
展开阅读全文

背景

一般来说,除了由于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.
To avoid these situations, on a sharded cluster, use the db.collection.aggregate() method

也就是说,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数据
    move_chunk

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 针对出现大量孤立文档的情况,做孤立文档清理

网友评论

登录后评论
0/500
评论
游客4xkfitkz6m3ay
+ 关注