缓存 API
概述
monSQLize 提供面向集合读操作的数据库查询缓存、可选本地/远端缓存组合、手动失效和统计能力。缓存失效用于让常见应用读缓存足够及时,但它不是与 MongoDB 写入原子绑定的提交步骤。
本页只覆盖当前数据库运行时缓存路径:查询结果缓存、Bookmark 缓存、Redis 远端缓存、分布式失效与缓存统计。
核心特性
- ✅ TTL 过期:自动淘汰过期数据
- ✅ LRU 淘汰:缓存满时淘汰最少使用的条目
- ✅ 多层缓存架构:本地缓存 + 可选远端
CacheLike,常见实现是 Redis - ✅ 双层缓存机制:查询结果缓存 + Bookmark 分页缓存
- ✅ 手动失效:通过
invalidate()方法清理指定集合的缓存 - ✅ 统计监控:命中率、淘汰统计、内存占用
缓存配置
全局缓存配置
在构造函数中配置全局缓存参数:
查询级缓存配置
在具体查询中指定缓存 TTL(毫秒):
重要说明:
- ✅
cache参数的值是毫秒数(TTL) - ✅
cache: 0表示禁用缓存 - ✅ 默认值:未设置时不使用缓存
- ❌ 不支持
cache: true和单独的ttl参数
缓存键生成
缓存键包含数据库与集合命名空间、操作名、标准化后的查询或聚合 pipeline,以及会影响返回结果的 driver options。
runtime 使用 BSON-aware 的稳定指纹生成缓存键:
ObjectId会表示为{ $oid }。Date会表示为{ $date }。RegExp会表示为{ $regex, $flags }。cache、meta、explain、stream这类控制选项不会单独生成一个数据缓存条目。
相同查询的不同参数会生成不同的缓存键:
TTL(生存时间)过期
自动过期
缓存条目在 TTL 到期后自动失效:
TTL 最佳实践
LRU(最近最少使用)淘汰
淘汰机制
当缓存条目数达到 maxEntries 时,自动淘汰最久未访问的条目。
LRU 访问顺序
多层缓存
monSQLize 提供两种多层缓存机制:
1. 本地 + 远端缓存架构(MultiLevelCache)
支持本地内存缓存(LRU-Cache)+ 远端缓存(Redis/Memcached)的两层架构,实现更高的缓存命中率和更大的缓存容量。
CacheLike 接口规范
要作为 remote 使用,缓存实例必须实现以下 10 个方法(CacheLike 接口):
验证建议:
- 优先直接使用
MonSQLize.createRedisCacheAdapter(),避免手写CacheLike适配器 - 若必须自定义 remote cache,请逐项确保实现了上表列出的 10 个方法
- 当前不再提供旧包装层里的
MemoryCache.isValidCache()校验工具
缓存策略
读操作:
- 优先从本地缓存读取(内存,速度快)
- 本地未命中则查询远端缓存(网络,速度较慢)
- 远端命中则异步回填到本地缓存(可配置)
- 远端失败则优雅降级(返回 undefined)
写操作:
both(默认):本地 + 远端双写,保持两级缓存同步;数据库提交与缓存失效不是原子步骤local-first-async-remote:本地优先,远端异步写入,提升性能
删除操作:
- 删除本地缓存(立即生效)
- 删除远端缓存(尽力而为)
delPattern支持可选的集群广播机制
配置示例
建议在应用启动时创建一次 Redis 适配器,并在下面的示例中复用同一个适配器。如果项目已经通过 cache.redis.url、cache.redis.client 或其他共享配置集中管理 Redis,请让这份共享配置指向 Redis,不要在每个示例里重复写 URL。
方式 1:只使用远程 Redis 缓存(无本地缓存)
如果不需要本地内存缓存,可以直接传入 Redis 适配器作为缓存实例:
适用场景:
- 多实例部署,需要共享缓存
- 服务器内存受限,不适合本地缓存
- 缓存数据量较大(百万级)
- 需要持久化缓存(Redis 持久化)
性能特点:
- 读取延迟:1-2ms(网络 + Redis 查询)
- 缓存容量:取决于 Redis 内存(可达 GB 级)
- 缓存一致性:跨实例共享;写后失效是显式 best-effort 流程,分布式广播最终收敛,不与数据库提交原子绑定
方式 2:本地 + 远程双层缓存(推荐高性能场景)
使用 multiLevel: true 启用本地内存 + Redis 双层架构:
适用场景:
- 高并发读取场景
- 热点数据频繁访问
- 希望热点读优先走本地内存的低延迟路径
- 多实例部署并共享缓存;写后失效仍是 best-effort、最终收敛
性能特点:
- 本地缓存命中:0.001ms(内存读取)
- Redis 缓存命中:1-2ms(网络 + Redis)
- 数据库查询:10ms+
方式 3:使用已创建的 Redis 实例
方式 4:手动封装 Redis(适用于自定义需求)
策略配置
性能对比
三种缓存策略对比:
场景选择建议:
性能影响示例:
最佳实践
-
本地缓存配置
- 设置合理的
maxEntries(推荐 1-10 万条) - 热点数据优先存入本地
- 设置合理的
-
远端缓存配置
- Redis 连接池配置(避免连接耗尽)
- 设置合理的超时时间(推荐 50-100ms)
- 监控 Redis 内存使用
-
写策略选择
- 需要两级缓存同步:使用
both(默认);强一致读路径应绕过缓存或使用事务/业务侧协调 - 高并发写入:使用
local-first-async-remote
- 需要两级缓存同步:使用
-
故障降级
- 远端缓存故障自动降级到本地缓存
- 不影响业务正常运行
2. 查询结果 + Bookmark 双层缓存
Bookmark 分页缓存
缓存失效行为
写操作默认不失效读缓存。需要写入后清理缓存时,可以在单次写入上使用 cache.invalidate 精准指定缓存,也可以使用 autoInvalidate: true 触发集合级 broad 失效。完整说明见 缓存失效。
这是 best-effort 的缓存一致性模型:
- 数据库写入成功后,如果后续缓存失效或分布式广播失败,不会回滚 MongoDB 写入。
- 事务内写入会先记录待失效 intent,只在事务成功 commit 后 flush;事务 abort 不会 flush。
- 跨实例缓存失效需要配置
cache.distributed与 Redis Pub/Sub,并且仍是最终收敛,不是与数据库 commit 原子绑定。 cache.invalidate: false或cache.invalidate: []会覆盖全局cache.autoInvalidate: true,表示本次写入不清理缓存。invalidate()仍适用于外部工具改数据、应用侧额外缓存或需要手动刷新缓存的场景。
写入失效示例
手动清理
使用 clearBookmarks() 手动清理 Bookmark 缓存:
统计监控
获取缓存统计
统计指标说明
监控与告警
性能测量
缓存命中会避开部分或全部数据库工作,但不代表固定时延或加速倍率。应在相同查询、负载、并发、序列化路径、MongoDB 拓扑与缓存后端下,比较 find、findPage 和 distinct 的冷、热路径分布。保存带 commit 与环境信息的原始样本,再为具体工作负载定义回归预算。
负载、环境、数据集、命令、产物与预算字段要求参见性能证据。
最佳实践(缓存策略文档)
1. 根据数据特征选择 TTL
2. 合理设置 maxEntries
3. 监控缓存健康度
4. 批量预热缓存
5. 缓存穿透保护
常见问题
Q: 缓存会占用多少内存?
A: 每个缓存条目包含查询键(约 100-200 字节)和查询结果(取决于数据大小)。
估算公式:
Q: 如何选择合适的 maxEntries?
A: 根据服务器内存和查询热点数据量选择:
建议:
- 从默认值 100000 开始
- 监控淘汰率(evictionRate)
- 如果 evictionRate > 10%,增加 maxEntries
Q: 如何手动清理缓存?
A: monSQLize 已支持写操作;但在以下场景里仍然建议手动清理缓存:
注意:
- 当使用外部工具修改数据后,仍需手动调用
invalidate()清理缓存 - 通过 monSQLize 发起的写操作只有在
cache.invalidate、autoInvalidate或全局cache.autoInvalidate配置后才会清理读缓存 - 若你的缓存策略跨进程/跨节点,建议同时结合分布式失效广播
Q: 如何禁用缓存?
A: 有三种方式:
Q: 缓存和 Bookmark 有什么区别?
A:
- 缓存(find/findOne/aggregate/distinct):缓存查询结果(完整的文档列表)
- Bookmark(findPage):缓存分页游标(仅存储每 N 页的起始位置)
缓存失效 API
invalidate()
手动清除指定集合的所有缓存。适用于需要立即刷新缓存的场景。
方法签名
参数说明
无参数。清除当前绑定集合的所有查询缓存。
返回值
返回 Promise<void>。
使用场景
1. 外部工具修改数据后刷新缓存
2. 定时刷新缓存
3. 多集合缓存清除
使用说明
重要提示:monSQLize 当前版本已经支持 insertOne / updateOne / deleteOne 等写操作;通过 monSQLize 写入时,只有显式配置了失效策略才会清理相关缓存。invalidate() 仍然用于以下场景:
- 外部写入后的显式清理:
- 使用 MongoDB Shell、Compass 或其他应用直接修改数据后
- 旁路批量导入、迁移脚本或手工修复数据后
- 主动刷新策略:
- 定时刷新热点集合缓存
- 临时强制清理某个集合的查询缓存
- 自定义缓存边界:
- 使用自定义 cache adapter 或业务侧额外缓存时,需要自行决定清理范围
当前最佳实践:
- 业务写入优先通过 monSQLize 执行,避免绕过缓存失效链路
- 外部工具或旁路脚本修改数据后,立即调用
invalidate() - 定期监控缓存命中率,决定是否需要定时刷新
- 避免过度使用,仅在必要时清除缓存
最佳实践(缓存失效 API)
1. 避免过度使用
2. 结合缓存监控
3. 批量清除时使用并行
4. 定时刷新的错误处理
注意事项
- 清除范围:
invalidate()只清除指定集合的查询缓存,不影响其他集合 - 性能影响:清除缓存后,下次查询需要访问数据库,会有性能损耗
- 不清除 Bookmarks:
invalidate()不清除 findPage 的 Bookmark 缓存,需要使用clearBookmarks() - 旁路写入限制:通过外部工具或其他服务绕过 monSQLize 写入时,必须手动调用
invalidate()或执行等效的业务清理动作
相关方法
clearBookmarks(collectionName?)- 清除 findPage 的 Bookmark 缓存(参见 Bookmarks 文档)getCache()- 获取缓存实例,可调用clear()清除所有缓存(参见 工具方法文档)