The real question isn't "database or not", it's "who pays the cost"
Every engineer who has fought with JWT revocation eventually asks the same question: can I kill a token early without hitting a database on every single request? The honest answer is: not for free. Something, somewhere, has to hold state that says "this token is dead now." The only real decision you get to make is where that cost lands.
Either you pay it on every request, in the form of a lookup against some fast store. Or you pay it upfront, by shortening how long a token lives so a stolen one expires on its own before it does real damage. Most articles on this topic present these as a binary choice — blacklist versus short TTL — and leave you to guess which one fits your system. That guess is expensive if you get it wrong at scale.
This post walks through the actual math behind that tradeoff, so you can plug in your own request volume and token lifetime and get a real answer instead of a gut feeling.
What "without a database" usually means in practice
When people say "no database," they rarely mean zero state anywhere. They mean no relational query, no connection pool exhaustion, no schema migration for a revocation table. In practice, that translates to one of these:
- An in-memory cache (Redis, Memcached) holding revoked token IDs or a per-user version number.
- A claim inside the token itself (like a "ver" or "epoch" field) checked against a value cached locally on each API node.
- No check at all — just a short expiry and a refresh flow.
The first two still involve a lookup, just a cheap one. The third avoids the lookup entirely by turning the problem into a scheduling one. Understanding which bucket you're actually choosing matters more than the marketing label "no database."
The two costs you're actually trading
Lookup cost
If you check revocation status on every request, your added cost is roughly:
added latency per request ≈ L (the round trip to your cache)
added QPS on your store ≈ R (your request rate)
At low R, this is trivial — a Redis call at 20 requests per second costs you nothing operationally. At high R — tens of thousands of requests per second — that same call becomes real infrastructure: a sharded cache, replication, monitoring, and a new single point of failure you now have to keep alive.
You can amortize this. If you cache the revocation check locally with a TTL of C seconds, your real query rate against the central store drops to roughly:
active_users ÷ C queries per second
not R. That's the trick that makes revocation checks survive at scale: you stop checking on every request and start checking once per C seconds per user, accepting up to C seconds of staleness in exchange for a massive reduction in load.
Exposure window cost
If you skip the lookup and rely purely on a short token lifetime T, the cost shows up differently. A compromised token stays usable until it naturally expires. Assuming the compromise happens at a random point in the token's life:
average exposure ≈ T / 2
worst-case exposure ≈ T
If you're propagating a revocation signal across nodes (say, invalidating a signing key or pushing a denylist update), add propagation delay P on top: worst-case exposure ≈ T + P.
The insight that actually matters here
Here's the part most comparisons skip: a cached revocation check with TTL C gives you almost the same exposure bound as just issuing tokens with lifetime T = C, minus the infrastructure. If you're willing to tolerate C seconds of staleness either way, ask yourself honestly — is the cache buying you anything the token lifetime couldn't buy you for free?
The lookup earns its cost only when the gap between your desired UX (long-lived tokens, no forced re-logins) and your required reaction time (how fast you need revocation to take effect) is large. If users expect to stay logged in for a day but you need to cut off a compromised account within a minute, a cached check is worth its price. If you'd be fine issuing 1-minute tokens anyway, skip the lookup and just do that — it's the same guarantee with less to maintain.
Ranked decision criteria
- Request volume (R) — decides whether a per-request lookup is free or expensive.
- Required reaction time — how fast a revoked token must actually stop working.
- Acceptable token lifetime for UX — how often users tolerate re-authenticating.
- Blast radius per token — what damage one leaked token can do before it expires.
- Operational budget — do you have the appetite to run and monitor a cache cluster.
Worked scenarios
Scenario A: high-traffic billing API. 8,000 requests/sec, tokens authorize real money movement, current TTL is 60 minutes for mobile UX reasons. Shrinking TTL to match a 1-minute reaction requirement would force constant refresh traffic and hurt UX badly. Instead, keep the 60-minute TTL and add a cached revocation flag with C = 30–60 seconds at the gateway. Real query load against the central store drops to roughly active_users ÷ 60 per second, not 8,000 — cheap, and it meets the reaction time requirement. This is the case where the lookup earns its keep. If you want the mechanics of doing this cache layer well, see invalidate JWT without database lookup on every request.
Scenario B: internal admin dashboard. 20 requests/sec, 15-minute TTL already, root-level accounts. R is so low that even a direct, uncached lookup on every request costs nothing — 20 QPS against Redis is noise. Don't bother building a caching layer here; the math doesn't justify the extra moving part. Just check directly.
Scenario C: mobile content app, massive fan-out. Millions of devices, low-sensitivity data, current TTL 24 hours. Building a lookup layer that can handle this fan-out is genuinely expensive infrastructure for data that isn't worth protecting that hard. Here, dropping TTL to 15–30 minutes with silent refresh gives you an average exposure window of 7–15 minutes, at zero lookup cost. This matches the classic short-lived token advice — and it's the right call precisely because R is enormous and the blast radius is small.
Default recommendation, with named exceptions
Compute your R and your required reaction time. If R is low (roughly a few hundred QPS or less), just do a direct check — the lookup cost is negligible regardless of store type. If R is high and your required reaction time is much shorter than an acceptable token lifetime, build the amortized cached check. If R is high and sensitivity is low, shorten the token lifetime and skip lookups entirely.
The named exception: never let a high-privilege or financial token rely on TTL alone, no matter how low R is or how short you make the lifetime. Pair it with at least a cached check at the edge — the blast radius of getting this wrong is not something volume math should override. For the fuller catalogue of revocation strategies, including signing key rotation and denylists, how to revoke a JWT before it expires covers the mechanics in more depth.
Common mistakes worth checking for
- Storing full tokens instead of an identifier. Store the "jti" claim or a hash, never the raw token — a leaked denylist shouldn't hand an attacker anything usable.
- Checking the cache synchronously without a TTL. If your "cache" always hits origin, you've rebuilt the database problem with extra network hops.
- Forgetting the refresh token side. Revoking an access token is pointless if the refresh token can silently mint a new one — see refresh token reuse detection without storing all tokens in a database for handling that without blowing up your storage.
- Ignoring clock skew. If services trust the "exp" claim independently and their clocks drift, your exposure window math gets quietly wrong in production.
Verifying you picked correctly
After implementing either approach, measure two things directly: the added p99 latency on protected endpoints, and the actual time between a revocation event and the token failing on a real request. If your measured exposure window is close to your calculated C or T, your model holds. If it's noticeably larger, you likely have an unaccounted propagation delay somewhere in your cache or CDN layer, and that's worth chasing down before you trust the numbers again.