zoukankan      html  css  js  c++  java
  • MongoDB 3.2.9 请求 hang 分析及 wiredtiger 调优

    转载自:http://www.mongoing.com/archives/3675

    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 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));
    }
    

    作者简介

    张友东,阿里巴巴技术专家,主要关注分布式存储、Nosql数据库等技术领域,先后参与TFS(淘宝分布式文件系统)Redis云数据库等项目,目前主要从事MongoDB云数据库的研发工作,致力于让开发者用上最好的MongoDB云服务。

  • 相关阅读:
    python之字典操作
    python之元组操作
    初始超算
    后缀自动机
    博弈
    曼哈顿最小生成树
    莫队算法
    主席树
    [HNOI2014]世界树
    [SDOI2011]消耗战
  • 原文地址:https://www.cnblogs.com/xibuhaohao/p/12313303.html
Copyright © 2011-2022 走看看