MongoDB 3.2.9 请求 hang 分析及 wiredtiger 调优

本文涉及的产品
云数据库 MongoDB,通用型 2核4GB
云原生内存数据库 Tair,内存型 2GB
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
简介: MongoDB 3.2.9 版本在 wiredtiger 上做了很多改进,但不幸的时,这个版本引入了一个新的 bug,持续大量 insert/update 场景,有一定的可能导致 wiredtiger 进入 deadlock,MongoDB 官方迅速的在3.2.10里修复了该问题,该版本在 wir.

MongoDB 3.2.9 版本在 wiredtiger 上做了很多改进,但不幸的时,这个版本引入了一个新的 bug,持续大量 insert/update 场景,有一定的可能导致 wiredtiger 进入 deadlock,MongoDB 官方迅速的在3.2.10里修复了该问题,该版本在 wiredtiger 内存使用上也做了控制,尽量避免了因为内存碎片导致 wiredtiger 内存使用远超出 cacheSizeGB 配置的问题,目前 MongoDB 3.2.10+ 的版本已经非常稳定。

MongoDB 目前有4个可配置的参数来支持 wiredtiger 存储引擎的 eviction 策略调优,其含义是:

参数 默认值 含义
eviction_target 80 当 cache used 超过 eviction_target,后台evict线程开始淘汰 CLEAN PAGE
eviction_trigger 95 当 cache used 超过 eviction_trigger,用户线程也开始淘汰 CLEAN PAGE
eviction_dirty_target 5 当 cache dirty 超过 eviction_dirty_target,后台evict线程开始淘汰 DIRTY PAGE
eviction_dirty_trigger 20 当 cache dirty 超过 eviction_dirty_trigger, 用户线程也开始淘汰 DIRTY PAGE

上述默认值是在3.2.10版本里调整的,如果你正在使用 MongoDB 3.0/3.2,遇到了 wiredtiger 相关问题(绝大部分场景遇不到),可以先升级到3.2的最新版本。

在此基础上(使用3.2.10+),如果通过 mongostat 发现 used、dirty 持续超出eviction_triggereviction_dirty_trigger,这时用户的请求线程也会去干 evict的事情(开销大),会导致请求延时上升,这时基本可以判定,mongodb 已经存在资源不足的问题,即用户读写『从磁盘上读取的数据的速率』 远远 超出了 『mongodb 将数据从内存淘汰出去速率』,可以做的优化包括:

  • 增强 IO 能力

    • SATA 盘升级到 SSD
    • 将 wiredtiger 的数据和 journal 分到不同的盘上
  • 扩充机器内存,加大 wiredtiger cache

    • cache 越大,越能平衡上述2个速率的差距
  • eviction 参数调优

    • 降低eviction_target 或 eviction_dirty_target,让evict 尽早将数据从 wiredtiger 的 cache 刷到操作系统的 page cache,以便提早刷盘。

    db.runCommand({setParameter: 1, wiredTigerEngineRuntimeConfig: "eviction_dirty_target=5,eviction_target=80"})

    --

接下来分析一下 MongoDB 3.2.9 bug 产生的原因,想了解源码的往下看

当用户请求打开wiredtiger cursor 的时候,会检查是否需要 进行 cache 淘汰,当 『cache 使用百分比超出 eviction_trigger』 或者 『cache 脏页百分比超过 eviction_dirty_triger』,用户请求线程就会进入到 cache 淘汰逻辑,执行__wt_cache_eviction_worker

static inline bool
__wt_eviction_needed(WT_SESSION_IMPL *session) {
    bytes_inuse = __wt_cache_bytes_inuse(cache);
    bytes_max = conn->cache_size + 1; // Avoid division by zero
    
    pct_full = (u_int)((100 * bytes_inuse) / bytes_max);
    if (pct_full > cache->eviction_trigger) 
        return true;
   
   if (__wt_cache_dirty_inuse(cache) >
  (cache->eviction_dirty_trigger * bytes_max) / 100)
        return (true);
    
    return false;
}

用户线程执行 __wt_cache_eviction_worker 会持续的检查 __wt_eviction_needed 条件是否满足,不需要 evict 时,用户线程就会继续响应请求;如果需要evict,就会从 evict queue 里取 page 进行淘汰,当 evict queue 为空时,用户线程 wait 一段时间继续重复上述逻辑。

int
__wt_cache_eviction_worker(WT_SESSION_IMPL *session, bool busy, u_int pct_full) {

    for (;;) {
    
        /* See if eviction is still needed. */
        if (!__wt_eviction_needed(session, busy, &pct_full) ||
            (pct_full < 100 &&
            cache->pages_evict > init_evict_count + max_pages_evicted))
          return (0);
          
          /* Evict a page. */
        switch (ret = __evict_page(session, false)) {
        case 0:
          if (busy)
            return (0);
          /* FALLTHROUGH */
        case EBUSY:
          break;
        case WT_NOTFOUND:
          /* Allow the queue to re-populate before retrying. */
          __wt_cond_wait(
              session, conn->evict_threads.wait_cond, 10000);
          cache->app_waits++;
          break;
    }

}

后台的 evict server 线程会遍历 wiredtiger 的 btree 页,将满足条件的的 page 加入到 evict queue 并进行淘汰,每一轮都会通过 __evict_update_work 更新当前的工作状态信息,并告知调用者是否还需要继续执行 evict。


// 这个就是执行上述表格中描述的逻辑
// WT_CACHE_EVICT_CLEAN 标记代表后台线程需要淘汰 CLEAN PAGE
// WT_CACHE_EVICT_CLEAN_HARD 代表用户线程也需要去淘汰 CLEAN PAGE
// DIRTY* 参数类似
static bool
__evict_update_work(WT_SESSION_IMPL *session)
{
  bytes_max = conn->cache_size + 1;
  bytes_inuse = __wt_cache_bytes_inuse(cache);
  if (bytes_inuse > (cache->eviction_target * bytes_max) / 100)
    F_SET(cache, WT_CACHE_EVICT_CLEAN);
  if (__wt_eviction_clean_needed(session, NULL))
    F_SET(cache, WT_CACHE_EVICT_CLEAN_HARD);

  dirty_inuse = __wt_cache_dirty_leaf_inuse(cache);
  if (dirty_inuse > (cache->eviction_dirty_target * bytes_max) / 100)
    F_SET(cache, WT_CACHE_EVICT_DIRTY);
  if (__wt_eviction_dirty_needed(session, NULL))
    F_SET(cache, WT_CACHE_EVICT_DIRTY_HARD);
    
   return (F_ISSET(cache, WT_CACHE_EVICT_ALL | WT_CACHE_EVICT_URGENT));
}

__evict_update_work 最后通过 F_ISSET(cache, WT_CACHE_EVICT) 来判断是否要继续 evict

#define WT_CACHE_EVICT_ALL  (WT_CACHE_EVICT_CLEAN | WT_CACHE_EVICT_DIRTY)

这样可能会出现一种情况,eviction_triggereviction_dirty_trigger 触发了,这时后台线程是需要继续进行 evict 的,但eviction_target、eviction_ditry_target都不满足,导致上述判断条件返回 false,后台线程不继续干活,这样就不会有新的 page 加入到 evict queue,而上述用户线程还在继续等待 evict,一直不会返回,这样就会导致请求 hang 。

修复上述问题的主要代码如下:github commit

主要修改逻辑是,当 used 超过eviction_trigger时,同时也设置WT_CACHE_EVICT_CLEAN标记(DIRTY 类似),这样确保有用户线程在等时,evict 一定会进行。

static bool
__evict_update_work(WT_SESSION_IMPL *session)
{
  bytes_max = conn->cache_size + 1;
  bytes_inuse = __wt_cache_bytes_inuse(cache);
  if (bytes_inuse > (cache->eviction_target * bytes_max) / 100)
    F_SET(cache, WT_CACHE_EVICT_CLEAN);
  if (__wt_eviction_clean_needed(session, NULL))
    - F_SET(cache, WT_CACHE_EVICT_CLEAN_HARD);
   + F_SET(cache, WT_CACHE_EVICT_CLEAN | WT_CACHE_EVICT_CLEAN_HARD);


  dirty_inuse = __wt_cache_dirty_leaf_inuse(cache);
  if (dirty_inuse > (cache->eviction_dirty_target * bytes_max) / 100)
    F_SET(cache, WT_CACHE_EVICT_DIRTY);
  if (__wt_eviction_dirty_needed(session, NULL))
    - F_SET(cache, WT_CACHE_EVICT_DIRTY_HARD);
    + F_SET(cache, WT_CACHE_EVICT_DIRTY | WT_CACHE_EVICT_DIRTY_HARD);
    
   return (F_ISSET(cache, WT_CACHE_EVICT_ALL | WT_CACHE_EVICT_URGENT));
}
相关实践学习
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
相关文章
|
2月前
|
存储 监控 NoSQL
【MongoDB 专栏】MongoDB 的日志管理与分析
【5月更文挑战第11天】MongoDB日志管理与分析至关重要,包括系统日志和操作日志,用于监控、故障排查和性能优化。合理配置日志详细程度、存储位置和保留策略,使用日志分析工具提升效率,发现性能瓶颈和安全性问题。日志分析有助于优化查询、调整配置,确保数据安全,并可与其他监控系统集成。面对日志量增长的挑战,需采用新技术如分布式存储和数据压缩来保障存储和传输。随着技术发展,不断进化日志管理与分析能力,以支持MongoDB的稳定高效运行。
【MongoDB 专栏】MongoDB 的日志管理与分析
|
1月前
|
存储 NoSQL 算法
MongoDB存储引擎发展及WiredTiger深入解析(二)
MongoDB存储引擎发展及WiredTiger深入解析(二)
|
2月前
|
监控 NoSQL MongoDB
【MongoDB 专栏】MongoDB 的监控与性能调优
【5月更文挑战第11天】在数字化时代,MongoDB作为流行的非关系型数据库,其监控和性能调优至关重要。监控能实时了解数据库运行状态,预防性能问题,评估系统健康。关键监控指标包括系统资源、查询性能、连接数和数据存储量。常用工具如`mongostat`、`mongotop`,以及Prometheus、Grafana等。性能调优涉及索引、查询优化、数据模型调整、配置修改及分片复制。通过持续监控和调优,可确保MongoDB高效稳定运行,适应业务发展需求。
【MongoDB 专栏】MongoDB 的监控与性能调优
|
1月前
|
监控 NoSQL 大数据
深入解析 MongoDB Map-Reduce:强大数据聚合与分析的利器
深入解析 MongoDB Map-Reduce:强大数据聚合与分析的利器
|
1月前
|
监控 NoSQL MongoDB
深度优化:掌握 MongoDB 查询分析的关键技巧
深度优化:掌握 MongoDB 查询分析的关键技巧
|
2月前
|
存储 NoSQL 数据挖掘
MongoDB 实时分析案例
【5月更文挑战第7天】
112 0
|
2月前
|
监控 NoSQL MongoDB
MongoDB性能调优:监控与诊断工具的技术探讨
【4月更文挑战第30天】本文探讨了MongoDB性能调优,重点关注监控与诊断工具。MongoDB自带的Shell和Profiler有助于理解数据库性能,而MMS、PMM和mongostat等第三方工具则提供实时监控和深度分析。调优实践包括优化索引、调整内存配置、分片与复制、硬件升级及查询优化。通过这些工具和策略,可有效提升MongoDB性能。
|
2月前
|
存储 NoSQL MongoDB
【MongoDB】MongoDB 索引结构底层原理分析
【4月更文挑战第1天】【MongoDB】MongoDB 索引结构底层原理分析
|
2月前
|
存储 NoSQL MongoDB
MongoDB 助力移动式汽车保养运营模式优化,将开发请求减少 90%
MongoDB针对初级,中级及熟练的技术开发人员推出系列技术文章与行业案例。深入浅出地剖析MongoDB产品基础原理,使用技巧,典型行业场景及应用,还有Code Demo及线上线下活动推荐!
4877 1
MongoDB 助力移动式汽车保养运营模式优化,将开发请求减少 90%
|
7月前
|
存储 人工智能 NoSQL
多维数据实时分析,MongoDB给零售企业提供快速高效的数据洞察力
客户行为正在迅速演变,供应链正在重组,员工也正在以新的方式工作。企业需要提供更加个性化的客户体验,对市场趋势做出更快速的反应,监测和预防潜在问题。
多维数据实时分析,MongoDB给零售企业提供快速高效的数据洞察力

相关产品

  • 云数据库 MongoDB 版