Building block

Rate limiting

Caps the number of requests from one client over a period of time. It protects the system from overload, abuse and costly bugs in other people's code.

Rate limiting: the limiter counts requests in Redis and returns 429 when over the limitINCR keyover limitallowedClientRate limiterRedis counters429 Too ManyAPIDatabase
Rate limiting: the limiter counts requests in Redis and returns 429 when over the limit

Why limit

One client with a bug in a loop can send thousands of requests a second and take the API down for everyone. Password guessing, scraping the course catalog and signup spam are also stopped by limits.

Limits are set per key: IP, user id, API key, sometimes a specific endpoint. For example, the GitHub REST API allows 5,000 requests per hour for an authenticated user and 60 for an anonymous one. For login, 5 attempts per minute per IP and per account is sensible.

Algorithms

Fixed window counts requests in a window (say, a minute) with INCR and EXPIRE in Redis. Simple, but at the window boundary a client can make double the requests within a few seconds.

Sliding window smooths that boundary by weighting the previous window. Token bucket keeps a «bucket» of N tokens refilled at a constant rate: it allows short bursts up to the bucket size but caps the average rate. Stripe uses a token bucket in its write-up on rate limiters.

Leaky bucket lets requests through at a constant rate and queues or drops the excess. Good for protecting a slow external API with a hard limit.

Telling the client and distributed limits

When the limit is exceeded, return 429 Too Many Requests with a Retry-After header, and in normal responses headers like RateLimit-Remaining. Well behaved clients use them to back off instead of blindly retrying.

If the API runs on 10 copies, an in-memory counter in each gives a limit 10 times higher. So counters live in a shared Redis with atomic operations (a Lua script or INCR), and the check runs at the gateway so a request is rejected before any business logic.

Pros

  • Protects from overload and accidental DoS by your own clients.
  • Makes password guessing and scraping harder.
  • Shares resources fairly between clients and plans.
  • Keeps spending on paid external APIs under control.

Cons

  • Poorly chosen limits block legitimate users (for example, an office behind one NAT).
  • Distributed counters add a Redis dependency and a little latency.
  • You need to decide what happens when the counter store is down.

When to choose it

  • Any public API or login and signup form.
  • An API with plans and quotas for partners.
  • In front of expensive operations or paid external services.

Where it is used

  • Stripe: four kinds of limiters, including a token bucket in Redis and load shedding (2017).
  • GitHub REST API: 5,000 requests per hour for authenticated users.

Tools

Sources

Logos belong to their owners and are used for identification only. Icons: Simple Icons (CC0), Devicon (MIT).