Hermes Wiki
Architecture/CaseStudies/canva-stateless-sessions-and-revocation

Canva: Stateless Session Cookies and Revocation at Hundreds of Millions of Users

Problem + constraints

Every backend request at Canva needs to know which logged-in user made it — their user ID, permissions, and roles. At Canva's scale (260M+ users), that question gets asked hundreds of thousands of times per second. The naive answer — look the session up in a networked datastore on every request — puts a shared, latency-sensitive dependency directly in the hot path of every single API call, and turns that datastore into both a bottleneck and a single point of failure for the entire product.

The opposite naive answer — a fully stateless, self-contained token that the gateway can verify on its own — solves the latency and availability problem but creates a much harder one: how do you revoke something the server never has to look up? If a user logs out, gets their permissions downgraded, or an admin needs to kill every session for an entire brand, a purely stateless token has no way to know it's no longer valid until it naturally expires.

Solution

Canva's gateways store everything needed to identify a session — user ID, permissions, roles — directly in an encrypted browser cookie. Because the cookie is encrypted (not just signed), gateways can trust its contents outright and authorize a request without a network call to a datastore on every request. This removes the shared datastore from the hot path entirely for the common case.

The hard part is revocation, since a self-contained token doesn't naturally support it. Canva's answer is a revocation list distributed to every gateway, kept in memory:

  • Revocations are written to a database, then an asynchronous worker continuously scans for revocations that haven't yet been propagated and uploads them as chunks to S3.
  • Each gateway loads and holds these revocation chunks in memory rather than querying a database per-request, keeping the check as fast as the rest of the stateless path.
  • Revocations aren't just "kill this session" — the system supports multiple revocation types: invalidating cached permission/role data in a cookie without fully logging the user out, and revoking at the granularity of an entire brand (not just one user) in a single operation. This is encoded efficiently by reserving bits for flags and sorting revocations by principal in a flat array, rather than modeling each revocation type as a separate lookup structure.
  • Because the in-memory revocation cache has to be rebuilt on every gateway deploy, loading it fast enough not to bottleneck rollouts became its own engineering problem that had to be solved alongside the steady-state design.

What to steal

  • Stateless-by-default, revocation-list-as-the-exception is a more general pattern than "session cookies" — anywhere you want to avoid a network round-trip for the common read path (feature flags, entitlements, API keys), a background-propagated, in-memory-cached negative list lets you keep the fast path stateless while still supporting "kill this now."
  • Design the revocation format around the operations you actually need, not a generic revoke-by-ID list. Canva's bit-flag + principal-sorted flat array supports "downgrade permissions without full logout" and "revoke an entire brand" as first-class, cheap operations — worth asking "what are all the granularities we'll ever need to revoke at" before picking a data structure.
  • Propagation lag is a deliberate, bounded tradeoff, not an oversight. The async worker + S3 chunk distribution means revocation isn't instant across every gateway — that's an explicit choice to keep the hot path cheap, and it's the kind of tradeoff worth stating out loud (and bounding with an SLA) rather than leaving implicit.

Principal Engineer Lens

This is a clean case of a tradeoff that looks like a contradiction until you separate the read path from the write path: "stateless for speed" and "revocable for security" are usually presented as opposites, but Canva gets both by making revocation an eventually-consistent negative cache layered on top of an otherwise fully stateless token, rather than making every read pay for revocability. In an architecture review, the sharp question to ask about any design like this is "what's the actual propagation bound on revocation, and is that bound acceptable for the sensitivity of what's being revoked?" — a stolen laptop session and a permissions downgrade probably have different acceptable lag, and a mature design would say so explicitly rather than treating "eventually" as good enough everywhere. The bit-flag/principal-sorted revocation array is also a good small example of Principal-level thinking: it's not a fancier algorithm, it's just refusing to build a generic structure when you already know the finite set of operations you need to support cheaply.

Reel Script

Setup: At 260M+ users, checking "who is this and what are they allowed to do" hundreds of thousands of times a second can't involve a database round-trip on every request — but it also can't ignore that sessions need to be revocable in near real time.

Concept walkthrough: Walk through the encrypted-cookie design first — the cookie carries user ID, permissions, and roles, and because it's encrypted, the gateway trusts it without a lookup. Then introduce the revocation list as the piece that makes this safe: an async worker moves new revocations from the database to S3-hosted chunks, and every gateway keeps them in memory for a fast check layered on top of the stateless read.

Real example / case study tie-in: Trace a concrete revocation: an admin kills every session for a brand. That's one write to the revocation table, tagged at brand granularity — not a fan-out to invalidate N individual user sessions — and it propagates to every gateway's in-memory cache within the system's propagation bound.

Tradeoffs & alternatives: Contrast with a fully server-side session store (instantly revocable, but a shared bottleneck and single point of failure on every request) and with a purely stateless JWT with no revocation mechanism at all (fast and simple, but a logged-out or compromised session stays valid until natural expiry — often an unacceptable security gap). The revocation-list hybrid is the middle path.

Principal Engineer takeaway: When two requirements look mutually exclusive — "no network call on the hot path" and "must be instantly revocable" — look for where you can push the expensive part to the write path (or to an async background process) instead of the read path, rather than assuming you must pick one requirement over the other.

Hermes Wiki