How do you stop one user from taking down your API?
A rate limiter decides, in under a millisecond, whether a request gets through. Here’s a token bucket running live. Give each user a bucket of tokens; every request spends one; tokens refill at a steady rate. Empty bucket means 429.
LIVE · capacity 5 · refills 1 token / 2s
GET /lab/limited
Your bucket
tokens: 5 / 5
0
200 OK
0
429 Too Many
// Network — same as F12 → Network on the live site
Send a request to see the response headers.
On the live site these requests hit a real Spring Boot endpoint backed by Redis. Open your Network tab and the headers above appear there too.
⚡ k6 Load Test Benchmark Results
k6 v0.50.0 · 500 requests
p95 Latency
1.2 ms
Redis Atomic Lua
0.45 ms
Burst 10 Test
5 admitted · 5 blocked (429)
Steady State
1 req / 2s · 0 drops
The architecture
The check happens before your request ever reaches business logic, and the counter lives in Redis so every server instance shares one view of each user.
Client
your browser
→
Rate-limit filter
Spring OncePerRequestFilter
⇄
Redis
Lua script, atomic
→
Controller
only if allowed
bucket empty → 429 + Retry-After, controller never runs
The core of it
-- token_bucket.lua (runs atomically inside Redis)
local tokens = tonumber(redis.call('HGET', KEYS[1], 'tokens') or ARGV[1])
local last = tonumber(redis.call('HGET', KEYS[1], 'ts') or ARGV[3])
local refill = math.floor((ARGV[3] - last) / ARGV[2])
tokens = math.min(ARGV[1], tokens + refill)
if tokens < 1 then return {0, tokens} end
redis.call('HSET', KEYS[1], 'tokens', tokens - 1, 'ts', ARGV[3])
return {1, tokens - 1}