Skip to content

Lab: Rate Limiter Races

Pairs with: Rate Limiting

No app code needed — just concurrent shell processes hitting a real Redis instance. Reproduces two real races: a naive INCR+EXPIRE counter whose window never actually closes under continuous traffic, and a check-then-act limiter that lets 20 requests through a limit of 5 under real concurrency. Both get fixed with one atomic Lua script.

docker-compose.yml

name: labs-rate-limiter

services:
  redis:
    image: redis:7
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 3s
      timeout: 3s
      retries: 20

atomic_incr_expire.lua

Fixes the "TTL never expires" bug — EXPIRE only fires on the request that creates the key:

-- Fixed-window counter, done correctly: EXPIRE is set only on the request
-- that creates the key (count == 1), never re-armed on every request.
-- Run atomically via EVAL so no other client can interleave between the
-- INCR and the conditional EXPIRE.
local count = redis.call('INCR', KEYS[1])
if count == 1 then
  redis.call('EXPIRE', KEYS[1], ARGV[1])
end
return count

atomic_fixed_window.lua

Fixes the check-then-act race — increment and limit check happen inside one atomic EVAL, so there's no separate read step for two requests to race on:

-- Atomic fixed-window rate limiter: increment-then-check, in one EVAL call.
-- No client can observe a stale count between "read" and "act" because
-- there is no separate read — the increment and the limit check happen
-- inside the same atomic script execution.
-- KEYS[1] = counter key, ARGV[1] = window seconds, ARGV[2] = limit
local count = redis.call('INCR', KEYS[1])
if count == 1 then
  redis.call('EXPIRE', KEYS[1], ARGV[1])
end
if count > tonumber(ARGV[2]) then
  return 0  -- deny
else
  return 1  -- allow
end

Exercises

The full walkthrough (reproduce both races with exact commands, verify the fix count is exactly 5 allowed / 15 denied under concurrency) lives in the lab's README:

labs/rate-limiter/README.md on GitHub

git clone https://github.com/sanketn26/interview-prep
cd interview-prep/labs/rate-limiter
docker compose up -d

← All Labs