FEATURED · 精选文章

Polar 中的跨请求 LRU 缓存:在 Next.js/Vercel 上打通 React.cache() 的请求边界

发布时间 / 2026/9/16 12:25:11
来源 / 创域科博编辑部
栏目 / 资讯中心
Polar 中的跨请求 LRU 缓存:在 Next.js/Vercel 上打通 React.cache() 的请求边界 Polar 中的跨请求 LRU 缓存在 Next.js/Vercel 上打通 React.cache() 的请求边界【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar导读本指南讲解来自 Vercel React 最佳实践技能包vercel-react-best-practices位于 clients/apps/web/.agents/skills/vercel-react-best-practices/中的一条 HIGH 优先级规则跨请求 LRU 缓存Cross-Request LRU Caching。React.cache()只能在一个请求内做去重而 LRU 缓存能把用户连续点击多个端点时共享的数据在函数实例内跨请求复用避免重复数据库查询。读完本文你将掌握LRU 缓存的标准实现、它与React.cache()的边界划分、在 Vercel Fluid Compute 与传统 Serverless 两种部署模型下的取舍以及 Polar 仓库中真实存在的每请求缓存代码作为对照。一、问题背景为什么React.cache()不够用在 server-cache-react.md 规则中React.cache()被用于单请求内的去重在同一个请求的组件树里多次调用同一个异步函数只会真正执行一次。典型场景包括数据库查询Prisma、Drizzle 等认证信息获取文件系统操作其他非fetch的异步工作这在 Polar 的 web 端有大量真实应用。以 src/utils/organization.ts 为例// Tell React to memoize it for the duration of the request const _getOrganizationBySlugCached ( api: Client, slug: string, cached: boolean true, ) _getOrganizationBySlug(api, slug, cached) export const getOrganizationBySlug cache(_getOrganizationBySlugCached)类似的模式还出现在 src/utils/order.tsgetOrderById cache(_getOrderById)、src/utils/checkout.ts、src/utils/product.ts、src/utils/subscription.ts 等数据访问工具中。但React.cache()有一个明确的边界缓存生命周期只有一个请求。当用户点击按钮 A、再点击按钮 B这是两个顺序发生的独立请求React.cache()的缓存已经失效相同的数据会被重新查询。这正是 server-cache-lru.md 这条规则要解决的问题对于顺序请求之间共享的数据改用 LRU 缓存。判断基准如果多个端点需要在几秒内读到同一份数据例如用户连续操作触发的多个请求就应该考虑 LRU 缓存而不是只依赖React.cache()。二、标准实现基于lru-cache的跨请求缓存规则给出了一个可直接落地的实现骨架lru-cache是社区广泛使用的 LRU 缓存库import { LRUCache } from lru-cache const cache new LRUCachestring, any({ max: 1000, ttl: 5 * 60 * 1000, // 5 minutes }) export async function getUser(id: string) { const cached cache.get(id) 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两个核心配置项说明如下配置项示例值作用与建议max1000缓存最大条目数超出后按 LRU 策略淘汰最久未访问的条目防止内存无限增长。应根据单实例可容纳的数据量合理设置ttl5 * 60 * 1000条目存活时间毫秒。过期条目在下次访问时自动失效并重新查询是缓存与数据一致性之间的第一道平衡注意缓存读取逻辑存在一个常见陷阱如果缓存值本身可能是null/undefined例如用户不存在简单的if (cached) return cached会把合法空值当成未命中。更稳妥的写法是用cache.has(id)先判断存在性或引入空值占位符来缓存负面结果。另外模块级单例是这种缓存的关键cache实例必须定义在模块顶层而非函数内部或每个请求内新建否则每个请求都会创建全新的缓存跨请求共享就无从谈起。这与server-hoist-static-io将静态 I/O 提升到模块级的思路一脉相承。三、与 Vercel Fluid Compute 的协同无需 Redis 的跨请求共享规则特别指出在Vercel Fluid Compute模型下LRU 缓存的收益会被放大多个并发请求可以共享同一个函数实例function instance和它的内存缓存。这意味着缓存可以跨请求持久存在无需引入 Redis 这类外部存储。这是因为 Fluid Compute 让函数实例在处理完一个请求后继续存活、复用而非每次冷启动销毁。实例内存中的 LRU 缓存因此天然成为进程内共享缓存请求 1 查询数据并写入缓存请求 2、请求 3…落在同一实例上时直接命中内存零网络开销、零外部依赖在流量较小时单个实例即可服务大量顺序请求命中率可观。这种方案的工程价值在于架构极简没有额外的运维组件、没有缓存一致性的分布式难题、也没有冷热数据迁移成本。对 Polar 这种以计费数据为主的 Web 应用凡是秒级窗口内、跨端点复用的热点数据都符合这种模式的使用前提。四、传统 Serverless 下的取舍何时该上 Redis规则同时给出了对照传统 Serverless 每次调用都在隔离环境中运行函数实例之间不共享内存进程内 LRU 缓存无法跨调用复用。此时需要区分两种诉求单次调用内的去重仍然用React.cache()参考 server-cache-react.md跨进程/跨实例的共享缓存引入 Redis 等外部存储例如ioredis 带 TTL 的SET/GET或 Redis 的 LRU 淘汰策略maxmemory-policy allkeys-lru。结论可以归纳为一张决策表部署形态请求内去重跨请求复用推荐方案任意 Next.js 部署✅React.cache()❌每请求去重即可Vercel Fluid Compute✅React.cache()✅ 实例内存 LRU进程内lru-cache免外部依赖传统 Serverless✅React.cache()❌需外部存储跨进程共享才上 Redis需要谨慎的是进程内缓存意味着数据可能存在秒级滞后且实例回收scale-to-zero会导致缓存清空。因此 LRU 缓存只适合可容忍短暂陈旧的读密集型数据不能用于强一致性的计费、订单状态等关键写路径。五、与 React.cache() 的组合用法分层缓存server-cache-react与server-cache-lru两条规则并不互斥而是可以组合成两级缓存第一级请求内React.cache()保证同一请求内组件树多次调用只执行一次避免重复的get与潜在的重复回填第二级跨请求LRU 缓存命中则直接返回未命中才执行真实 IO 并回填同时维持ttl与max约束。import { cache } from react import { LRUCache } from lru-cache const lru new LRUCachestring, unknown({ max: 1000, ttl: 300_000 }) const _getUser async (id: string) { const hit lru.get(id) if (hit ! undefined) return hit const user await db.user.findUnique({ where: { id } }) lru.set(id, user ?? null) return user } export const getUser cache(_getUser)Polar 的 src/utils/organization.ts 展示了另一个实战细节getOrganizationBySlugOrNotFound在缓存未命中时主动绕过缓存重新拉取一次用于规避新组织刚创建时的竞态条件——这提示我们跨请求缓存必须为缓存击穿/陈旧数据预留显式的绕过通道。六、实践要点速查明确缓存生命周期React.cache()止于请求LRU 止于实例Fluid Compute或需要 Redis传统 Serverless。实例必须模块级单例放在模块顶层避免每个请求重建。配置max与ttlmax防内存膨胀ttl控制陈旧度空值结果建议显式缓存或使用has()判断。只在读多写少、可容忍短暂陈旧的数据上使用计费、订单等强一致路径不要放入进程内缓存。预留绕过通道参考 Polar 的getOrganizationBySlugOrNotFound命中失败时允许强制刷新规避竞态。参考规则原文rules/server-cache-lru.md姊妹规则请求内去重rules/server-cache-react.md技能包总览64 条规则 / 8 大分类README.md 与 SKILL.mdPolar 每请求缓存实战src/utils/organization.ts、src/utils/order.ts、src/utils/checkout.ts【免费下载链接】polarPolar — A billing platform for the intelligence era项目地址: https://gitcode.com/GitHub_Trending/po/polar创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻