A logout button looks local. Click it, kill the cookie, move on. At Canva’s size, that fiction dies fast. Hundreds of millions of sessions mean every backend request must decide whether an encrypted browser cookie still represents a valid user, role, or permission set. That decision has to happen hundreds of thousands of times per second, close to the gateway, without turning each request into a datastore lookup.
Canva’s new engineering write-up is useful because it treats identity as distribution machinery. The problem was plain: gateways kept recent session revocations in memory for speed, but every deployment forced hundreds of gateway pods to seed that cache by pulling more than a million revocations from MySQL. Deploying the edge of the application became a coordinated read storm against the database that knew who should still be trusted.
The cookie moved the database out of the request path
Canva stores session details such as user ID, permissions, and roles inside encrypted browser cookies. Gateways can verify those cookies without asking a networked database on every request. That design buys latency and reliability, which is the whole point of putting identity evidence near the request path.
The tradeoff arrives when authority changes. A user logs out. A permission changes. A brand-level decision invalidates a family of cached session facts. The cookie still exists in the browser, so the gateway needs a fast revocation answer before it accepts the request. Canva keeps a 12-hour revocation window in gateway memory because session cookies refresh periodically; slower MySQL checks catch tokens during refresh.
That split is sane. The request path remains hot and local. The refresh path remains authoritative. The ugly part was startup. A deployment could make every gateway pod download the whole recent revocation set from MySQL. Adding read replicas could absorb the pain for a while, but that treats deploys as database capacity events. That is clown architecture with better invoices.
Object storage became the revocation broadcast layer
The fix was to stop asking MySQL to replay the revocation window to every gateway. Canva moved the distributable representation into S3: 30-minute chunks, sorted binary arrays, and direct operation on downloaded bytes. Each revocation packs the principal and cutoff login timestamp into 16 bytes, with a few reserved bits for revocation types. Sorting the array lets gateways binary-search chunks without expanding them into Java objects.
That one representation change matters. Canva says the binary format cut the in-memory cache footprint by 87.5 percent. It also changed the shape of the load. MySQL no longer had to serve the same historical rows to each gateway startup. Gateways could stream compact objects from S3, keep only the 12-hour window, and drop chunks once their time range aged out.
S3 is doing a dull job here, which is why the design is good. It serves large durable files cheaply. It supports conditional reads. It has strong read-after-write consistency for object operations. The magic lives in making revocation records fit the grain of object storage instead of pretending object storage is a row store.
Canva partitioned time rather than identity. A gateway can find recent chunk keys by cutoff timestamp, then search inside each chunk by principal. That keeps startup deterministic: fetch the relevant objects, hold dense bytes in memory, answer request-time checks locally. Identity state becomes a small rolling archive.
The hard part was avoiding lost revocations
Writing a moving window into object storage creates a different class of failure. A naive worker reads a chunk, appends new revocations, and uploads the replacement object. Run multiple workers and one can overwrite another’s update. The worst version silently drops a logout. That is the kind of bug that turns clean diagrams into security incidents.
Canva handled this with conditional PUT requests and optimistic concurrency control. A worker updates a chunk only if the object has not changed since it was read; newly created chunks use similar preconditions so two workers cannot both create the same time block. A ZooKeeper leader election reduces conflict load, but the S3 preconditions carry correctness. Canva calls out the nasty pause case directly: a node can stall before writing, lose leadership, then wake up and attempt to overwrite newer data. The conditional write must reject that stale operation.
AWS’s own S3 documentation backs the pieces Canva is leaning on. Conditional writes use headers such as If-Match and If-None-Match to prevent overwrites when an object’s ETag has changed or when an object already exists. Conditional reads let clients retrieve objects only when metadata says the object changed. S3 also documents strong read-after-write consistency for PUT and DELETE requests. Canva’s design depends on those storage semantics becoming part of the identity pipeline.
The asymmetry is the point. Request-time revocation checks need to be brutally fast. Write-time chunk maintenance can tolerate worker loops, retries, and object preconditions. The system moves complexity away from the path that every user request touches.
O(N²) lost to boring measurements
The design has an obvious theoretical wart. Building chunks by repeatedly inserting batches into sorted arrays can look like O(N²) work. Canva tested the implementation on real infrastructure and found that processing many hundreds of revocations per batch produced more than 2,000 revocations per second. The worker bottleneck landed on network latency while handling hundreds of thousands of records in a dense array.
That result should make architecture astronauts itchy. A theoretically fancier distributed write path could have spread complexity across more stateful services. Canva chose the dumb-looking path after measuring it. Dense arrays, time chunks, object preconditions, and gateway-local reads beat another managed cache with another consistency story.
The piece also contains a quiet warning about AI-generated system design. Canva notes that AI coding agents make it easy to play with mock distributed-system implementations, then says the team built multiple options and tested them at expected size on real infrastructure. Good. Mock distributed systems are toy cities where every road is empty and every bridge has admin privileges. Production is where deploy waves, stale workers, Java heap overhead, object semantics, and database replica counts begin punching each other.
Identity keeps becoming distribution
This is the deeper pattern. Session management used to feel like application code: cookies, login tables, password resets, maybe a Redis cache if the team got fancy. Modern product infrastructure turns it into a distribution system. Authority changes have to propagate across gateway fleets, regions, worker processes, object stores, browser cookies, and database refresh paths. A stale permission is a security bug. A slow revocation path is a trust boundary with latency.
The same pressure appears in passkeys, device sessions, enterprise role sync, browser-mediated identity, and agent delegation. The user sees an account action. The platform sees proof material, cache invalidation, transport semantics, object naming, race control, and blast-radius limits. Logout is the humane word. Revocation pipeline is the machinery.
Canva’s design is valuable because it resists fashionable complexity. Redis would have looked normal. A stream processor would have looked enterprise. A custom revocation service would have looked promotable. The shipped answer uses MySQL for source truth, workers for compaction, S3 for durable broadcast, conditional object operations for race safety, and byte arrays for gateway memory. It is ugly in the good way: specific, measured, and hostile to bullshit abstraction.
The platform lesson is simple. If authority must be checked near every request, the revocation path becomes part of the product’s security perimeter. Store it, compact it, distribute it, and test it like infrastructure. The logout button is just the plastic cap on the machine.