MongoDB 内核存储引擎 WiredTiger Eviction(缓存驱逐)机制全解析:阈值配置、LRU 队列与应用线程协同
2026/9/17 2:53:30 网站建设 项目流程

MongoDB 内核存储引擎 WiredTiger Eviction(缓存驱逐)机制全解析:阈值配置、LRU 队列与应用线程协同

【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo

导读

本文聚焦 MongoDB 底层存储引擎 WiredTiger 的Eviction(缓存驱逐)模块,系统讲解它如何把 cache 中的数据量控制在用户定义的边界内,从而保障读写性能稳定。你将掌握eviction_target/eviction_trigger/eviction_dirty_*/eviction_updates_*等核心参数的语义与默认值、Eviction Server + Worker Threads + 应用线程三方协作的工作流程、forced eviction(强制驱逐)的触发场景,以及 evict.h 中对外暴露的管理 API。所有结论均以当前仓库的文档、配置与 C 源码为准,可直接用于 MongoDB 部署时的缓存调优与问题诊断。

说明:WiredTiger 以源码形式内嵌于本仓库的src/third_party/wiredtiger目录下,MongoDB 正是通过它完成磁盘数据的缓存与持久化。下文凡引用配置定义、实现文件,均指向该目录内的真实路径。


一、Eviction 模块概述:把缓存维持在"目标区间"

WiredTiger 的 Eviction 模块负责确保存放在 cache 中的数据量被高效地控制在用户定义的边界之内,以获得最优性能。这些边界由target(目标值)trigger(触发值)两套阈值共同刻画,并针对三类内容分别设定:

  • total(总量):clean(干净)页、dirty(脏)页与内存内 updates 的合计内存占用;
  • dirty(脏量):所有已修改但尚未写回磁盘的页所占内存;
  • updates(更新量):所有内存内更新所占内存。

当 cache 使用量超过这些上限时,驱逐流程便会启动:把内容从 cache 中驱逐出去并写回磁盘。从代码结构看,整个模块由 src/third_party/wiredtiger/src/evict/ 目录下的十余个文件构成,职责高度内聚:

文件职责(从函数与注释推断)
evict.h/evict_private.h/evict_inline.h对外 API 声明、内部数据结构(__wt_evict)、内联辅助函数
evict_conn.c驱逐配置解析、校验与原子发布,服务器/线程的创建销毁
evict_server.c(对应evict_thread.cevict_dispatch.c驱逐服务器线程与工作线程的调度循环
evict_queue.c两个轮换 LRU 队列与 urgent 紧急队列的管理
evict_walk.cLRU 遍历(walk)选择候选页
evict_page.c单页驱逐的核心实现
evict_file.c/evict_exclusive.c整棵 Btree / 文件级驱逐及独占模式
evict_stat.c/evict_verbose.c统计信息与 verbose 调试输出

下文将沿原文档骨架逐一展开。


二、核心配置参数:语义、默认值与合法范围

WiredTiger 在 src/third_party/wiredtiger/dist/api_data.py(dist目录下集中定义全部配置项,实际 C 结构由脚本自动生成)中提供了多项驱逐配置。这些设置既可在打开数据库时(wiredtiger_open)调整,也可在运行期通过WT_CONNECTION::reconfigure重新配置。与驱逐最相关的参数如下:

参数描述
eviction_target驱逐试图维持的 cache 用量目标百分比。
eviction_trigger当 cache 用量超过该阈值时,应用线程开始协助驱逐。
eviction_dirty_target驱逐试图维持的cache 用量目标百分比。
eviction_dirty_triggercache 用量超过该阈值时,应用线程开始协助驱逐。
eviction_updates_target驱逐试图维持的updatescache 用量目标百分比。
eviction_updates_triggerupdatescache 用量超过该阈值时,应用线程开始协助驱逐。

重要约束:每个 target 值必须始终低于其对应的 trigger 值(见原文档 Note,以及 evict_conn.c 中"eviction target must be lower than the eviction trigger"等三条 EINVAL 校验)。

2.1 默认值与取值范围(来自 api_data.py)

查阅 src/third_party/wiredtiger/dist/api_data.py 可得到每个参数的默认值、类型与边界:

  • eviction_target:默认80,合法值域10 ~ 100(按百分比计);大于 100 时按绝对字节数解释,且不得超过cache_size
  • eviction_trigger:默认95,值域10 ~ 100;同样支持大于 100 时按绝对大小解释。默认即"cache 用到 95% 时应用线程开始帮忙"。
  • eviction_dirty_target:默认5,值域1 ~ 100(注意与 total 版本不同,最小为 1)。
  • eviction_dirty_trigger:默认20,值域1 ~ 100;并且只有当它低于eviction_trigger时才会改变行为
  • eviction_updates_target:默认0(表示"由系统推算"),值域0 ~ 100默认推算为eviction_dirty_target的一半,若开启了 precise checkpoint(精确检查点)则等于eviction_dirty_target
  • eviction_updates_trigger:默认0,值域0 ~ 100;默认推算为eviction_dirty_trigger的一半。

2.2 配置校验与自动纠偏的源码证据

配置并非简单地读入即用。在 evict_conn.c 的__wt_evict_config(由evict.h声明)中,先通过__wt_config_gets逐一读取上述 6 个阈值,再由__evict_config_abs_to_pct把"大于 100 的绝对值"换算为占cache_size的百分比。随后是一系列自动纠偏逻辑:

  • eviction_dirty_target > eviction_target时,将 dirty target 钳制为 eviction target(L107-L113);
  • eviction_dirty_trigger > eviction_trigger时,将 dirty trigger 钳制为 eviction trigger(L124-L130);
  • updates target/trigger 为零时按上一节规则推算默认值,且在 precise checkpoint 模式下改为对齐 dirty 阈值,以避免 history store 页被过早驱逐后又被检查点重新读回(L132-L178);
  • updates trigger 不得超过总 trigger(L180-L187);
  • 最后若出现target >= trigger则直接返回EINVAL,并通过__wt_atomic_store_double_relaxed把校验后的值原子发布,供驱逐、检查点与连接关闭并发读取(L189-L205)。

这里还有一个值得一提的实现细节:__wt_evict结构体中的阈值字段全部使用double 类型,注释明确说明这是为了"允许指定小于 1 的百分比"(见 evict.h),因此你可以在配置中给出小数百分比。


三、Eviction 流程:三个参与组件如何协同

原文档把驱逐流程归纳为三个组成部分,下面逐一展开并结合源码印证。

3.1 Eviction Server:候选页识别与 LRU 排队

Eviction Server线程负责识别可驱逐的页/候选页,把它们放入驱逐队列,并按LRU(Least Recently Used,最近最少使用)算法排序。它是一个后台进程,只要任一 target 阈值被触及就会启动

队列侧的实现位于 evict_queue.c:模块维护两个轮换的 LRU 队列evict_queues[WTI_EVICT_QUEUE_MAX],当前队列/填充队列/另一队列)与一个urgent 紧急队列evict_urgent_queue)。当驱逐负载很高时,server 会在切换队列之前提前填充当前队列;紧急队列中的页会连同约三分之一的普通候选页被优先处理(L164-L177、L291 附近注释)。__wt_evict结构中还保留了read_gen(当前页读取代数)与evict_pass_gen(驱逐 pass 代数)等字段,用于在遍历时评估"页有多久没被访问",这正是 LRU 排序的依据(evict.h)。

3.2 Eviction Worker Threads:出队、写盘、移除

Eviction Worker Threads是另一类后台线程:从驱逐队列弹出页,把页写回磁盘,然后从 cache 中移除。threads_maxthreads_min配置项控制 WiredTiger 中驱逐工作线程数量的上限与下限。在 api_data.py 中:

  • threads_max:默认8,合法范围1 ~ 64(上限必须与代码中的WT_EVICT_MAX_WORKERS(64)一致,见 evict.h)。实际启动的线程数会随当前驱逐负载动态变化;每个驱逐工作线程使用一个来自保留池的会话,该保留池最多可容纳WT_EVICT_MAX_WORKERS(64)个会话。
  • threads_min:默认1,范围1 ~ 64,即至少保留一个工作线程。

注意事项(原文档明确提醒):也可以只运行 eviction server 而不启动驱逐工作线程,但这样驱逐会更慢,因为此时 server 线程还得分身去驱逐队列中的页。

3.3 Application Threads Eviction:触发值与强制驱逐

当后台驱逐线程无法把 cache 内容维持在边界内、且 cache 用量超过trigger 阈值时,WiredTiger 会暂停应用线程,让它们协助驱逐工作线程清理队列中的页。这必然带来额外的读写延迟。

另一类场景是forced eviction(强制驱逐):应用线程直接驱逐满足"应当立即驱逐"条件的页。典型条件包括(源自 README.md 与配置定义):

  • 页大小超过配置的memory_page_max(该参数定义于 api_data.py,默认5MB,用于限制单页内存占用上限);
  • 带超大 skip list 的页;
  • 空的内部(internal)页;
  • 过时(obsolete)页;
  • 更新链(update chains)过长的页;
  • 显示出大量已删除记录的页。

原文档强调:在上述两种情况下(trigger 触发 + forced eviction),应用线程都可能经历更高的读/写延迟。这是调优时权衡"缓存命中率"与"尾部延迟"的关键点。

3.4 压力信号:aggressive score 与 cache stuck

__wt_evict结构还记录了驱逐"健康度"相关的内部信号(evict.h):evict_aggressive_score表示驱逐在挑选候选页时的激进程度评分——若驱逐难以取得进展,该评分会上升(上限WT_EVICT_SCORE_MAX=100,此时 cache 被视为 "stuck",事务将被回滚);evict_empty_score反映 LRU 队列在重新填充时持续为空的频率。这些字段是诊断"驱逐停滞"问题时的有力抓手。


四、Eviction 对外 API(evict.h)

驱逐 API 声明于 src/third_party/wiredtiger/src/evict/evict.h,允许 WiredTiger 内部其他模块管理驱逐流程。原文档归纳的功能如下:

  • 暂停/恢复 eviction server(必要时);
  • 指定哪些 Btree 优先驱逐或从驱逐中排除
  • 监控驱逐健康度,包括驱逐滞后(lag)、进度缓慢(slow progress)与停滞(stalled progress);
  • 允许调用方直接驱逐页或整棵 Btree,绕过后台驱逐流程;
  • 修改页状态,这对于对页做驱逐优先/降权至关重要。

对照evict.h中由prototypes.py自动生成的函数声明(L181-L239),可看到上述能力的直接落点,例如:

  • __wt_evict_server_wake:唤醒驱逐 server(对应暂停/恢复与调度);
  • __wt_evict_file/__wt_evict_file_exclusive_on/__wt_evict_file_exclusive_off:文件级驱逐与独占模式开关;
  • __wt_evict_priority_set/__wt_evict_priority_clear:设置/清除页的驱逐优先级;
  • __wt_evict/__wt_evict_page_urgent:直接驱逐单页、将页标记为 urgent;
  • __wt_evict_aggressive/__wt_evict_cache_stuck:判断驱逐是否进入激进模式或 cache 是否卡死;
  • __wt_evict_clean_needed/__wt_evict_dirty_needed/__wt_evict_needed:供上层判断当前是否需要驱逐。

原文档建议:关于每个 API 的功能细节,请参阅evict.h中每个函数定义上方的注释(例如__wt_evict_timeline结构即记录了驱逐各阶段的起始/完成时间戳,用于超时后的耗时追踪,见 evict.h)。


五、术语表

  • 1target:驱逐试图维持的 cache 用量水平。
  • 2trigger:应用线程开始协助驱逐页时的 cache 用量水平。
  • 3total:clean 页、dirty 页与内存内 updates 的合并内存占用。
  • 4dirty:所有已修改但尚未写回磁盘的页的内存占用。
  • 5updates:所有内存内更新的内存占用。检查点之后,页上可能仍保留已持久化到磁盘的修改——这部分内存既不是 clean(它并非磁盘映像的精确副本),也不是 dirty(数据已落盘),WiredTiger 将这类数据记为 "updates"。

理解这五个术语是正确阅读本文档、乃至 WiredTiger 缓存统计输出(cache_bytescache_dirty_bytescache_bytes_updates等,见 evict_stat.c 对应的统计收集逻辑)的前提。


六、实战调优建议(基于上述机制)

结合参数语义与源码约束,给出可落地的调优方向:

  1. 先盯默认值eviction_target=80/eviction_trigger=95/eviction_dirty_target=5/eviction_dirty_trigger=20是 WiredTiger 默认行为。除非有明确监控数据,否则不建议轻易改动。
  2. 遵守 target < trigger:任何一对 target/trigger 违反该约束,连接打开或 reconfigure 都会收到EINVAL(evict_conn.c)。注意eviction_dirty_*eviction_updates_*还会被自动钳制到不超过对应 total 阈值。
  3. 按负载类型微调:写密集负载可适当提高 dirty trigger 以聚合更多随机写;而延迟敏感场景应压低 trigger,让驱逐更早介入,避免应用线程在高峰期被"拉壮丁"导致尾延迟飙升。
  4. 工作线程数量threads_min/threads_max1 ~ 64WT_EVICT_MAX_WORKERS)之间动态伸缩,无需手工精确指定;只有极端高吞吐场景才考虑调大threads_max
  5. 监控驱逐健康度:若evict_aggressive_score长期偏高、LRU 队列频繁为空(evict_empty_score),说明 cache 处于高压状态,应优先考虑增大cache_size或优化访问模式,而不是继续压缩阈值。

结语

WiredTiger 的 Eviction 模块通过"target/trigger 双阈值 + Server 识别排队 + Worker 写盘 + 应用线程兜底"的四层协作,把缓存压力化解为平滑可控的磁盘 I/O。掌握本文的参数语义(默认值、合法范围、自动纠偏)、三组件工作流与evict.h暴露的管理接口,你就能在 MongoDB 部署中准确解读缓存统计、定位驱逐停滞问题,并做出有依据的参数调整。如需深入某个函数的具体行为,建议直接阅读 src/third_party/wiredtiger/src/evict/ 目录下各实现文件及 evict.h 顶部注释。

【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询