MongoDB Secondary 延时高(同步锁)问题分析

背景介绍

  • MongoDB 复制集里 Secondary 不断从主上批量拉取 oplog,然后在本地重放,以保证数据与 Primary 一致。同步原理参考MongoDB复制集同步原理解析
  • Secondary 拉取到一批 oplog 后,在重放这批 oplog 时,会加一个特殊的 Lock::ParallelBatchWriterMode 的锁,这个锁会阻塞所有的读请求,直到这批 oplog 重放完成。这么做的原因有2个
    • 尽量避免脏读,等一批 oplog 重放完后,这批数据才允许用户读到。
    • 尽量保证同步性能,设想一下,如果重放 oplog 时,使用普通的锁,那么 oplog 的重放就需要跟正常的读写竞争锁资源,如果 Secondary 上有大量的读,那么势必会造成备同步逐步跟不上。参考 SERVER-18190

案例分析

基于上述问题,某些用户在读取备节点时,可能遇到因为 Secondary 重放 oplog 占用特殊锁时间较长,导致读取的延时变长。

问题1:单个请求耗时长

一个长达1小时的『前台创建索引』请求,在 Secondary 节点上重放时,一直占用 ParallelBatchWriterMode 锁,导致 Secondary 上所有请求阻塞长达1小时,这个案例我在Secondary节点为何阻塞请求近一个小时? 里已经分析过,这里不再赘述,解决方案是尽量后台建索引。

上述场景除了会影响 Secondary 上的读请求,如果 Priamry 上写请求指定了 writeConcern 来写多个节点({w: 2+}),而 Secondary 又一直阻塞在创建索引上,导致其后的oplog 重放都要等待创建索引结束,从而主上的写入也阻塞。

问题2:多个请求加起来耗时长

当主上写入并发很大时,Secondary 每次能拉到很多条 oplog,然后并发重放,重放一条的耗时可能很小,但累计起来一次重放上百、上千条 oplog,耗时就会高很多,而重放过程中,Secondary 上读请求都是要阻塞等待的,所以总体看上去,「Secondary 上平均延时,可能比 Primary 上更长点」(这就是为什么很多用户在写入比较多时,会发现读secondary 比 读praimry 更慢),但只要延时在可接受范围内,这个问题并无影响,而且根据云上用户使用的经验,绝大部分用户都是感受不到这个差异的。

但有一种情况值要注意

从上面的例子可以看到,一条 update 操作,指定了 {multi: true} 选项,更新了2个匹配的文档,针对每个文档都产生了一条 oplog(主要为了保证 oplog 幂等性),如果匹配的文档有成千上万条,就会产生对应数量的 oplog,然后 Secondary 拉取这些 oplog 并重放;这个场景下,update 的开销在Secondary 上被放大多倍,此时Secondary 的读延时可能会受比较大的影响。

如何评估重放 oplog 时锁的影响有多大?

从上述的例子可以看出,Secondary 在某些场景下会出现读延时很高的情况,那么当实际遇到问题时,如何判断问题就是 Secondary 重放 oplog 占用锁时间太长导致呢?

我们的做法是增加审计日志,把『Secondary 节点重放每一批 oplog 的时间开销记录到审计日志』里,这样就能很方便的看出影响到底有多大,如下是一个『简化版本的加日志的patch』,有需要的可以应用到 MongoDB 3.2上。

diff --git a/src/mongo/db/repl/sync_tail.cpp b/src/mongo/db/repl/sync_tail.cpp
index 50517c2..e7d58bc 100644
--- a/src/mongo/db/repl/sync_tail.cpp
+++ b/src/mongo/db/repl/sync_tail.cpp
@@ -550,6 +550,8 @@ OpTime SyncTail::multiApply(OperationContext* txn,
     // stop all readers until we're done
     Lock::ParallelBatchWriterMode pbwm(txn->lockState());

+    unsigned long long startTime = curTimeMicros64();
+
     if (inShutdownStrict()) {
         log() << "Cannot apply operations due to shutdown in progress";
         return OpTime();
@@ -585,6 +587,8 @@ OpTime SyncTail::multiApply(OperationContext* txn,
         setMinValid(txn, boundaries->end, DurableRequirement::None);  // Mark batch as complete.
     }

+    log() << "batch writer cost " << (curTimeMicros64() - startTime) << us;
+
     return lastOpTime;
 }

参考资料

  • MongoDB复制集同步原理解析
  • Secondary节点为何阻塞请求近一个小时?
  • 阿里云数据库 MongoDB 版
时间: 2024-09-14 04:44:01

MongoDB Secondary 延时高(同步锁)问题分析的相关文章

MongoDB Secondary同步慢问题分析

MongoDB Scondary同步慢问题分析 问题背景 最近生产环境出现多次Primary写入QPS太高,导致Seconary的同步无法跟上的问题(Secondary上的最新oplog时间戳比Primary上最旧oplog时间戳小),使得Secondary变成RECOVERING状态,这时需要人工介入处理,向Secondary发送resync命令,让Secondary重新全量同步一次. 同步过程 下图是MongoDB数据同步的流程 Primary上的写入会记录oplog,存储到一个固定大小的c

MongoDB Secondary同步慢问题分析(续)

在MongoDB Scondary同步慢问题分析文中介绍了因Primary上写入qps过大,导致Secondary节点的同步无法追上的问题,本文再分享一个case,因oplog的写入被放大,导致同步追不上的问题. MongoDB用于同步的oplog具有一个重要的『幂等』特性,也就是说,一条oplog在备上重放多次,得到的结果跟重放一次结果是一样的,这个特性简化了同步的实现,Secondary不需要有专门的逻辑去保证一条oplog在备上『必须仅能重放』一次. 为了保证幂等性,记录oplog时,通常

PostgreSQL 同步流复制锁瓶颈分析

PostgreSQL 同步流复制锁瓶颈分析 作者 digoal 日期 2016-11-07 标签 PostgreSQL , 同步流复制 , mutex , Linux , latch 背景 PostgreSQL的同步流复制实际上是通过walsender接收到的walreceiver的LSN位点,来唤醒和释放那些需要等待WAL已被备库接收的事务的. 对同步事务来说,用户发起结束事务的请求后,产生的RECORD LSN必须要小于或等于walsender接收到的walreceiver反馈的LSN位点.

起底多线程同步锁(iOS)

iOS/MacOS为多线程.共享内存(变量)提供了多种的同步解决方案(即同步锁),对于这些方案的比较,大都讨论了锁的用法以及锁操作的开销,然后就开销表现排个序.小哥以为,最优方案的选用还是看应用场景,高频接口PK低频接口.有限冲突PK激烈竞争.代码片段耗时的长短,以上都是正确选用的重要依据,不同方案在其适用范围表现各有不同.这些方案当中,除了熟悉的iOS/MacOS系统自有的同步锁,另外还有两个自研的读写锁,还有应用开发中常见的set/get访问接口的原子操作属性. 1.@synchronize

ART世界探险(9) - 同步锁

ART世界探险(9) - 同步锁 Java是一种把同步锁写进语言和指令集的语言. 从语言层面,Java提供了synchronized关键字. 从指令集层面,Java提供了monitorenter和monitorexit两条指令. 下面我们就看看它们是如何实现的吧. 三种锁的方式 Java代码 有三种方式来加锁: 直接在函数上加synchronized关键字 在函数内用某Object去做同步 调用concurrent库中的其他工具 public synchronized int newID(){

“大”事务引起的锁等待分析案例

一.现象 生产环境数据库在某一刻突然发现大量活跃连接,而且大部分状态是updating.问题出现在周六上午,持续了大概三.四分钟,得益于我们自己的快照程序,拿到了当时现场的processlist, 锁等待关系,及innodb status 信息:(经过脱敏处理) innodb_status.txt gist片段: var_mydb_snapshot.html,详见:https://gist.coding.net/u/seanlook/d6ad649f81c64e23a25f3a980c44a1f

多线程的同步锁问题

问题描述 首先,祝大家伙节日快乐大过节的,又要麻烦大家了.不过,我觉得这是很经典的一个例子,希望大伙能一起瞧瞧 我先说明一下意思银行类里面有两个方法,transfer()和getTotalBalance()他们用的是同一把锁,在实例化该类的时候产生我的主要困惑在于,当一个线程进入transfer()方法的时候,他获得了锁,但是他同时调用了getTotalBalance()方法System.out.printf("TotalBalance:%10.2f%n",getTotalBalanc

java-Java静态方法的同步锁

问题描述 Java静态方法的同步锁 Java静态方法的同步锁必须是字节码class吗?可以是static对象吗? 解决方案 如果你是synchronized加在静态方法上,那么锁就是当前类的class对象,但是如果是在静态方法内部的静态语句块,那么锁对象就可以是任意的可引用的对象.如果是加在非静态方法上,那么锁对象就是当前this对象.实例代码: public class CTest { private static String dataStatic = new String(""l

java线程学习6——线程同步之同步锁

  import java.util.concurrent.locks.ReentrantLock; public class Account {  /**   * 同步锁   */  private final ReentrantLock lock = new ReentrantLock();  /**   * 账户号   */  private String accountNo;  /**   * 账户余额   */  private double balance;  public Acco