FlowGuard
Distributed Rate Limiter
A Redis-backed rate limiter that smooths bursty traffic with a sliding-window weighted average and enforces limits before a request ever reaches application logic.
- HTTP 429
- returned on limit violation
- 60s
- weighted sliding window
- 5 min
- automatic key expiry
- 2–3
- Redis ops per request
Overview
FlowGuard sits in front of a Spring Boot service as a servlet filter. It tracks request counts per client IP across the current and previous minute, computes a weighted average, and rejects requests that would exceed the configured limit.
The problem
A naive fixed-window limiter allows double the traffic across a window boundary and blocks legitimate users with hard resets. Storing counters in a single process also breaks the moment the service scales horizontally.
FlowGuard solves both by keeping shared, self-expiring state in Redis and by weighting the two adjacent windows.
Architecture
Request interception
A servlet filter with highest-precedence ordering runs on every request before routing, so no endpoint can bypass the limit.
Rate limit evaluation
The service reads the current and previous window counters from Redis and applies the weighted-average formula to decide whether the request is allowed.
Shared state
Redis holds per-IP counters, making the limiter distributed by default and consistent across instances.
Response handling
Allowed requests continue normally; violations get an immediate HTTP 429 with a clear message, kept separate from application errors.
Design decisions
Sliding-window weighted average
weighted_avg = (elapsed × current + (60 − elapsed) × previous) / 60 blends the current and previous minute, so the limit transitions smoothly instead of snapping open at the minute boundary.
Redis-backed shared state
Counters live in Redis and are keyed per client IP, so the limit holds across every instance of the service rather than only within a single process.
Enforcement before application logic
A custom servlet filter registered with highest precedence rate-checks every incoming request before it reaches a controller and returns HTTP 429 on violation.
Five-minute key expiry
Keys expire automatically, so stale per-IP counters never pile up and Redis memory stays bounded without a cleanup job.
Failure & concurrency
Burst at a minute boundary
The weighted average carries load from the previous window forward, so a client cannot double its allowance by waiting for the clock to tick.
Horizontal scaling
Because counters are centralised in Redis, adding instances does not multiply the allowed rate.
Stale clients
Keys expire after five minutes, so idle clients free their memory automatically.
Redis unavailable
The limiter's state layer is isolated behind a service boundary, keeping failure handling explicit rather than tangled with business logic.
Testing & verification
- A status endpoint returns remaining requests and reset time for quick verification.
- Rapid repeated requests against the endpoint confirm the 200 → 429 transition at the configured limit.
- Limits and window parameters are configurable through application properties.