Canva just published how they rebuilt session revocation without a shared cache tier, and it is the third architecture writeup I have run into this month that lands on the exact same shape. Different companies, different problems, same answer: durable object storage as the source of truth, plus a locally rebuilt index, no shared cache in the hot path.
The problem: revocation has to be fast and shared
Session revocation is a specific kind of hard problem. When a token needs to die before its natural expiry, every gateway in the fleet needs to find out, fast, without every request round-tripping to a central store. The obvious answer for years has been a shared cache, Redis cluster or Memcached fleet, that every gateway queries or subscribes to.
A shared cache tier solves the propagation problem, but it becomes its own liability. It is a single point of contention under load. It has its own failure modes: eviction storms, hot keys, painful cluster rebalances. And it needs its own on-call rotation, separate from the service that actually owns the data.
What Canva built instead
Canva stores revocation records as 16-byte binary entries in S3, sliced into 30-minute objects across a 12-hour revocation window. Instead of gateways querying a shared store on every request, each gateway pulls the recent object slices and rebuilds a local, in-memory sorted-array index from them.
S3 layout (conceptual):
revocations/2026-08-11T00:00-00:30.bin
revocations/2026-08-11T00:30-01:00.bin
revocations/2026-08-11T01:00-01:30.bin
...
Concurrency is handled with conditional GETs and PUTs, giving optimistic concurrency without a lock service. ZooKeeper leader election is used to reduce write conflicts further, but Canva is explicit that it is an optimization, not a correctness dependency. The system is correct without it, just slightly noisier under contention.
The results, from their own numbers: cache memory down 87.5%. Workers sustaining 2,000+ revocations per second. The database footprint down to two read replicas. A million revocations fit in roughly 16MB.
The pattern, not just the case study
Strip away the specifics and the shape is: a workload with small records, a bounded time window, and tolerance for a few seconds of staleness, served by cheap durable object storage instead of a shared, stateful cache. Each worker rebuilds its own disposable index locally instead of depending on a shared service to hold the current state for everyone.
This shape fits more than session revocation. Feature flags, entity caches, compacted-topic-style event state, anything that is read far more than it is written, has a small per-record footprint, and does not need every reader to agree on the exact same value at the exact same instant.
Seeing three unrelated teams land here independently in the same month is a stronger signal than any one writeup. It suggests the shared cache tier's default status, "many workers need the same fast-changing data, reach for Redis," is no longer the obvious answer for a meaningful slice of these workloads.
The honest trade-off
This pattern is not a free upgrade. Two real costs:
It only works when the dataset has a natural staleness tolerance and a bounded window. Canva's 12-hour revocation window caps how much history a worker ever needs to rebuild from scratch. Without a bound like that, "rebuild the index from object storage" gets slower and the object count grows without limit.
It trades a shared cache's single-source consistency for eventual consistency across workers. A shared Redis instance gives every reader the same answer the instant a write lands. Here, a revocation is only as fresh as the last object slice a given gateway has pulled. If your correctness requirements demand that every worker sees a write at the exact same millisecond, no propagation lag tolerated, this pattern is the wrong tool and a shared cache (or a shared database) is still the right one.
Canva's own use of ZooKeeper leader election as an optional optimization, not a dependency, is a tell about how the trade-off was made: they accepted some write-conflict noise in exchange for not needing a coordination service to be up for the system to be correct.
Where this fits
If you are running a workload that looks like "many stateless workers need to check a small, frequently-updated dataset, and a few seconds of staleness is fine," this is worth evaluating before reaching for another Redis cluster. The infrastructure is simpler to operate (no separate cache fleet to keep alive), the failure mode is more boring (a worker with a stale local index, not a cache stampede), and the cost profile at scale, per Canva's own numbers, is dramatically smaller.
Have you replaced a shared cache tier with this shape? What broke first when you tried it, and did the staleness window end up being the real constraint?
Top comments (0)