MongoDB Primary 为何持续出现 oplog 全表扫描?

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
云原生多模数据库 Lindorm,多引擎 多规格 0-4节点
简介: 线上某 MongoDB 复制集实例(包含 Primary、Secondary、Hidden 3个节点 ),Primary 节点突然 IOPS 很高,调查后发现,其中 Hidden 处于 RECOVERING 状态,同时 Priamry 上持续有一全表扫描 oplog 的操作,正是这个 oplog 的 COLLSCAN 导致IO很高。

线上某 MongoDB 复制集实例(包含 Primary、Secondary、Hidden 3个节点 ),Primary 节点突然 IOPS 很高,调查后发现,其中 Hidden 处于 RECOVERING 状态,同时 Priamry 上持续有一全表扫描 oplog 的操作,正是这个 oplog 的 COLLSCAN 导致IO很高。

2017-10-23T17:48:01.845+0800 I COMMAND  [conn8766752] query local.oplog.rs query: { ts: { $gte: Timestamp 1505624058000|95, $lte: Timestamp 1505624058000|95 } } planSummary: COLLSCAN cursorid:20808023597 ntoreturn:0 ntoskip:0 keysExamined:0 docsExamined:44669401 keyUpdates:0 writeConflicts:0 numYields:353599 nreturned:0 reslen:20 locks:{ Global: { acquireCount: { r: 707200 } }, Database: { acquireount: { r: 353600 }, acquireWaitCount: { r: 15 }, timeAcquiringMicros: { r: 3667 } }, oplog: { acquireCount: { r: 353600 } } } 935646ms

上述问题,初步一看有2个疑问

  1. Hidden 上最新的 oplog 在 Primary 节点上是存在的,为什么 Hidden 会一直处于 RECOVERING 状态无法恢复?
  2. 同步拉取 oplog 时,会走 oplogHack 的路径,即快速根据oplog上次同步的位点定位到指点位置,这里会走一个二分查找,而不是COLLSCAN,然后从这个位点不断的tail oplog。既然有了这个优化,为什么会出现扫描所有的记录?

接下里将结合 MongoDB 同步的细节实现来分析下上述问题产生的原因。

备如何选择同步源?

MongoDB 复制集使用 oplog 来做主备同步,主将操作日志写入 oplog 集合,备从 oplog 集合不断拉取并重放,来保持主备间数据一致。MongoDB 里的 oplog 特殊集合拥有如下特性:

  1. 每条 oplog 都包含时间戳,按插入顺序递增,如果底层使用的KV存储引擎,这个时间戳将作为 oplog 在KV引擎里存储的key,可以理解为 oplog 在底层存储就是按时间戳顺序存储的,在底层能快速根据ts找位置。
  2. oplog 集合没有索引,它一般的使用模式是,备根据自己已经同步的时间戳,来定位到一个位置,然后从这个位置不断 tail query oplog。针对这种应用模式,对于 local.oplog.rs.find({ts: {$gte: lastFetechOplogTs}}) 这样的请求,会有特殊的oplogStartHack 的优化,先根据gte的查询条件在底层引擎快速找到起始位置,然后从该位置继续 COLLSCAN。
  3. oplog 是一个 capped collection,即固定大小集合(默认为磁盘大小5%),当集合满了时,会将最老插入的数据删除。

2

选择同步源,条件1:备上最新的oplog时间戳 >= 同步源上最旧的oplog时间戳

备在选择同步源时,会根据 oplog 作为依据,如果自己最新的oplog,比同步源上最老的 oplog 还有旧,比如 secondaryNewest < PrimaryOldest,则不能选择 Primary 作为同步源,因为oplog不能衔接上。如上图,Secondary1 可以选择 Primary 作为同步源,Secondary2 不能选择 Primary作为同步源,但可以选择 Secondary1 作为同步源。

如果所有节点都不满足上述条件,即认为找不到同步源,则节点会一直处于 RECOVERING 状态,并会打印 too stale to catch up -- entering maintenance mode 之类的日志,此时这个节点就只能重新全量同步了(向该节点发送 resync 命令即可)。

选择同步源,条件2:如果minvalid处于不一致状态,则minvalid里的时间戳在同步源上必须存在

local.replset.minvalid(后简称minvalid)是 MongoDB 里的一个特殊集合,用于存储节点同步的一致时间点,在备重放oplog、回滚数据的时候都会用到,正常情况下,这个集合里包含一个ts字段,跟最新的oplog时间戳一致,即 { ts: lastOplogTimestamp }

  1. 当备拉取到一批 oplog 后,假设第一条和最后一条 oplog 的时间戳分别为 firstOplogTimestamp、lastOplogTimestamp,则备在重放之前,会先把 minvalid 更新为 { ts: lastOplogTimestamp, begin: firstOplogTimestamp},加了begin字段后就说明,当前处于一个不一致的状态,等一批 oplog 全部重放完,备将 oplog 写到本地,然后更新 minvalid 为{ ts: lastOplogTimestamp},此时又达到一致的状态。
  2. 节点在ROLLBACK时,会将 minvalid 先更新为{ ts: lastOplogTimestampInSyncSource, begin: rollbackCommonPoint},标记为不一致的状态,直到继续同步后才会恢复为一致的状态。比如

     
        主节点  A B C F G H
        备节点1 A B C F G 
        备节点2 A B C D E
    
        备节点就需要回滚到 CommonPoint C,如果根据主来回滚,则minvalid会被更新为 { ts: H, begin:C}` 
    

    在选择同步源时,如果 minvalid 里包含 begin 字段,则说明它上次处于一个不一致的状态,它必须先确认 ts 字段对应的时间戳(命名为 requiredOptime)在同步源上是否存在,主要目的是:

  3. 重放时,如果重放过程异常结束,重新去同步时,必须要找包含上次异常退出时oplog范围的节点来同步
  4. ROLLBACK后选择同步源,必须选择包含ROLLBACK时参考节点对应的oplog范围的节点来同步;如上例,备节点2回滚时,它的参考节点包含了H,则在接下来选择同步源上,同步源一定要包含H才行。

为了确认 requireOptime 是否存在,备会发一个 ts: {$gte: requiredOptime, $lte: requiredOptime} 的请求来确认,这个请求会走到 oplogStartHack的路径,先走一次二分查找,如果能找到(绝大部分情况),皆大欢喜,如果找不到,就会引发一次 oplog 集合的全表扫描,如果oplog集合很大,这个开销非常大,而且会冲掉内存中的cache数据。

oplogStartHack 的本质

通过上面的分析发现,如果 requiredOptime 在同步源上不存在,会引发同步源上的一次oplog全表扫描,这个主要跟oplog hack的实现机制相关。

对于oplog的查找操作,如果其包含一个 ts: {$gte: beginTimestamp} 的条件,则 MongoDB 会走 oplogStartHack 的优化,先从引擎层获取到第一个满足查询条件的RecordId,然后把RecordId作为表扫描的参数。

  1. 如果底层引擎查找到了对应的点,oplogStartHack优化有效
  2. 如果底层引擎没有没有找到对应的点,RecordId会被设置为空值,对接下来的全表扫描不会有任何帮助。(注:个人认为,这里作为一个优化,应该将RecordId设置为Max,让接下里的全表扫描不发生。)
     if (查询满足oplogStartHack的条件) { 
        startLoc = collection->getRecordStore()->oplogStartHack(txn, goal.getValue());  // 1. 将起始值传到底层引擎,通过二分查找找到起始值对应的RecordId
     }

    // Build our collection scan...
    CollectionScanParams params;
    params.collection = collection;
    params.start = *startLoc;                               // 2. 将起始RecordId作为表扫描的参数
    params.direction = CollectionScanParams::FORWARD;
    params.tailable = cq->getParsed().isTailable();

总结

结合上述分析,当一致时间点对应的oplog在同步源上找不到时,会在同步源上触发一次oplog的全表扫描。当主备之间频繁的切换(比如线上的这个实例因为写入负载调大,主备角色切换过很多次),会导致多次ROLLBACK发生,最后出现备上minvalid里的一致时间点在同步源上找不到,引发了oplog的全表扫描;即使发生全表扫描,因为不包含minvalid的oplog,备也不能选择这个节点当同步源,最后就是一直找不到同步源,处于RECOVERING状态无法恢复,然后不断重试,不断触发主上的oplog全表扫描,恶性循环。

如何避免上述问题?

  1. 上述问题一般很难遇到,而且只有oplog集合大的时候影响才会很恶劣。
  2. 终极方法还是从代码上修复,我们已经在阿里云MongoDB云数据库里修复这个问题,并会向官方提一个PR,在上述的场景不产生全表扫描,而是返回找不到记录。
相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
6月前
|
NoSQL MongoDB 数据库
通过优化索引以消除 MongoDB 中的 "查询目标已超过1000个扫描对象/返回的文档数" 警告
MongoDB NoSQL数据库在处理复杂查询时可能出现“查询目标已超过1000个扫描对象/返回的文档数”警告。文章分析了该问题,展示了一个示例集合和相关索引,并提供了查询示例。通过`explain`命令发现查询未有效利用索引。解决方案是遵循ESR规则,创建新索引从而优化查询并消除警告。
175 1
|
NoSQL 网络协议 关系型数据库
mongodb 基于oplog的时间点恢复
本文简单介绍mongodb时间点恢复的过程: 1.首先创建hezi集合,并插入10000条数据; MongoDB Enterprise liuhe_rs:PRIMARY>use liuwenhe MongoDB Enterprise liuhe_rs:PRIMARY>for ( var i = 0; i < 100000; i++) { db.hezi.insert({id: i}); } MongoDB Enterprise liuhe_rs:PRIMARY> db.hezi.count(); 100000 2.执行备份操作,使用参数 --oplog ,会在备份路径下产生oplog.b
718 0
|
存储 NoSQL MongoDB
MongoDB 定位 oplog 必须全表扫描吗?
MongoDB oplog (类似于 MySQL binlog) 记录数据库的所有修改操作,除了用于主备同步;oplog 还能玩出很多花样,比如 全量备份 + 增量备份所有的 oplog,就能实现 MongoDB 恢复到任意时间点的功能 通过 oplog,除了实现到备节点的同步,也可以额外再往单独的集群同步数据(甚至是异构的数据库),实现容灾、多活等场景,比如阿里云开源的 MongoShake 就能实现基于 oplog 的增量同步。
|
NoSQL 固态存储 Shell
MongoDB Oplog Stones 实现分析及启动加载优化
对 Oplog Stones 的实现和初始化流程进行了详细的分析,简单分析了 Oplog 回收的逻辑。并对 oplog stones 的启动加载流程进行了优化,对比有数量级提升。
20074 0
|
NoSQL MongoDB 存储
最佳实践 | MongoDB 定位 oplog 必须全表扫描吗?
MongoDB oplog (类似于 MySQL binlog) 记录数据库的所有修改操作,除了用于主备同步;oplog 还能玩出很多花样。
2527 0
|
存储 监控 NoSQL
MongoDB · 引擎特性 · journal 与 oplog,究竟谁先写入?
MongoDB journal 与 oplog,谁先写入?最近经常被人问到,本文主要科普一下 MongoDB 里 oplog 以及 journal 这两个概念。 journal journal 是 MongoDB 存储引擎层的概念,目前 MongoDB主要支持 mmapv1、wiredtiger、mongorocks 等存储引擎,都支持配置journal。
1947 0
|
1月前
|
存储 关系型数据库 MySQL
一个项目用5款数据库?MySQL、PostgreSQL、ClickHouse、MongoDB区别,适用场景
一个项目用5款数据库?MySQL、PostgreSQL、ClickHouse、MongoDB——特点、性能、扩展性、安全性、适用场景比较
|
20天前
|
NoSQL Cloud Native atlas
探索云原生数据库:MongoDB Atlas 的实践与思考
【10月更文挑战第21天】本文探讨了MongoDB Atlas的核心特性、实践应用及对云原生数据库未来的思考。MongoDB Atlas作为MongoDB的云原生版本,提供全球分布式、完全托管、弹性伸缩和安全合规等优势,支持快速部署、数据全球化、自动化运维和灵活定价。文章还讨论了云原生数据库的未来趋势,如架构灵活性、智能化运维和混合云支持,并分享了实施MongoDB Atlas的最佳实践。

相关产品

  • 云数据库 MongoDB 版