ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GraphQL Yoga 中 @envelop/validation-cache 插件源码级解析:验证结果 LRU 缓存的工作原理与实战配置

GraphQL Yoga 中 @envelop/validation-cache 插件源码级解析:验证结果 LRU 缓存的工作原理与实战配置 后端API设计【免费下载链接】graphql-yoga Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance great developer experience. The core of Yoga implements WHATWG Fetch API and can run/deploy on any JS environment.项目地址https://gitcode.com/gh_mirrors/gr/graphql-yoga点击查看免费下载导读envelop/validation-cache是 GraphQL Yoga 所基于的 Envelop 体系中的一个官方插件它为 GraphQL 请求链路中开销较大的validate步骤引入 LRU 缓存让相同操作的验证结果可以直接命中缓存而无需重复计算。本文将围绕该插件当前的 README 与 CHANGELOG结合 核心实现、测试用例 与 GraphQL Yoga 的内置集成源码完整讲解它的安装配置、缓存键构造原理、可定制接口以及版本演进中踩过的坑读完即可在自己的服务中正确使用或替换该缓存。插件定位为什么要缓存验证结果一个 GraphQL 请求在服务端通常要经历四个阶段解析parse→ 验证validate→ 执行execute→ 订阅subscribe。其中验证阶段会针对整个 schema 运行一组ValidationRule检查操作文档是否合法字段是否存在、类型是否匹配、参数是否正确等。当 schema 较大、规则较多时验证成本不容忽视。envelop/validation-cache的定位正是缓存这一步骤对相同的操作文档跳过重复验证直接复用上次的验证结果包括错误结果。根据插件 README 中基于基准测试的说明使用该插件可提升验证性能约 50%GraphQL Yoga 官方文档 Parsing and Validation Caching 亦指出解析缓存可带来约 60% 的性能提升验证缓存约 50%。需要说明的是该缓存保存的是验证结果一组GraphQLError而不是执行结果因此不会影响数据新鲜度属于安全且收益明显的优化手段。安装与快速接入安装在项目的 package.json 中声明该依赖yarn add envelop/validation-cache从 package.json 可以看到它要求node 18.0.0以envelop/core为 peer 依赖同时支持graphql的^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0全版本范围自 10.2.0 起兼容 graphql-js 17。基础用法在 Envelop 的插件链中注册即可import { execute, parse, specifiedRules, subscribe, validate } from graphql import { envelop, useEngine } from envelop/core import { useValidationCache } from envelop/validation-cache const getEnveloped envelop({ plugins: [ useEngine({ parse, validate, specifiedRules, execute, subscribe }), // ... 其他插件 ... useValidationCache({ // 选项 }) ] })插件链中的顺序并不强制要求useValidationCache放在最后但从 实现 来看它通过onValidate阶段的setValidationFn包装验证函数因此它会感知到链中其他插件动态添加的验证规则。API 参考cache选项与默认行为插件暴露的选项非常精简只有一个cacheexport type ValidationCacheOptions { cache?: ValidationCache }cache用于传入自定义缓存实例若不传插件会创建一个默认的 LRU 缓存。在 实现 中可以看到默认参数max: 1000最多缓存 1000 个验证结果超出后按 LRU 策略淘汰最久未使用的条目ttl: 3_600_000毫秒即 1 小时缓存条目存活时长过期后自动失效。值得注意的是这个默认值是当前版本的内部实现细节在插件的 2.0.0 版本之前max和ttl曾作为插件选项直接暴露2.0.0 起见 CHANGELOG移除了这两个选项改为要求用户通过自定义缓存实例来定制容量与过期策略。因此如果你需要调整缓存大小或 TTL请自己构造缓存实例import { LRUCache } from lru-cache import type { GraphQLError } from graphql import { useValidationCache } from envelop/validation-cache const cache new LRUCachestring, readonly GraphQLError[]({ max: 5000, // 自定义容量 ttl: 60_000 // 自定义 60 秒过期 }) const plugin useValidationCache({ cache })自定义缓存只需实现ValidationCache接口——一个带get/set方法的键值存储export interface ValidationCache { get(key: string): readonly GraphQLError[] | undefined set(key: string, value: readonly GraphQLError[]): void }这一点也使得该插件可以无缝对接外部存储如 Redis、内存表等而不必受限于内置 LRU。缓存键的构造原理核心机制验证结果能否安全复用完全取决于缓存键的设计。从 src/index.ts 可以看到缓存键由三部分拼接而成key schemaHash | ruleKey | documentString1. schemaHashSchema 哈希const schemaHashCache new WeakMapGraphQLSchema, string() function getSchemaHash(schema: GraphQLSchema) { let hash schemaHashCache.get(schema) if (hash) return hash const introspection introspectionFromSchema(schema) hash String(objectHash(introspection.__schema)) schemaHashCache.set(schema, hash) return hash }验证结果依赖 schema 的具体形态字段、类型、指令等因此键中必须包含 schema 身份。实现通过对introspectionFromSchema得到的 introspection 结果进行object-hash哈希得到一段稳定的 schema 指纹并用WeakMap缓存哈希结果避免重复计算。这一设计的演进值得关注在 5.1.0 之前插件检测到不同 schema 时是整体重置缓存5.1.0见 CHANGELOG改为将 schema 的 introspection sha1 哈希纳入缓存键从而在 schema 变化时只失效受影响的部分而不是清空整个缓存。在 5.1.0 时代该哈希由fast-json-stable-stringifyjs-sha1计算到 10.1.0底层哈希库由hash-it替换为object-hash见 CHANGELOG。2. ruleKey验证规则集合let ruleKey if (Array.isArray(args[2])) { for (const rule of args[2]) { ruleKey ruleKey rule.name } }validate的第三个参数是验证规则数组。不同规则组合会得出不同的验证结论因此规则名拼接串也被纳入键。规则名按数组原始顺序拼接源码注释也提到可以做排序但认为“可能过度”。这个字段是 5.0.5 版本加入的见 CHANGELOG用于防止跳过其他插件条件性添加的验证规则。因此该版本起要求自定义验证规则必须拥有唯一的name属性否则不同规则可能碰撞出相同的 key 前缀。3. documentString操作文档字符串const key: string schemaHashKey | ruleKey | getDocumentString(params.documentAST, print)操作文档由getDocumentString得到它优先返回 Envelop 内部documentStringMapWeakMap中已记录的原始文档字符串否则回退到print(document)打印 AST。getDocumentString由envelop/core导出定义在 document-string-map.ts该工具会记忆化结果。这里的关键改进来自 2.3.0见 CHANGELOG改用用户发送的原始文档字符串作为键而不是打印 AST。原因在于print会把文档规范化如调整空白与缩进导致语义相同但书写不同的文档产生不同键白白降低命中率而使用原始字符串可以在大多数情况下获得更高的缓存命中率。源码级流程onValidate 包装与错误结果缓存插件只挂载了一个生命周期钩子onValidate完整流程如下对应 src/index.tsonValidate({ params, setValidationFn, validateFn }) { setValidationFn((...args) { // 1. 计算 schema 哈希带 WeakMap 缓存 // 2. 拼接规则名 // 3. 构造 key schemaHash | ruleKey | documentString const cachedResult resultCache.get(key) if (cachedResult ! undefined) { return cachedResult // 命中直接返回跳过 validate } const result validateFn(...args) resultCache.set(key, result) // 未命中执行验证并写入缓存 return result }) }几个值得注意的实现细节通过setValidationFn而非直接读取params.rules构造键。源码注释明确指出插件链中的其他插件可能会在onValidate期间动态追加规则若在包装函数之外提前固定规则集合会导致缓存键与实际执行的规则不一致。错误结果同样被缓存。validate返回的GraphQLError[]包括验证失败的结果会被原样写入缓存。对应测试用例 “Should call validate once once when operation is cached and errored”见 validation-cache.spec.ts验证了同一个非法操作连续执行两次底层 validate 只被调用一次且两次返回结果一致。每次请求只读一次缓存。1.0.1 版本的 Patch见 CHANGELOG专门修复了单请求内多次读取缓存的问题。测试用例行为契约一览仓库自带的 validation-cache.spec.ts 使用envelop/testing的createTestkit和 jest mock 验证了插件的核心行为契约测试场景断言要点缓存为空时执行操作会调用一次真实的 validate同一操作重复执行validate 仅被调用 1 次缓存命中同一非法操作重复执行validate 仅被调用 1 次且两次返回的错误结果相等不同操作文档分别触发 validatekey 不同传入ttl: 1的自定义缓存等待 10ms 后再次执行validate 重新调用过期失效传入自定义 LRU 实例缓存实例的get/set被实际调用动态追加NoSchemaIntrospectionCustomRule首次执行__schema查询无错误第二次因规则集合变化而缓存未命中重新验证后返回错误——证明规则集合参与键构造动态切换 schemaschema1 → schema2 → schema1 过程中验证次数正确反映缓存键中包含 schema 指纹其中“规则集合参与键构造”与“schema 参与键构造”两个用例正是缓存键三要素schemaHash、ruleKey、documentString的实证支撑。与 GraphQL Yoga 的内置集成如果你使用的是 GraphQL Yoga而非裸 Envelop验证缓存默认就是开启的无需手动安装本插件。Yoga 在 use-parser-and-validation-cache.ts 中内置了合并的解析 验证缓存插件默认通过validationCache true开启可通过validationCache: false显式关闭验证缓存基于rulesKey规则名按,连接→WeakMapGraphQLSchema, WeakMapDocumentNode, GraphQLError[]的嵌套结构组织即同一规则集合 同一 schema 同一 DocumentNode 才命中缓存与envelop/validation-cache的键语义一致但使用WeakMap而非字符串键避免长期持有文档对象也支持传入自定义存储validationCache选项可接受boolean | Cachetypeof validate配合documentCache/errorCache即可替换默认缓存。完整的关闭与自定义配置方式见官方文档 Parsing and Validation Cachingimport { createServer } from node:http import { createYoga } from graphql-yoga import { schema } from ./my-schema const yoga createYoga({ schema, parserCache: false, // 关闭解析缓存 validationCache: false // 关闭验证缓存 })import { createServer } from node:http import { DocumentNode, GraphQLError } from graphql import { createYoga } from graphql-yoga import { documentCacheStore, errorCacheStore, validationCacheStore } from ./my-cache import { schema } from ./my-schema interface CacheStoreT { get(key: string): T | undefined set(key: string, value: T): void } const yoga createYoga({ schema, parserCache: { documentCache: documentCacheStore as CacheStoreDocumentNode, errorCache: errorCacheStore as CacheStoreError }, validationCache: validationCacheStore as CacheStorereadonly GraphQLError[] })版本演进时间线从 CHANGELOG 看设计决策通过 CHANGELOG.md 可以还原该插件十余个版本的设计演进脉络理解“为什么现在是这个样子”版本关键变更设计含义1.0.1每次请求只读一次缓存修复重复读取开销2.0.0移除max/ttl选项改为传入自定义缓存实例缓存策略与插件解耦2.2.0 / 2.1.0GraphQL v16 支持跟上上游版本2.3.0用原始文档字符串作为缓存键替代 AST 打印提升命中率4.5.0tiny-lru替换为lru-cacheclear弃用为reset统一缓存库与 API5.0.5规则名纳入缓存键避免跳过条件验证规则要求规则有唯一name5.1.0schema introspection 哈希纳入缓存键取代“换 schema 即清空缓存”的粗暴策略5.1.2sha1-es替换js-sha1修复 Edge Runtime 兼容5.1.3ESM 环境下的验证缓存修复解决 ESM 打包兼容6.0.0弃用 Node 14用WeakMapDocumentNode替代字符串 LRU配合解析缓存导出getDocumentString借助解析缓存实现更优的内存利用7.0.0弃用 Node 16跟随运行时基线9.0.1lru-cache升至^11.0.0依赖升级10.1.0hash-it替换为object-hash^3.0.0哈希库替换10.2.0支持 graphql-js 17适配subscribe的类型保持对最新 graphql 的兼容10.2.1package.json 补充 homepage 与 bugs 字段元数据完善其中 6.0.0 的变更尤其值得展开见 CHANGELOG当配合envelop/parser-cache源码使用时解析结果DocumentNode本身已被缓存此时再用字符串 LRU 缓存验证结果会产生重复的文档引用改为WeakMapDocumentNode后文档生命周期由解析缓存管理验证缓存不会持有额外引用内存更优。实践建议与注意事项综合源码、测试与演进历史在实际使用中请注意以下几点自定义验证规则必须设置唯一的name规则名是缓存键的一部分重名规则会导致键冲突。不要依赖默认max/ttl做容量控制调整容量与过期时间请传入自定义 LRU 实例默认 1000 条 / 1 小时仅适用于大多数场景。建议与useParserCache配合使用解析缓存能让getDocumentString优先命中原始文档字符串而非print进一步提高键的一致性与命中率。动态 schema / 动态规则场景无需额外处理schema 哈希与规则名都参与键构造schema 或规则变化时相关条目自然失效。Edge Runtime 与 ESM 支持均已就绪5.1.2 / 5.1.3 起修复了边缘运行时与 ESM 环境问题当前版本可以放心用于 Cloudflare Workers、Deno 等环境。使用 GraphQL Yoga 时无需重复安装Yoga 默认内置验证缓存直接通过validationCache: false或自定义 store 控制即可。总结envelop/validation-cache通过“schema 哈希 规则名集合 操作文档字符串”三要素构造缓存键以 LRU 缓存安全地复用validate的结果包括错误结果为 GraphQL 服务带来约 50% 的验证性能提升。它既可作为独立 Envelop 插件接入也已默认内置在 GraphQL Yoga 中其十余个版本围绕命中率、内存效率、运行时兼容的持续演进本身也是一份值得借鉴的缓存插件设计范本。深入源码路径 src/index.ts、测试 与 CHANGELOG 可以进一步验证上述全部结论。赞分享后端API设计【免费下载链接】graphql-yoga Rewrite of a fully-featured GraphQL Server with focus on easy setup, performance great developer experience. The core of Yoga implements WHATWG Fetch API and can run/deploy on any JS environment.项目地址https://gitcode.com/gh_mirrors/gr/graphql-yoga点击查看免费下载相关推荐envelop/parser-cache 插件实战用 LRU 缓存为 GraphQL Yoga 的 parse 阶段提速约 60%envelop/parser cache 插件实战用 LRU 缓存为 GraphQL Yoga 的 parse 阶段提速约 60% GraphQL 请求处理后端API设计Envelop Extended Validation 实战在 graphql-yoga 中编写可访问 Variables 的 GraphQL 验证规则Envelop Extended Validation 实战在 graphql yoga 中编写可访问 Variables 的 GraphQL 验证规则 本文后端API设计envelop/validation-cache 使用指南为 GraphQL 校验层引入 LRU 缓存将 validation 性能提升约 50%envelop/validation cache 使用指南为 GraphQL 校验层引入 LRU 缓存将 validation 性能提升约 50% 本文围后端API设计上一篇React Loading Skeleton 终极指南10分钟创建完美加载骨架屏下一篇CastNow 使用教程命令行 Chromecast 播放器终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表