GraphQL SWR 缓存(NestJS,code-first)
概述
根据意图而非机制进行缓存。一个策略形状,一个可观测事件流,一个每请求跟踪。默认在服务层挂载;仅在测量后扩展到字段/操作/传输层。
三个硬道理决定了每一个决策:
- Apollo Server 没有内置的 stale-while-revalidate。 其
responseCachePlugin仅支持 TTL 且针对操作级别。真正的 SWR 来自你自己的层(async-cache-dedupe,或基于 Mercurius 驱动的mercurius-cache)或来自 CDN。 - 作用域默认为
public。 公共条目被所有调用者完全共享,因此仅当结果对所有调用者完全相同时才正确。一旦值因调用者而异,则必须标记为perUser——否则会导致泄漏。 - Nest 拦截器不包装服务方法,且
@nestjs/cache-manager的CacheInterceptor在 GraphQL 中不起作用。 因此,你需要的服务层装饰器是一个包装去重引擎的方法包装器,而不是框架拦截器。
何时使用
- NestJS GraphQL(code-first)中,解析器/服务重复访问数据库或上游 API。
- 你需要消除 TTL 过期时的延迟峰值和资源争用(真正的 SWR)。
- 你需要考虑多用户缓存安全性/泄漏。
- 你需要了解命中/过期/未命中的决策。
- 你需要通过一个标志完全关闭缓存——用于开发、CI 或事件(参见第 1 节
setup-disable-switch)。 - 你正在首次将缓存能力接入应用(参见第 1 节)。
何时不使用:
- 如果你的公共 GET/APQ 流量已经通过 CDN 进行响应缓存——那么从那里开始。
- 写入/变更路径,或者没有重复使用且读放大较低的用户数据(缓存带来的风险大于收益)。
回调契约
key、@CacheTag 和 @InvalidateTags 接受函数。如果参数设置错误,缓存将静默未命中,标签也将静默无法清除。
| 回调 | 签名 | args 是什么 |
|---|---|---|
key | (args, ctx) => unknown[] | Promise<unknown[]> | 方法的参数列表,按调用顺序——args[0],绝不是 args.id |
@CacheTag | (result, args) => string[] | null | 相同的列表;result 是方法的返回值 |
@InvalidateTags | (result, args) => string[] | 相同的列表;result 是写入的返回值 |
- 基于位置,而非命名。 对于
getTitle(id: string),id 是args[0]。 - 结果在前出现在两个标签回调中。原始引擎自己的
references是(args, key, result)——顺序不同,第一个参数也不同(参见driver-async-cache-dedupe)。 ctx(userId、tenantId、locale,以及gen)由请求派生并由buildCacheContext组装;在请求外部为空,因此作业通过参数传递所需内容。- 类型化:默认值为
unknown[]/unknown——注解元组(@Cacheable<[string]>、@CacheTag<[string]>),否则回调体无法编译。 - 字段级不同:此处
args是解析器的命名参数对象,ctx是 GraphQL 上下文(driver-mercurius-cache)。
完整契约、键包 { a, k, u } 以及序列化边界情况:policy-key-derivation。运行时实现:setup-decorator-runtime。
按优先级分类的规则
| 优先级 | 类别 | 影响 | 前缀 |
|---|---|---|---|
| 1 | 一次性设置 | 关键 | setup- |
| 2 | 缓存策略 | 关键 | policy- |
| 3 | 泄漏安全 | 关键 | safety- |
| 4 | 失效 | 高 | invalidation- |
| 5 | 可观测性 | 中 | observability- |
| 6 | 驱动 | 中 | driver- |
快速参考
1. 一次性设置(关键)
setup-decorator-runtime- 将装饰器实现为带有稳定名称的方法包装器setup-disable-switch- 提供一个配置开关以绕过服务级缓存setup-engine-choice- 使用去重引擎,并以秒为单位计算 TTLsetup-fail-open- 故障开放,且绝不缓存错误setup-file-placement- 根据谁导入它来放置能力setup-identity-context- 从 AsyncLocalStorage 读取调用者,而非 Scope.REQUESTsetup-module-wiring- 使用 forRootAsync 一次性连接缓存模块setup-storage-and-invalidation- 启用失效并设置 referencesTTL 和 GC
2. 缓存策略(关键)
policy-five-knobs- 了解五个旋钮policy-key-derivation- 自动派生密钥,永不使用 JSON.stringifypolicy-mount-at-service- 默认在服务层挂载policy-one-writing-pattern- 装饰器是唯一的写入模式policy-service-escape-hatch- 服务 API 是逃生舱,而非第二种模式
3. 泄漏安全(关键)
safety-default-public- 作用域默认为公共——将因调用者而异的读取标记为 perUsersafety-key-dimensions- 作用域仅覆盖用户——所有其他维度都放入密钥中safety-no-leak-checklist- 对每个缓存读取运行无泄漏检查清单
4. 失效(高)
invalidation-after-success- 仅在成功写入后失效invalidation-atomic-purge- 原子化清除并限制标签基数invalidation-generation-prefix- 对巨大的或粗粒度的域使用生成前缀invalidation-tags-purge-only- 标签仅用于清除,绝不用作查找
5. 可观测性(中)
observability-limits-of-events- 了解引擎无法报告的内容observability-metrics-and-tests- 跟踪证明修复的指标,并根据结果进行测试observability-per-request-trace- 提供一个每请求跟踪,在 GraphQL 上仅用于开发observability-structured-events- 发出结构化的缓存事件,而非日志字符串
6. 驱动(中)
driver-apollo-response-cache- 选项 C——Apollo responseCachePlugin(操作级别,仅 TTL)driver-apollo-vs-mercurius- 从你的驱动中选择字段级选项driver-async-cache-dedupe- 选项 A——async-cache-dedupe(服务级别,传输无关)driver-cdn-transport- 选项 E——传输/CDN SWR(仅公共读取)driver-envelop-response-cache- 选项 D——Envelop useResponseCache(非 NestJS 原生)driver-federation-router- 仅限联邦——Apollo Router 响应缓存driver-mercurius-cache- 选项 B——mercurius-cache(字段级别,真正 SWR)driver-version-pinning- 锁定版本——此领域漂移
如何使用
从一次性设置部分开始,然后阅读策略、安全和失效规则以做出当前决策。规则文件位于 rules/ 下:
rules/setup-module-wiring.md
rules/_sections.md
每个规则文件包含:
- 规则是什么以及它防止的故障
- 带有解释的错误示例
- 带有解释的正确示例
- 参考资料
完整编译文档
有关包含所有规则展开的完整指南,请参见 AGENTS.md。