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: INCR and ZADD are 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: ZADD with a timestamp, ZRANGEBYSCORE to count, ZREMRANGEBYSCORE to 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 projectIts module 3 origin
Rate limiting middleware in FastAPIThe sliding window from capsule 03
User tiers (free/pro/enterprise)Multi-tier from capsule 03
429 Too Many Requests with correct headersCapsule 03
Session storage for "immediate logout"Capsule 04
GET /sessions for "see my active devices"Capsule 04
The JWT + session patternCapsule 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-py installed

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

  1. Redis: Rate Limiting — The official implementation with redis-py
  2. Redis: ZADD command — The complete sorted sets syntax
  3. HTTP Status Code 429 — The official Too Many Requests spec

To understand it from another angle

  1. GitHub API: Rate Limiting — A real case with standard headers
  2. Stripe API: Rate Limits — Another reference implementation
  3. Cloudflare: Rate Limiting — Rate limiting at the CDN/edge level (it complements the rate limiting in your API)

For sessions

  1. The Definitive Guide to JWT vs Sessions — A modern comparison that recommends combining both
  2. OWASP: Session Management Cheat Sheet — Security best practices

Before moving on to capsule 02

Take 5 minutes:

  1. Verify Redis and clean up the workspace:

    docker ps | grep redis
    redis-cli FLUSHDB  # optional, so you start fresh
  2. 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
    
  3. 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.