返回 文章 build CMS 文章

Vercel 如何将 CDN 元数据查找延迟降低 91%

Vercel 用分片分组元数据替代逐路径缓存,将 P99 查找延迟从 203ms 降至 31ms,并让部署更快。

CDN性能优化元数据缓存
成长分 / 100 78 综合收获、行动、留存与影响

Vercel 如何将 CDN 元数据查找延迟降低 91%
为什么值得读了解大规模 CDN 路由中元数据查找的性能瓶颈与优化思路。

学习如何通过分片、索引和二分查找平衡缓存命中率与传输成本。

关键洞察
  1. 逐路径缓存导致频繁部署的大型站点反复出现缓存未命中,因为每次部署都会创建新的缓存键。
  2. 将路径元数据分组到约 200 KB 的分片中,一次获取可预热多个路径,同时通过内联索引和二分查找避免解析整个分片。
  3. 分片大小需要在区域缓存命中率和 LRU 未命中填充成本之间权衡,生产测试确定约 200 KB 为平衡点。
转成行动

深入阅读

正文与原文对照

原文保真覆盖:全文原文字符:12184

对 Vercel 的每个请求都会经过我们的 CDN,该 CDN 平均每秒执行超过 8000 万条路由指令。其中的一部分工作是查找元数据,以确定哪些路径存在以及如何提供这些路径。当这些元数据未被缓存时,CDN 必须先获取它,然后才能提供响应。

我们过去一次只获取和缓存一个路径的元数据,仅检索每次查找所需的内容。这在当时看起来是高效的,但大型部署可能包含数十万个路径,每个路径都有自己的缓存条目。每次新部署都会引入新的元数据,使得缓存未命中成为频繁部署的大型站点反复出现的成本。

一次获取更多元数据使这些查找更快。通过将路径分组,每次获取可以为许多后续查找填充缓存。但获取过多也带来了自身的成本。通过生产环境实验,我们找到了一个平衡点,将 P99 元数据查找延迟降低了 91%,并在此过程中使部署更快。

复制标题链接CDN 如何找到正确的路由

请求中的路径并不总是与提供它的内容或函数的路径匹配。框架路由规则将两者连接起来。例如,对 /blog/hello-world

的请求可能解析为动态路由 /blog/[slug]

。对该页面 React Server Component 负载的请求可能解析为 /blog/[slug].rsc

。当 CDN 应用这些规则时,它可能需要检查多个目标路径以找到正确的响应。

框架在构建期间描述这些路由。借助框架定义的基础设施,应用程序代码声明哪些输出是静态的、哪些需要函数,以及哪些响应可以被缓存或重新生成。框架将该意图转换为 Build Output API 输出,Vercel 生成 CDN 读取的路由元数据。

在请求时,CDN 需要确定哪些目标路径存在并检索它们的元数据。我们添加到全局路由的 Bloom 过滤器排除了肯定不存在的路径。剩余的路径需要精确的元数据查找。

最初,我们将该元数据存储为每个目标路径的单独对象。CDN 独立地获取和缓存每个对象。这对于部署频率较低的小型项目效果很好。但每次新部署都会创建一组新的缓存键,因此每个路径的首次查找都会错过缓存。

复制标题链接按组获取元数据

为了减少缓存未命中,我们将许多路径的元数据分组到称为分片的文件中。我们限制了每个分片的大小,以控制一次查找必须获取多少数据。获取一个分片会将其所有路径的元数据带入缓存,因此对这些路径的后续查找可以重用它。

按路径填充缓存会预热一个路径。分片填充会预热分配给该分片的每个路径。

按路径填充缓存会预热一个路径。分片填充会预热分配给该分片的每个路径。

每个分片还包含一个索引,使 CDN 无需解码、解压缩或解析其他条目即可定位单个路径的元数据。每次获取会预热多个路径,而每次查找仍然只解析它所需的记录。

按路径查找需要依赖 HEAD 和 GET,而新路径在 Bloom 过滤器检查后获取一个有界分片并在本地查找路径。

按路径查找需要依赖 HEAD 和 GET,而新路径在 Bloom 过滤器检查后获取一个有界分片并在本地查找路径。

复制标题链接获取更多而不解析更多

我们使用 Vercel 其他大型路由数据集已采用的 JSONL 布局来构建分片。JSONL 每行存储一个 JSON 值。我们借用了 Bulk Redirects 中排序的交替键值记录,以及我们最初为 Bloom 过滤器构建的可直接寻址的 Base64 数据结构。

我们将数据布局与基于其上的查找结构分开,以便每个工作负载可以选择其所需的属性:

可检查、排序的键值 JSONL 记录,支持随机访问

可选的嵌入式索引,用于低开销的二分查找

嵌入的 Base64 数据,支持基于偏移量的解码

有界分片,控制传输和缓存成本

每个分片将排序后的目标路径及其元数据存储为交替的 JSONL 记录。内联索引记录每个条目的起始位置,因此路由进程可以直接跳转到该位置。这些位置存储为固定宽度的指针。每个指针是整数个六位 Base64 字符,因此任何单个指针都可以就地解码,而无需先将索引行解析为 JSON 或解码完整的 Base64 字符串。

路由进程使用构建时的字节偏移量对编码路径进行二分查找,然后仅解析匹配的 JSON 值。

路由进程使用构建时的字节偏移量对编码路径进行二分查找,然后仅解析匹配的 JSON 值。

与 Bulk Redirects 一样,部署的首次元数据获取已经确定了给定路径位于哪个分片,因此选择分片不会增加额外的往返。

在该分片内,路由进程使用索引指针对编码路径进行二分查找。查找一个路径需要 O(log n) 次指针读取和字符串比较。一旦找到匹配项,它就会解析下一行的元数据值。分片的其余部分保持未解析状态。

复制标题链接在生产环境中找到合适的分片大小

我们最初希望大多数部署的路径元数据能容纳在单个分片中。由于索引和二分查找使解析成本保持低廉,我们从数兆字节的分片开始。

大分片只有在靠近请求处保持缓存时才有意义。每个路由进程在内存中维护一个近期分片的最近最少使用(LRU)小缓存,位于该区域内所有进程共享的更大缓存之前。由于每次部署只有少量分片,我们预期大多数情况下会命中 LRU,从而抵消传输较大分片的成本。

在测试中,区域缓存命中率很高,但 LRU 命中率很低,因为请求分散在每个区域的许多进程中。传输数兆字节的分片也比我们预期的成本更高。

大分片使 LRU 未命中过慢,而小分片使区域未命中过于频繁。生产测试发现约 200 KB 是一个实用的平衡点。

大分片使 LRU 未命中过慢,而小分片使区域未命中过于频繁。生产测试发现约 200 KB 是一个实用的平衡点。

我们最终确定分片大小约为 200 KB,这保持了较高的区域缓存命中率,并使 LRU 未命中的填充成本很低。

生产环境测量显示平均查找延迟和 P99 查找延迟均有所降低:

基于 2026 年 8 月 5 日至 12 日的生产流量测量。

复制标题链接进一步缩小分片不值得为此进行发布

在每个分片中放入更少的条目降低了传输成本,但增加了分片数量。我们还评估了在不增加分片数量的情况下缩小分片,方法是将相同的条目编码得更紧凑。我们测试了三种方法:

对排序后的路径进行前端编码,使每个路径只存储与上一个路径不同的部分。

将分片拆分为去重元数据的 JSONL 文档。

使用更紧凑的自定义序列化格式。

离线模拟测量了编码后的大小和查找成本。

每种方法都产生了小得多的分片,但模拟预测延迟收益有限。我们认为额外的编码、兼容性和发布工作对于这次迁移不值得。当另一个工作负载使更多的索引或压缩值得付出努力时,它将进入共享库。

复制标题链接安全地更改路由

CDN 对每个部署的请求都运行此查找。如果分片元数据与按路径的元数据不一致,我们就会提供过时的路由、错误的状态码,或对存在的路径返回 404。

我们首先离线检查了这种不一致,然后在生产环境中检查。离线时,我们构建了测试部署,并在其上运行了一个测试框架,以两种方式查找每个路径并比较结果。

在生产环境中,在一个功能标志之后,路由系统对随机抽样的请求执行两种查找,同时仍然提供旧结果。它在后台将新结果与旧结果进行比较,我们观察了数周的不匹配情况,而没有减慢生产请求。我们称之为影子模式。

影子模式发现了差异,而且这些差异极为罕见。其中一个是旧编码中的 bug,它把路径打包进 RFC 2047 编码词,以便将非 ASCII 文本塞进仅支持 ASCII 的字段,只有当某个 emoji 被拆分到两个词中时才会显现。新格式将路径存储为纯 UTF-8,因此该 bug 在那里不会发生。捕获到这个边缘情况增强了我们对比较结果的信心,于是我们开始让这些分片承载生产流量。

复制标题链接更快的构建

一旦这些分片开始承载生产流量,我们便回到构建流水线,移除它们已经使其变得多余的工作:

跳过逐路径元数据上传可节省约 9.7 秒。

将路由组元数据写入清单可节省约 4.5 秒。

不上传这两项改动后变为空的文件可节省约 2.4 秒。

这些加起来大约节省 16.6 秒。在所有部署中,部署步骤大约快了 10%,而对于元数据繁重的部署——这些步骤在其中占主导地位——我们估计接近 25%。

复制标题链接为你下一次部署提供更快的路由

更快的元数据查找整体上改善了路由解析。对于大型站点,P99 路由解析现在大约快了一倍。在我们自己的营销和文档站点上,P99 元数据查找延迟从 203 毫秒降至 31 毫秒。中位数元数据查找延迟保持在约 0.7 毫秒。

索引分片部署测得 19.1 毫秒的 P99 路径元数据查找延迟,而同一生产窗口内旧格式部署为 215.8 毫秒。

索引分片部署测得 19.1 毫秒的 P99 路径元数据查找延迟,而同一生产窗口内旧格式部署为 215.8 毫秒。

应用程序的 Build Output API 契约保持不变。框架仍然描述应用程序需要什么,而 Vercel 改进 CDN 如何提供它。

2026 年 7 月 17 日之后构建的部署已经使用新的元数据分片。对于较旧的部署,重新部署你的项目以采用更快的查找。