Module 3: Rate Limiting and Session Storage
Introduction to Module 3: Rate Limiting and Session Storage
Overview
You closed modules 1 and 2 knowing how to cache read data professionally. Your API responds fast, PostgreSQL doesn't saturate, and you understand each strategy's tradeoffs. But there are two problems caching doesn't solve: abuse of your API (a user making 10,000 requests/minute, or a bot scraping your entire catalog) and session management with immediate revocation (a compromised JWT stays valid until it expires).
This module covers both. Rate limiting teaches you to protect your API with professional algorithms — token bucket, leaky bucket, and especially sliding window with sorted sets. It isn't a naive counter (INCR requests:user:42 with EXPIRE 60), which has a serious concurrency bug you'll learn to avoid. It's the implementation serious APIs like Stripe, GitHub, and AWS use — with standard HTTP headers (X-RateLimit-*, 429 Too Many Requests, Retry-After) and rate limits per user tier.
Session storage with Redis solves JWTs' best-known limitation: once issued, they can't be revoked until they expire. If a user reports their account as compromised, the token stays valid. Redis sessions complement JWTs: the JWT verifies identity (stateless, fast), the session holds mutable state and allows immediate revocation (DEL session:abc123 = instant logout). The combined JWT+session pattern is the industry standard.
This module is where module 1's sorted sets take on their true purpose. Remember ZADD, ZRANGEBYSCORE, and ZREMRANGEBYSCORE — they're the foundation of sliding window rate limiting. If those commands feel rusty, take a quick pass through module 1's capsule 04 before continuing.
Where are we in the guide?
You're in Module 3 of 5 of the Redis & Caching Strategies guide:
Module 1: Redis Fundamentals ✅
→ Installation, CLI, the 5 data types, redis-py
Module 2: Caching Patterns & TTL ✅
→ Cache-aside, write-through, write-behind, TTL, invalidation, stampede
Module 3: Rate Limiting & Session Storage ← YOU ARE HERE
→ Token bucket, sliding window, sessions complementing JWT
Module 4: Pub/Sub & FastAPI Integration
→ Pub/Sub, redis.asyncio, dependency injection, middleware
Module 5: Project — Production Cached API
→ The full stack: caching + rate limiting + sessions
How this module builds on the previous ones
From module 1: sorted sets are the fundamental data type for sliding window rate limiting. ZADD with a timestamp as the score, ZRANGEBYSCORE to count the requests in the window, ZREMRANGEBYSCORE to clean out old requests. Without understanding these commands, a sliding window is inaccessible.
From module 2: the TTL strategies (sliding TTL for active sessions) and cache invalidation (DELETE on logout) apply directly.
The connection with earlier guides in the path:
- The FastAPI APIs you built in guides #6-7 are the ones you'll protect with rate limiting
- The JWTs from guide #9 are the ones you'll complement with Redis sessions
Problem 1: your API is naked without rate limiting
Imagine you put your API into production without rate limiting. What can happen:
Scenario 1: An accidentally abusive user
A developer consuming your API has a bug in their client — an infinite loop making 100 requests per second to /products. PostgreSQL saturates, latencies climb, other users suffer. The developer doesn't even know they're causing damage.
Scenario 2: Bot scraping
Someone writes a script that iterates GET /products?page=1, GET /products?page=2, ... GET /products?page=10000. In 5 minutes they have your entire catalog. Nothing stops them.
Scenario 3: DDoS (Denial of Service)
An attacker points 1,000 different IPs at your most expensive endpoint (GET /search?q=...). Your API receives 100,000 requests/minute, saturates PostgreSQL, and nobody can use the service.
Scenario 4: Pricing/plan abuse
Your API has a free tier (100 requests/day) and a pro tier ($50/month, 10,000 requests/day). Without rate limiting, there's no way to enforce those limits — everyone consumes like a pro user.
Rate limiting solves all 4 scenarios. It's mandatory protection for APIs in production. It isn't optional.
Why Redis is ideal
- Atomic:
INCRandZADDare thread-safe operations, with no race conditions under concurrency - Ultra-fast: <1 ms per operation. The rate limit check doesn't add visible latency
- Automatic TTL: the keys expire on their own — you don't need cron jobs to clean up
- Shared between workers: every uvicorn worker (or multiple servers) sees the same counter
Problem 2: JWT alone doesn't allow immediate revocation
JWTs are wonderful because they're stateless: the server verifies the JWT with its secret and knows who the user is without touching the DB. This scales horizontally with no trouble.
But there's a fundamental problem: once issued, a JWT can't be revoked until it expires. If:
- A user reports their account as compromised
- An employee leaves the company and you strip their permissions
- An attacker steals a JWT
...the JWT stays valid until its natural expiration. If the JWT lasts 24 hours, the attacker gets 24 hours of access.
The typical "solutions" (and why they're bad)
Option A: Very short TTLs (5-15 minutes)
- Pros: a small exposure window
- Cons: bad UX (the user has to renew their token every 15 min), a refresh token backend, complexity
Option B: A blacklist in the DB
- Pros: immediate revocation
- Cons: every request has to query the DB to check whether the JWT is blacklisted → it kills the stateless benefit
Option C: JWT + a Redis session (the professional pattern)
- The JWT verifies identity (fast, stateless)
- Redis holds the mutable state: current roles, permissions, an "is_revoked" flag
- If the user is revoked,
DEL session:abc123= immediate logout at global scale - Every request is: verify the JWT (no DB) + verify the session exists in Redis (1 ms)
This module teaches you option C.
What you'll learn
By the end of the 5 capsules:
Token bucket and leaky bucket (capsule 02)
- Implementing a token bucket: a bucket with N tokens, each request consumes 1, and tokens refill at a fixed rate
- Implementing a leaky bucket: requests enter a bucket that drains at a constant rate (smoothing out burst traffic)
- Deciding between them: token bucket allows bursts (better UX), leaky bucket smooths traffic (better protection)
- Implementation with Redis using
INCR+EXPIRE(atomic, simple)
Sliding window with sorted sets (capsule 03)
- The professional rate limiting algorithm:
ZADDwith a timestamp,ZRANGEBYSCOREto count,ZREMRANGEBYSCOREto clean up - Why a sliding window beats a fixed window (the "double window" bug)
- Granular rate limiting: by IP (anti-DDoS), by user (fair use), by specific endpoint
- Standard HTTP headers:
X-RateLimit-Limit,X-RateLimit-Remaining,X-RateLimit-Reset,Retry-After - The 429 Too Many Requests status code
Sessions with Redis (capsule 04)
- Redis as a session store with
HSET/HGETALL(each session is a hash with fields) - A sliding TTL for active users' sessions
- The combined JWT + Redis session pattern: JWT for auth, Redis for revocable state
- Listing a user's active sessions (a set of session IDs)
- Local logout vs global logout ("log out on every device")
- Session expiration and automatic cleanup
The Rate Limiter Service mini-project (capsule 05)
- A FastAPI API with production-ready rate limiting middleware
- Multi-tier: free (100 req/hr), pro (1000 req/hr), enterprise (10000 req/hr)
- Sessions with HSET, complementing a (mock) JWT
- Load tests: simulating 1000 concurrent requests to verify the limit holds
- Correct HTTP headers on every response
Connection with the capstone project
Module 5's project (Production Cached API) uses everything from this module:
| Feature of the capstone project | Its module 3 origin |
|---|---|
| Rate limiting middleware in FastAPI | The sliding window from capsule 03 |
| User tiers (free/pro/enterprise) | Multi-tier from capsule 03 |
429 Too Many Requests with correct headers | Capsule 03 |
| Session storage for "immediate logout" | Capsule 04 |
GET /sessions for "see my active devices" | Capsule 04 |
| The JWT + session pattern | Capsule 04 |
This module gives you the protection and session management toolkit that most superficial tutorials skip.
The module's 5 capsules
Capsule 01: Module introduction (you are here)
→ Context, the problem, connections with the previous modules
Capsule 02: Token bucket and Leaky bucket
→ Algorithms with tradeoffs, implementation with INCR + EXPIRE
Capsule 03: Sliding window with sorted sets
→ The professional pattern, ZADD/ZRANGEBYSCORE, HTTP headers
Capsule 04: Sessions with Redis (vs JWT)
→ The combined pattern, sliding TTL, global logout, listing active sessions
Capsule 05: The Rate Limiter Service mini-project
→ A complete API with rate limiting + sessions, load tests
Estimated time: 2.5-3.0 hours of active study. It's the densest module after the capstone project.
What you will NOT learn in this module
These topics are intentionally left out:
- ❌ WAF (Web Application Firewall) — protection at the CDN/edge level, a different layer
- ❌ CAPTCHA and advanced bot detection — specialized tools (reCAPTCHA, hCaptcha)
- ❌ The full OAuth2 flow — that's guide #9 (Auth)
- ❌ Deep JWT signing and validation — guide #9
- ❌ Refresh token rotation — guide #9
- ❌ Distributed rate limiting across DCs — out of scope (Redis Cluster)
If after this guide you need some of these, the resources at the end point to references.
Prerequisites
Software (you should have this from modules 1-2)
- ✅ Redis running in Docker
- ✅ Python 3.10+ with a virtual environment
- ✅
redis-pyinstalled
Creating the module 3 workspace
mkdir -p ~/projects/redis-guide/module-03-rate-limiting
cd ~/projects/redis-guide/module-03-rate-limiting
python -m venv .venv
source .venv/bin/activate
pip install redis fastapi uvicorn httpx
Prior knowledge
From module 1:
- ✅ Sorted sets:
ZADD,ZRANGEBYSCORE,ZREMRANGEBYSCORE,ZCARD - ✅ Strings with INCR/DECR (atomic counters)
- ✅ Hashes:
HSET,HGETALL,HDEL
From module 2:
- ✅ TTL strategies (sliding TTL is important for sessions)
- ✅ Cache invalidation (sessions are invalidated with DELETE)
From the path:
- ✅ FastAPI middleware (Guide #7) — you'll build rate limiting middleware
- ✅ The JWT concept (Guide #9, even though it's incomplete — we'll use JWT as a concept, not implement a complete one)
The module's teaching philosophy
Three principles:
1. Algorithms with tradeoffs, not recipes
Token bucket vs leaky bucket vs sliding window — each one has a case where it shines and another where it's suboptimal. You'll come out understanding WHY you'd choose each one, not memorizing. Capsule 02 shows the fixed window bug that the sliding window solves — that's what separates professional rate limiting from what you copy-paste off StackOverflow.
2. Standard HTTP headers
If your rate limiting doesn't return X-RateLimit-* headers, clients can't adapt their behavior (e.g., the GitHub CLI uses those headers to poll with backoff). It's like having a sign that says "closed" without opening hours — the user doesn't know when to come back. HTTP standards exist for a reason.
3. Sessions complement JWT, they don't compete with it
For years there was a "JWT vs sessions" debate. The modern answer is JWT + sessions. Each covers what the other can't. JWT scales statelessly. Redis sessions allow revocation. The combined pattern is what serious APIs use.
Resources to get started
Official documentation
- Redis: Rate Limiting — The official implementation with redis-py
- Redis: ZADD command — The complete sorted sets syntax
- HTTP Status Code 429 — The official Too Many Requests spec
To understand it from another angle
- GitHub API: Rate Limiting — A real case with standard headers
- Stripe API: Rate Limits — Another reference implementation
- Cloudflare: Rate Limiting — Rate limiting at the CDN/edge level (it complements the rate limiting in your API)
For sessions
- The Definitive Guide to JWT vs Sessions — A modern comparison that recommends combining both
- OWASP: Session Management Cheat Sheet — Security best practices
Before moving on to capsule 02
Take 5 minutes:
-
Verify Redis and clean up the workspace:
docker ps | grep redis redis-cli FLUSHDB # optional, so you start fresh -
A sorted sets refresher (if you don't remember, go back to module 1's capsule 04):
redis-cli> ZADD test 100 "a" 200 "b" 300 "c" redis-cli> ZRANGEBYSCORE test 100 250 redis-cli> ZREMRANGEBYSCORE test 0 150 redis-cli> ZCARD test -
Create the workspace:
mkdir -p ~/projects/redis-guide/module-03-rate-limiting cd ~/projects/redis-guide/module-03-rate-limiting python -m venv .venv source .venv/bin/activate pip install redis fastapi uvicorn httpx pyjwt
If all 3 are ✅, you're ready.
Summary
In this capsule you understood:
- Rate limiting is mandatory protection for APIs in production. Without it, your API is vulnerable to accidental abuse, scraping, DDoS, and pricing fraud
- Redis is ideal because its operations are atomic, ultra-fast, with automatic TTL, and shared between workers
- JWT alone doesn't allow immediate revocation — a compromised token stays valid until it expires
- The professional pattern: JWT + a Redis session — the JWT verifies identity (stateless), the session holds revocable mutable state
- The 3 rate limiting algorithms you'll cover: token bucket (it allows bursts), leaky bucket (smoothing), sliding window (precise, without the fixed window bugs)
- Standard HTTP headers:
X-RateLimit-*,429,Retry-After— serious APIs implement them, clients expect them
In capsule 02 you get into token bucket and leaky bucket — the simplest algorithms, but with real tradeoffs you'll understand in code.
What's next?
Capsule 02: Token bucket and Leaky bucket — Two classic algorithms, an implementation with INCR+EXPIRE, and a burst traffic simulation to see the difference between them. It's the first rate limiting you'll build and watch working.
If your workspace is ready, let's go.