ARTICLE DETAIL

资讯详情

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

Sanity 工程实践:跨请求 LRU 缓存——React.cache() 之外的高性能服务端缓存

Sanity 工程实践:跨请求 LRU 缓存——React.cache() 之外的高性能服务端缓存 Sanity 工程实践跨请求 LRU 缓存——React.cache() 之外的高性能服务端缓存【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity本篇基于仓库中的 server-cache-lru 规则文档展开讲解服务端缓存的两种作用域边界React.cache()只能在一个请求内去重而用户连续点击、连续操作触发的多个请求之间的数据共享需要跨请求的 LRU 缓存。读完后你将能够独立实现带容量与过期控制的服务端 LRU 缓存判断不同运行环境长驻函数实例 vs 传统 Serverless下的正确缓存选型并从 Sanity 仓库自身的 LRU/SWR 实现中看到这一模式在 React 代码库中的真实落地方式。规则背景它属于哪一类最佳实践该文档位于仓库内置的 Vercel React 最佳实践技能目录 .agents/skills/vercel-react-best-practices/rules/server-cache-lru.md是 57 条性能规则之一。按照 SKILL.md 的优先级分类server-前缀规则属于第三优先级Server-Side Performance服务端性能影响级别为 HIGH其元数据明确标注了impact: HIGH、impactDescription: caches across requests在请求之间进行缓存tags: server, cache, lru, cross-request。它与 server-cache-react 规则 是一对互补方案后者用React.cache()做单请求内去重MEDIUM 影响前者用 LRU 缓存做跨请求共享HIGH 影响。完整的编译版本见 AGENTS.md 的 3.3 节。问题本质React.cache() 的作用域边界规则文档开宗明义React.cache()only works within one request只能在单个请求内生效。它适用于一次页面渲染中多个组件重复请求同一份数据的场景——例如getCurrentUser()在组件树中被调用多次React.cache()用浅相等Object.is匹配参数并保证查询只执行一次。但缓存的生命周期随请求结束而销毁。文档举了跨请求的典型场景用户先点击按钮 A再点击按钮 B——这两个动作是两次独立的请求two separate requests。第一次请求的结果无法被第二次请求看到于是每个按钮背后各自再查一遍数据库。当连续的用户操作在短时间内命中多个需要相同数据的端点时Use when sequential user actions hit multiple endpoints needing the same data within seconds就出现了规则文档定义的 LRU 缓存适用区间。核心实现lru-cache 的完整可运行示例原文档给出的标准实现基于 Node 生态广泛使用的lru-cache包npm 包名lru-cache即 isaacs/node-lru-cache 项目。以下完整保留原文示例并补充参数注释import {LRUCache} from lru-cache const cache new LRUCachestring, any({ max: 1000, // 容量上限最多缓存 1000 个条目 // 超出时淘汰最久未访问Least Recently Used的条目 ttl: 5 * 60 * 1000, // 存活时间每个条目 5 分钟毫秒后过期 }) export async function getUser(id: string) { const cached cache.get(id) // 命中则直接返回不产生任何 IO if (cached) return cached const user await db.user.findUnique({where: {id}}) cache.set(id, user) // 未命中时回源查询并写入缓存 return user } // Request 1: DB query, result cached —— 第一次请求走数据库结果入缓存 // Request 2: cache hit, no DB query —— 第二次请求命中缓存零数据库查询这段代码有三个值得注意的实现要点缓存是模块级单例。const cache声明在模块顶层函数实例存活期间所有请求共享同一份内存缓存——这正是跨请求得以成立的前提详见下文部署环境一节。键的设计决定命中率。示例中以用户id字符串为键。实际项目中若一个缓存对象要服务多种查询形态键中应拼入全部区分维度例如id、perspective、语言等否则会发生错误的缓存命中。max与ttl是两道独立的防线。max约束内存占用上限ttl约束数据新鲜度。文档默认值1000 条 / 5 分钟适合秒级内重复访问同一份数据的典型交互节奏对变更频繁的数据应调小ttl对大体量对象应调小max并配合更细粒度的键。缓存命中路径上没有任何异步 IO因此跨请求 LRU 缓存的收益直接体现在第二次及后续请求的响应延迟、数据库负载与外部 API 配额消耗上。部署环境差异实例复用决定缓存有效性规则文档特别区分了两类运行环境这是选型时必须核实的前提在 Vercel 的 Fluid Compute 上LRU 缓存尤其有效因为多个并发请求可以共享同一个函数实例及其内存中的缓存。缓存因此天然跨请求存活无需引入 Redis 之类的外部存储。这与每个请求冷启动一个新实例的传统模型有本质区别——在实例可能被随时回收或从不复用的环境下模块级缓存的存活窗口不可预期。在传统 Serverless 上每次调用运行在相互隔离的环境中模块级内存缓存对其他调用不可见。此时若仍需要跨请求共享数据应考虑 Redis 等跨进程cross-process缓存方案LRU 内存缓存只能作为进程内的补充层不能承担跨请求缓存的全部职责。一句话总结选型逻辑先确认你的运行时是否保证函数实例在请求间存活复用再决定用纯内存 LRU还是内存 LRU Redis 二级缓存。Sanity 仓库中的同族模式QuickLRU 与 SWR虽然 Sanity Studio 本身是内容管理前端而非 Next.js 服务端应用但LRU 缓存回源这一模式在仓库源码中有真实的工程化落地可以作为上述规则的补充佐证。rxSwr.ts基于 QuickLRU 的 SWR 缓存层。packages/sanity/src/core/util/rxSwr.ts 实现了 Stale-While-Revalidate陈旧数据先行、后台回源更新的响应式缓存它定义了一个最小缓存接口SWRCacheT第 9-17 行只要求get/has/set/delete四个方法——get在键不存在时抛错调用方必须先has检查这与规则文档示例中先cache.get判空再回源的防御式写法异曲同工createSWR(options: {maxSize: number})第 27-43 行返回一个 rxjs 操作符订阅时先用concat把缓存中的旧值标记fromCache: true同步发出随后上游 Observable 的最新值fromCache: false经tap写回缓存。旧值先行、新值补齐——这正是 SWR 语义而底层容量控制交给 LRU唯一的缓存层实现是createLRUCache第 50-70 行内部使用quick-lru的QuickLRUstring, {value: T}以maxSize作为容量上限。值得注意的是其实现细节存入的是{value}包装对象而非值本身这使得缓存层可以扩展出标记值已失效但保留旧值等能力而SWRCache接口保持不变——一个值得借鉴的抽象手法。该依赖在 packages/sanity/package.json 中声明为quick-lru: ^7.3.0同时工作区的 pnpm-lock.yaml 中存在lru-cache的多个版本如 10.4.3、11.5.2说明该库生态在依赖树中是被实际使用的。presentation 模块中的待办印证了键设计的重要性。packages/sanity/src/presentation/useDocumentsOnPage.ts 中有一段明确的TODO第 19-22 行当前文档缓存按 perspective 分别存两份 Record计划重构为以 perspective数组时做稳定排序 URL path 为键的 lru-cache使在视角与 URL 之间切换更快。这是一个现成的反面教材键维度选择不当当前实现是整份替换而非逐键查找会让缓存切换成本变高——恰好呼应了上文键中拼入全部区分维度的设计要点。适用场景与使用边界综合规则文档与源码证据跨请求 LRU 缓存的适用判断可以归纳为判断维度适用信号不适用信号访问模式连续用户操作在秒级内命中多个需要相同数据的端点数据只被单次请求消费数据特征读多写少、以稳定 ID 等标量为键可寻址数据变更频繁ttl内一致性无法接受运行环境函数实例在请求间存活复用如 Fluid Compute严格隔离的传统 Serverless需叠加 Redis资源预算可估算max上限内存占用可控值体量大且条目数不可控需要明确的边界LRU 缓存解决的是重复计算/重复回源的性能问题不是数据一致性机制。ttl只是兜底对强一致性要求如支付、库存的数据不应依赖它缓存只读副本写入路径要么绕过缓存直接读源要么在写后主动delete对应键——这一点也可以从 Sanity 的SWRCache接口必须暴露delete方法的设计中得到印证。小结React.cache()管请求内LRU 缓存管请求间二者按数据生命周期分层使用单请求内的鉴权、数据库查询去重交给React.cache()跨请求的热点数据共享交给带max/ttl双约束的模块级 LRU 缓存。实现时把握三点——模块级单例声明、键包含全部区分维度、先核实运行时的实例复用能力——即可获得规则文档所描述的效果第一次请求回源并缓存后续请求零回源命中缓存。主要依据文件规则原文.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md配套规则.agents/skills/vercel-react-best-practices/rules/server-cache-react.md规则总览与优先级分类.agents/skills/vercel-react-best-practices/SKILL.md仓库内 LRU/SWR 实现packages/sanity/src/core/util/rxSwr.ts键设计待办示例packages/sanity/src/presentation/useDocumentsOnPage.ts【免费下载链接】sanitySanity Studio – Rapidly configure content workspaces powered by structured content项目地址: https://gitcode.com/GitHub_Trending/sa/sanity创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表