Skip to content

Cache interception fetches the same key twice on a miss #1214

Description

@km-tr

Describe the bug

When enableCacheInterception is enabled, an ISR cache miss causes the incremental cache to be queried twice for the same key within a single request.

cacheInterceptor fetches the entry, and on a miss it returns the event without passing the (empty) result downstream. NextServer then starts and the cache handler fetches the very same key again:

None of the four return event paths carry the fetched result, so the second lookup is unavoidable.

On a hit this costs nothing, but on a miss the store round-trip is paid twice before any rendering starts.

Observed impact

Running on Cloudflare Workers with the R2 incremental cache, using Workers automatic tracing. Every ISR miss shows two cache_match / r2_get pairs before the API fetches begin:

cache_match (regional cache)      4 ms
r2_get                          288 ms   <- cacheInterceptor
cache_match (regional cache)      3 ms
r2_get                          306 ms   <- NextServer cache handler
fetch (API, parallel)           212 ms
r2_put                        1,633 ms

Five consecutive traces of different pages all showed exactly two r2_get spans, 274–319 ms each. That is roughly 290 ms of avoidable latency per miss, about 20–25 % of the total request time in our case.

Sites where most requests hit the cache will not notice this. Sites with a long tail of rarely-visited pages (ours has one page per restaurant) miss often, so the duplicate lookup is paid on a large share of requests.

Steps to reproduce

  1. Deploy an app with enableCacheInterception: true and an incremental cache backed by remote storage
  2. Request an ISR page whose entry is not in the cache
  3. Observe two lookups for the same key — via tracing, or by logging inside the incremental cache override's get

Expected behavior

The key should be fetched once per request. Some options:

  • Pass the interceptor's result (including the miss) to the downstream cache handler
  • Memoize incrementalCache.get for the duration of a request

Request-scoped memoization would not have the correctness problem discussed in #621, since it cannot outlive a single request and therefore cannot serve data invalidated by on-demand revalidation.

Versions

  • @opennextjs/aws: 4.0.2 (behaviour confirmed unchanged on main)
  • @opennextjs/cloudflare: 1.20.1
  • Next.js: 15
  • Deployed on Cloudflare Workers

I have not measured this on AWS/S3, but the code path is shared.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions