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

本文涉及的产品
云数据库 MongoDB,通用型 2核4GB
简介: 背景一般来说,除了由于secondary延迟可能造成查询secondary节点数据不准以外,关于count的准确性问题,在MongoDB4.0官方文档中有这么一段话On a sharded cluster, db.

背景

一般来说,除了由于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 针对出现大量孤立文档的情况,做孤立文档清理

相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。   相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
6月前
|
运维 NoSQL 安全
【最佳实践】高可用mongodb集群(1分片+3副本):规划及部署
结合我们的生产需求,本次详细整理了最新版本 MonogoDB 7.0 集群的规划及部署过程,具有较大的参考价值,基本可照搬使用。 适应数据规模为T级的场景,由于设计了分片支撑,后续如有大数据量需求,可分片横向扩展。
485 1
|
6月前
|
NoSQL Oracle MongoDB
【最佳实践】MongoDB导出导入数据
【最佳实践】MongoDB导出导入数据
389 2
|
6月前
|
NoSQL MongoDB 索引
【最佳实践】MongoDB导入数据时重建索引
【最佳实践】MongoDB导入数据时重建索引
169 0
|
22天前
|
消息中间件 NoSQL Kafka
云原生最佳实践系列 5:基于函数计算 FC 实现阿里云 Kafka 消息内容控制 MongoDB DML 操作
该方案描述了一个大数据ETL流程,其中阿里云Kafka消息根据内容触发函数计算(FC)函数,执行针对MongoDB的增、删、改操作。
|
1月前
|
存储 缓存 NoSQL
|
1月前
|
缓存 NoSQL 关系型数据库
|
4月前
|
存储 监控 NoSQL
数据存储与分析:办公室电脑屏幕监控的MongoDB应用实例
在当今数字时代,数据的存储和分析变得愈发重要,尤其是在办公环境中,对电脑屏幕进行监控成为一种日益普遍的需求。本文将介绍如何利用MongoDB数据库实现办公室电脑屏幕监控,并通过代码实例展示其应用。
219 0
|
4月前
|
存储 人工智能 NoSQL
多维数据实时分析,MongoDB给零售企业提供快速高效的数据洞察力
客户行为正在迅速演变,供应链正在重组,员工也正在以新的方式工作。企业需要提供更加个性化的客户体验,对市场趋势做出更快速的反应,监测和预防潜在问题。
多维数据实时分析,MongoDB给零售企业提供快速高效的数据洞察力
|
7月前
|
存储 NoSQL 自动驾驶
MongoDB在自动驾驶场景方案及最佳实践
MongoDB在自动驾驶场景方案及最佳实践
314 1
|
7月前
|
监控 NoSQL 安全
MongoDB在游戏行业方案介绍及最佳实践
MongoDB在游戏行业方案介绍及最佳实践
492 1