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.c、evict_dispatch.c) | 驱逐服务器线程与工作线程的调度循环 |
evict_queue.c | 两个轮换 LRU 队列与 urgent 紧急队列的管理 |
evict_walk.c | LRU 遍历(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_trigger | 当脏cache 用量超过该阈值时,应用线程开始协助驱逐。 |
eviction_updates_target | 驱逐试图维持的updatescache 用量目标百分比。 |
eviction_updates_trigger | 当updatescache 用量超过该阈值时,应用线程开始协助驱逐。 |
重要约束:每个 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_max与threads_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_bytes、cache_dirty_bytes、cache_bytes_updates等,见 evict_stat.c 对应的统计收集逻辑)的前提。
六、实战调优建议(基于上述机制)
结合参数语义与源码约束,给出可落地的调优方向:
- 先盯默认值:
eviction_target=80/eviction_trigger=95/eviction_dirty_target=5/eviction_dirty_trigger=20是 WiredTiger 默认行为。除非有明确监控数据,否则不建议轻易改动。 - 遵守 target < trigger:任何一对 target/trigger 违反该约束,连接打开或 reconfigure 都会收到
EINVAL(evict_conn.c)。注意eviction_dirty_*与eviction_updates_*还会被自动钳制到不超过对应 total 阈值。 - 按负载类型微调:写密集负载可适当提高 dirty trigger 以聚合更多随机写;而延迟敏感场景应压低 trigger,让驱逐更早介入,避免应用线程在高峰期被"拉壮丁"导致尾延迟飙升。
- 工作线程数量:
threads_min/threads_max在1 ~ 64(WT_EVICT_MAX_WORKERS)之间动态伸缩,无需手工精确指定;只有极端高吞吐场景才考虑调大threads_max。 - 监控驱逐健康度:若
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),仅供参考