Session Revocations at Scale

SerCe1 pts0 comments

Session revocations at scale - Canva Engineering BlogSkip navigation

Skip to main content

Engineering BlogOverviewSubscribe

Discover moreUI/UXBackendMachine LearningInfrastructureSecurityEngineering PracticesData Platform

About Canva⁠(opens in a new tab or window)

Backend

Session revocations at scale

How Canva keeps hundreds of millions of user sessions fast and secure

Llew VallisJul 22, 2026

Managing sessions for hundreds of millions of users is a tricky problem because every backend request needs to know which logged-in user made it. At Canva’s scale, this means answering this question hundreds of thousands of times every second. We keep session revocations directly in memory for the best possible performance and reliability, but as we grew, loading this cache during deploys became a bottleneck.

Canva uses browser cookies to store everything we need to know about a user’s session, from their user ID to their permissions and roles. By encrypting these cookies, our gateways can trust these details without talking to a networked datastore on every request. However, if a user is logged out or their permissions change, we need to be able to revoke or update their cookies in near real time.

To enable this, each gateway needs a record of any revoked sessions. We use in-memory lookups because they’re faster and more reliable than checking a networked datastore. Since our session cookies refresh periodically, we store 12 hours of revocations in memory and rely on slower MySQL lookups during these refreshes. Tokens that need refreshing will always be checked against the database, so they don’t have to rely on the in-memory cache.

Although reading the in-memory cache is cheap, seeding it was becoming an issue. With hundreds of gateway pods each potentially pulling over a million revocations from MySQL on startup, our deployments became a coordinated stampede on our database. Although we could temporarily mitigate this by adding a large number of read replicas, we needed a better solution, and ideally one that would reduce the in-memory size of the cache too.

The diagram below shows this pre-existing system end-to-end.

Our pre-existing session revocation architecture

Optimizing our in-memory cache

We didn’t want to address this by slowing down our deployments, so we fundamentally needed to deal with many gateway instances downloading the whole cache of over a million revocations at once. The challenge was to maintain the fast and reliable per-request in-memory session revocation checks at the gateway level, but avoid overloading our MySQL database when the cache needed to be loaded on deploy.

A standard solution to scaling reads is to introduce a cache between the database and the reader, but the choice of caching technology isn’t so obvious. Redis is a popular caching technology, so we evaluated it as an intermediary cache. On startup, each gateway instance would ask Redis (over a network call) for the full in-memory dataset, and then periodically poll for new revocations to stay up to date. The main problem is that Redis isn’t typically deployed in a fully durable configuration, and we would still need to manage the Redis cluster itself. We would just be moving our problems from one datastore to another, while adding significant complexity to keep the cache consistent.

We needed something that provides strong durability guarantees and that supports efficient reads of large amounts of data. This led us to S3, which is designed to serve downloads of large, durably stored files at a low cost. This is exactly what we needed for our use case, but it also presented a major challenge since we’re caching a moving window of records, not a blob of static binary data.

If we wanted to use S3 to cache our data, we needed to convert our sliding window of revocation records into a format suitable for object storage. We decided to partition our sliding window into 30-minute segments, each corresponding to one object in S3. We don’t need to worry about individually removing old revocations from S3, since each gateway instance can fetch only the most recent chunks. This also lets us perform smaller updates, since each 30-minute block contains less data than the full multi-hour window. However, even with 30-minute chunks, adding a single revocation into S3 requires downloading and re-uploading potentially hundreds of thousands of records. We’ll go into more detail on that later.

Binary data format of a single revocation

Each revocation contains two critical pieces of information: who it applies to (called the “principal”), and the login timestamp up to which it applies (typically, you would revoke all sessions started before a specific timestamp). With some bit twiddling we’re able to pack this into 16 bytes, with our revocation chunks just being a flat array of these 16-byte elements. By sorting this array, we can efficiently binary search each chunk to find revocations that apply to a specific principal. This...

cache revocations memory session revocation gateway

Related Articles