How Vercel invalidates a million cache entries with one timestamp

Vercel Runtime Cache can invalidate every entry carrying a cache tag without finding or deleting any of them. Each cached entry stores its tags and the…

Loading
vercel··PT2M2.9S

video loads only when you press play

Vercel Runtime Cache can invalidate every entry carrying a cache tag without finding or deleting any of them. Each cached entry stores its tags and the…

Vercel Runtime Cache can invalidate every entry carrying a cache tag without finding or deleting any of them. Each cached entry stores its tags and the time it was written; a separate tag store in each region keeps one timestamp per tag, the last time it was invalidated. Invalidating a tag such as catalog writes one timestamp, whether one entry carries it or a million do. On every read the service compares: if any of the entry's tags was invalidated after the entry was written, it's stale and the app fetches fresh data.

  • Every Runtime Cache entry stores its tags and the time it was written, and a per-region tag store keeps one timestamp per tag. Invalidating a tag writes that one timestamp, whether one entry carries it or a million do; nothing is found or deleted.
  • On every read the service compares: if any of the entry's tags was invalidated after it was written, the entry is stale. Invalidations apply immediately in the receiving region and reach the others through a push pipeline, typically within a few hundred milliseconds.
  • The trade-off is a freshness check on every cache hit. When a slow tag-store shard stalled reads during bursts, circuit breakers fell back to in-memory tag timestamps, accepting brief staleness; p99 read latency in Vercel's largest region fell to about a fifth.

What Vercel Runtime Cache is

Runtime Cache is a managed cache for the data your code fetches or computes, so the next request can reuse it instead of calling the origin again. Vercel's example is a product page that reuses cached catalog data while still rendering a personalised response for each visitor.

That makes it different from CDN caching. A CDN stores whole HTTP responses, keyed by URL. Runtime Cache stores the pieces your code uses to build a response: a catalog, a feature-flag lookup, a CMS document. Your app or framework decides what goes in and handles a miss; Vercel runs the storage, the authentication and the invalidation.

There are four ways in, according to Vercel:

  • getCache from @vercel/functions, the direct API, which works outside Next.js (Routing Middleware, SvelteKit, Nuxt, build steps).
  • 'use cache: remote' in Next.js, which caches function and component output through the Runtime Cache API.
  • Next.js fetch caching and unstable_cache, which used a separate product called Data Cache until 30 October 2026 and are being moved onto Runtime Cache without code changes.

How a read works, region by region

Behind the API is a distributed caching service shared by many teams: a Go service in front of Redis fleets in every Vercel region. Vercel says it handles more than 440 million reads and moves more than 18 TB of data every hour.

Each region keeps its own cache. A hit is one round trip to the cache service in the region where your code is running, authenticated with short-lived credentials, so there are no API keys to manage. On a miss, your app or framework fetches or computes the value and writes it to that region's cache. The service does not look in other regions for a copy. Two regions can therefore hold different copies of the same key, each filled by its own misses.

Tags, and why invalidation writes a timestamp

Every entry can carry tags: labels you choose that group entries you will want to throw away together. Tag all your product data catalog, and when the catalog changes you invalidate the catalog tag.

The obvious implementation would find every entry with that tag and delete it. Vercel's does not. Each cached entry stores its tags and the time it was written. A separate tag store in the same region holds, for each tag, the time it was last invalidated. Invalidating catalog writes one value, the current time, into that tag's slot, whether one entry carries the tag or a million do.

The check happens on the read instead. When the service reads an entry, it looks up the invalidation times of that entry's tags. If any of them is later than the entry's write time, the entry is stale, and your code treats it as a miss and fetches fresh data. In pseudocode (ours, not Vercel's):

function isFresh(entry, tagStore) {
  // entry = { value, tags: ["catalog", "product:42"], writtenAt }
  return entry.tags.every((tag) => {
    const invalidatedAt = tagStore.get(tag) ?? 0;
    return invalidatedAt <= entry.writtenAt;
  });
}

The stale entries are never visited at invalidation time. Our reading is that they stay in storage until the next miss overwrites them or the cache evicts them for room; Vercel doesn't describe that part.

Why not just delete every tagged entry?

Vercel says only that the service "doesn't need to find and delete" the entries individually. The reasons it matters are our reading, not Vercel's statement:

  • Finding them is work. Deleting by tag needs an index from each tag to every key that carries it, and that index has to be kept correct on every write.
  • The work scales with the tag. A tag shared by a million entries means a million deletes, spread across Redis in every region, for one change to the catalog.
  • It races new writes. While a bulk delete runs, requests keep missing and writing fresh entries with the same tag, and the delete has to avoid removing data written after the change it is reacting to.

A timestamp makes invalidation cost the same whatever the tag's size: one write per region. The cost moves to reads, where it is a small constant: a lookup per tag on the entry.

How the timestamp reaches other regions

An invalidation updates the tag store in the region that received the request immediately. Vercel's shared tag infrastructure, the same one its CDN uses, then distributes the timestamp through a global push pipeline, typically reaching other regions within a few hundred milliseconds.

The consequence, in our reading: for that short window, another region can still serve an entry that was invalidated in the first one. That is the same kind of window any multi-region cache has; the push pipeline keeps it to a fraction of a second rather than a cache TTL.

The trade-off: every hit now depends on the tag store

Checking freshness on every read means a cache hit needs the tag store as well as the entry. Vercel describes the failure that followed: during traffic bursts, a slow shard in the tag store could delay reads even when the cached value was already available. The data was sitting there; the read was waiting on the freshness check.

The fix was a circuit breaker. When the tag store shard is slow, reads temporarily check freshness against tag timestamps cached in memory and proceed without waiting for the shard. The cost is stated plainly: during this fallback, a read can briefly treat recently invalidated data as fresh. Vercel chose a short window of possible staleness over stalled reads.

The breakers were enabled in every region in August 2026. During the heaviest bursts in Vercel's largest region, p99 cache-service read latency fell to about one fifth of its previous level at comparable traffic, and the on-call alerts for that failure mode stopped. Vercel gives the ratio, not the latencies, so the chart in the video has no axis numbers.

Using the pattern without Vercel

None of this is specific to Vercel. The same idea shows up as generation counters, versioned cache keys and "last modified" checks: rather than removing what is out of date, record when you stopped trusting it and compare on read. A few things to decide if you build it yourself:

  • Where the timestamps live. They are read on every hit, so they need to be faster and more available than the cache they guard, or you have Vercel's slow-shard problem.
  • Clock agreement. Write times and invalidation times must come from clocks you can compare. A single service stamping both, as here, avoids skew between machines.
  • What happens to stale entries. They still take memory until overwritten or evicted, so the cache needs an eviction policy or TTLs.
  • What you do when the timestamp store is unreachable. Fail open (serve, maybe stale) or fail closed (miss, more origin load). Vercel's breaker is a deliberate, bounded fail open.

What the video left out

The post is also a product announcement, and the video skipped that part:

  • Data Cache is moving into Runtime Cache. Next.js fetch caching and unstable_cache switch over without code changes, so all four APIs share one set of storage budgets and usage tools. Teams created on or after 30 October 2026 use Runtime Cache by default; Hobby teams migrate on 29 November 2026, and Pro teams are notified of their date.
  • Pricing changes. Data Cache reads and writes that were previously unbilled are charged at Runtime Cache rates after migration. The changelog lists rates from $0.40 per million read units and $4.00 per million write units, varying by region (as of 30 October 2026).
  • More storage. On Pro, each project and environment gets its own allocation with 8x the space of Data Cache's previous team-wide pool; Enterprise projects get 10x their previous Data Cache storage. Among Enterprise projects with frequent evictions, Vercel says cache miss rates roughly halved within a day of the increase.
  • Observability. Deployment markers, revalidation causes and write utilisation were added to the Runtime Cache view, to compare cache behaviour with releases and find data that is written but rarely read.

Terms

Runtime Cache
Vercel's managed data cache for values your code fetches or computes, with a regional cache per Vercel region.
Data Cache
The earlier Vercel product behind Next.js fetch caching and unstable_cache, being merged into Runtime Cache from 30 October 2026.
Cache tag
A label attached to many cache entries so they can be invalidated together.
Invalidation
Marking cached data as no longer servable. Here it is done by recording a time, not by deleting entries.
Tag store
The per-region store holding the last invalidation time of every tag.
Push pipeline
Vercel's global mechanism, shared with its CDN, that distributes tag invalidations to every region.
Shard
One partition of a store, holding a slice of its keys on its own servers.
Circuit breaker
A switch that stops calling a slow or failing dependency and uses a fallback instead, here tag timestamps cached in memory.
p99 latency
The time within which 99% of requests complete; the slowest 1% take longer.
Generation counter
The general form of this pattern: a number or time per group that makes older entries invalid when it moves forward.

Questions

How does Vercel invalidate cache entries by tag?

Invalidating a tag writes the current time into a per-region tag store. Every cached entry records its tags and write time, and a read treats the entry as stale if any of its tags was invalidated after it was written. No entries are found or deleted when the tag is invalidated.

Does revalidating a tag in Next.js delete the cached data?

On Runtime Cache, as Vercel describes it, the entries are not deleted when a tag is invalidated. They stop counting as fresh because their write time is older than the tag's invalidation time, and the next read fetches fresh data.

How fast does a tag invalidation reach other Vercel regions?

It applies immediately in the region that received the request, and the push pipeline typically delivers it to other regions within a few hundred milliseconds, according to Vercel.

Can Runtime Cache serve stale data?

Briefly, in two cases: in other regions during the few hundred milliseconds before an invalidation arrives, and while a circuit breaker is falling back to in-memory tag timestamps during a tag store slowdown. Vercel states the second one explicitly.

Is Runtime Cache the same as Vercel's CDN cache?

No. The CDN caches whole HTTP responses. Runtime Cache stores data your code uses to build responses, such as a product catalog, and is called from your code or framework.

What happens to Next.js fetch caching and unstable_cache?

They are moving from Data Cache to Runtime Cache without code changes. Reads and writes that were unbilled under Data Cache are charged at Runtime Cache pricing after a team migrates.

Vercel can throw away a million cached entries by writing one number, and that number is just the time. It's the trick inside Runtime Cache, which Vercel runs for your app's data, so a product page can reuse the catalog it fetched a minute ago instead of asking the database again. Underneath, that's Redis in every Vercel region, serving more than 440 million reads an hour. Every entry can carry tags, labels you choose, like a tag called catalog on everything built from your product data, so when the catalog changes, you invalidate that tag, and every entry wearing it has to stop being served. The obvious way is to find every entry with the tag and delete them one at a time, but a million can share one tag across every region, and new ones keep landing while you're still hunting the old ones. Vercel skips the hunt, because every entry also records when it was written, and a separate tag store keeps one number per tag, the last time it was invalidated. Invalidating the catalog tag just writes the current time into that one slot, however many entries share it. Then on every read, the service compares, and if any of an entry's tags was invalidated after the entry was written, it counts as stale and your code fetches it fresh. A push pipeline carries the new time to the other regions, usually within a few hundred milliseconds. The catch is that every cache hit now waits on that check, so during traffic bursts, one slow shard of the tag store could hold up reads whose data was already sitting right there. Vercel added circuit breakers, which stop waiting on the slow shard and check freshness against timestamps cached in memory instead, accepting that for a moment, something just invalidated might still be served. In their largest region, p99 read latency during the heaviest bursts fell to about a fifth, and the on-call alerts for it stopped. And none of this needs Vercel, because when throwing something out means finding a million things, you don't have to find them, you only have to write down when you stopped trusting them.

This explainer is based on Simplifying runtime caching on Vercel by Vercel ↗. The original reporting and technical work belong to its publisher.