AWS
内部原理
ISR

在独立模式下,Next.js 会在构建过程中预构建 ISR 缓存。并且在运行时,NextServer 期望此缓存位于服务器的本地。当服务器运行在单台 Web 服务器机器上,在所有请求之间共享缓存时,这会有效运行。在 Lambda 环境中,缓存需要集中存放在所有服务器 Lambda 函数实例均可访问的位置。S3 充当了这个中心位置。

为了实现这一点:

  • ISR 缓存文件被排除在 server-function bundle 之外,而是上传到缓存桶。
  • 通过在 next.config.js 中配置 incrementalCacheHandlerPath (opens in a new tab) 字段,默认缓存处理程序被自定义缓存处理程序替换。
  • 自定义缓存处理程序管理 S3 上的缓存文件,处理读取和写入操作。
  • 由于我们使用的是 FIFO 队列,如果我们想一次处理多个重新验证,我们需要单独的 Message Group ID。我们基于路由路径为每个重新验证请求生成一个 Message Group ID。这确保了对同一路由的重新验证请求只被处理一次。你可以使用 MAX_REVALIDATE_CONCURRENCY 环境变量来控制同时处理的重新验证请求数量。默认情况下,它设置为 10。
  • revalidation-function 从队列中轮询消息,并向带有 x-prerender-revalidate 头的路由发出 HEAD 请求。
  • server-function 接收 HEAD 请求并重新验证缓存。
  • 标签在 DynamoDB 表中以不同方式处理。我们使用单独的表来存储每个路由的标签。自定义缓存处理程序在更新缓存时会更新表中的标签。

过期页面 ISR 请求的生命周期

  1. Cloudfront 接收页面请求。假设页面在 Cloudfront 中已过期。
  2. Cloudfront 在后台将请求转发给 server-function,但仍返回缓存版本。
  3. server-function 检查 S3 缓存。如果页面已过期,它将过期响应发送回 Cloudfront,同时向重新验证队列发送消息以触发后台重新验证。它还会将 cache-control 头更改为 s-maxage=2, stale-while-revalidate=2592000
  4. 2 秒后同一个页面有新的请求进入。Cloudfront 将缓存版本发送回用户,并将请求转发给 server-function
  5. 如果重新验证完成,server-function 将更新缓存并将更新后的响应发送回 Cloudfront。后续请求将获得更新后的版本。否则,我们回到步骤 3。

标签

标签存储在 DynamoDB 表中。 表中有 3 个字段:tagpathrevalidatedAttag 字段是分区键,path 是排序键。

我们使用一个名为 revalidate 的索引,其中 path 作为分区键,revalidatedAt 作为排序键。

每个标签有多个路径,每个子路径也被视为一个标签。例如,如果我们有一个标签 tag1 路径为 /a/b/c,我们也有标签 /a/a/layout/a/page/a/b/a/b/layout/a/b/page/a/b/c/layout/a/b/c/page

当调用 revalidateTag 时,我们更新与此标签关联的每个路径和子路径的 revalidatedAt 值。

当我们检查页面是否过期时,我们检查每条记录的 revalidatedAt 值和此 S3 缓存对象的 LastModified。如果 revalidatedAt 大于 LastModified,我们认为页面已过期。

成本

⚠️

请注意,fetch 缓存正在使用 S3。next 中默认情况下 fetch 是被缓存的,即使是 SSR 请求,它 也会被写入 S3。这可能导致大量的 S3 请求并且可能很昂贵。你可以通过在 fetch 选项中将 cache 设置为 no-store 来禁用 fetch 缓存。另见 此 解决方法

get 将在每次请求未在 Cloudfront 中缓存的 ISR 和 SSG 时被调用,set 将在每次重新验证时被调用。 它们也可能在 fetch 请求中被调用,如果 cache 选项未设置为 no-store

部署也有一些相关成本,因为你需要将缓存上传到 S3 并将标签上传到 DynamoDB。

对于这里的示例,让我们假设一个应用路由在 us-east-1 中有 5 分钟的重新验证延迟。这是假设你获得通往该路由的恒定流量(如果没有流量,你只需支付存储成本)。

S3
  • 每次对缓存的 get 请求将导致至少 1 个 GetObject
  GetObject 成本 - 8,640 请求 * $0.0004 每 1,000 请求  = $0.003456
  总成本 - 每条路由每月 $0.003456
  • 每次对缓存的 set 请求将导致 1 个 S3 中的 PutObject
  PutObject 成本 - 8,640 请求 * $0.005 每 1,000 请求 = $0.0432
  总成本 - 每条路由每月 $0.0432

然后你可以根据你的使用量和 S3 定价 (opens in a new tab) 计算成本

DynamoDB

对于示例,让我们考虑同一路由有 2 个标签,每个标签有 10 个路径和子路径。这是假设你获得通往该路由的恒定流量。

  • 每次 revalidateTag 请求将导致 1 个 DynamoDB 中的 Query 和每个与标签关联的路径的 PutItem,它们在 BatchWriteItem 请求中以 25 个为一组进行分组。
  假设你每 5 分钟进行 1 次重新验证
  Query 成本 - 8,640 请求 * $0.25 每 1,000,000 读 = $0.00216
  BatchWriteItem 成本 - 86,400 请求 * $0.25 每 1,000,000 写 = $0.0216
  总成本 - 每个标签重新验证每月 $0.04536
  • 每次 get 请求将导致 1 个 DynamoDB 中的 Query
  Query 成本 - 8,640 请求 * $0.25 每 1,000,000 读 = $0.00216
  总成本 - 每条路由每月 $0.00216
  • 每次 set 请求将导致 1 个 DynamoDB 中的 Query 和每个与路径关联且不存在于 DynamoDB 中的标签的 PutItem,它们在 BatchWriteItem 请求中以 25 个为一组进行分组。
  Query 成本 - 8,640 请求 * $0.25 每 1,000,000 读 = $0.00216
  总成本 - 每条路由每月 $0.00216

然后你可以根据你的使用量和 DynamoDB 定价 (opens in a new tab) 计算成本