Protect it with rate limiting and abuse controls
Continues from the last build: a sharded system with no protection against abuse or overload
The consistent-hash ring from the last rung spreads keys evenly, but it never asked who was allowed to write them.
What you'll build
A shared, atomic rate limiter that holds its limit regardless of how many app instances are running, a documented fail-open policy for when the limiter store itself is unavailable, and abuse controls (URL screening, per-plan quotas) that keep malicious links and scraper churn out of the system, all backed by capacity math and an ADR a staff engineer would sign off on.
See how we teach, before you sign up
You don't just get code dumped on you. Every starter file and every solution is explained line-by-line, in plain English. Here's one real file from this project:
# ADR-0007: Rate limiting and abuse controls for Linkly ## Status Proposed ## Context Linkly runs 5,000 create QPS and 40,000 redirect QPS at steady state. Bot traffic now spikes redirect QPS to 10x normal from a single IP range, and abusers are submitting links to phishing pages. The app tier scaled to 20 stateless instances with no shared limit state, so each instance enforcing its own in-memory counter lets roughly 20x the intended limit through before anything trips. ## Options considered 1. Fixed window counter per instance, in-memory: cheap, but bursts at the window boundary and multiplies by instance count. 2. Token bucket in a shared Redis store: smooth, atomic via a Lua script, small per-key state. 3. Sliding window log in Redis sorted sets: most accurate, more memory per key. ## Decision Use a token bucket per user and IP pair, backed by a shared Redis cluster, checked with one atomic Lua script per request. Fail open for a 5 second grace window on Redis outage, then fall back to a static per-instance limit, so the limiter store dying never takes the whole API down with it. ## Consequences Adds one Redis round trip to every request on the hot path. Redis is now a dependency the whole API can degrade with, bounded by the fail-open window. Abuse controls, URL screening and quotas, are a separate concern and run async so they never block the redirect path.
Reading this file
lets roughly 20x the intended limit through before anything trips.This is the bug this rung fixes: per-instance in-memory counters multiply the real limit by the instance count.## DecisionEvery ADR ends with one committed decision, not a menu of options left open.Fail open for a 5 second grace window on Redis outage, then fall back to a static per-instance limitNames the exact failure mode of the limiter store itself, not just the happy path.Abuse controls, URL screening and quotas, are a separate concern and run asyncKeeps content screening off the latency-sensitive redirect path.
Fill in Options and Decision after milestone 1 and 2.
That's 1 of 5 explained code blocks in this single project.
The build, milestone by milestone
- 1
Pick the rate-limiting algorithm and defend it with numbers
4 guided stepsA fixed window counter is the easiest thing to reach for, but it lets a client burst up to 2x the limit right at the window boundary (all of the last second of one window plus all of the first second of the next), which is exactly the kind of burst a scraper will find by accident.
- 2
Decide where the limiter's state lives, and what happens when it dies
4 guided stepsWith 20 stateless app instances behind the edge, an in-memory counter per instance means the real limit is the intended limit times 20, since each instance only sees a twentieth of the traffic and thinks it is under budget.
- 3
Implement the distributed check as one atomic operation
4 guided stepsIf the check is read-tokens then write-tokens as two separate Redis calls, two requests arriving at nearly the same time can both read '1 token left' and both proceed, silently doubling the effective limit under concurrency, the same class of bug as the per-instance counters, just one layer deeper.
- 4
Add abuse controls beyond raw rate limits
4 guided stepsRate limiting controls volume but says nothing about intent, a user well under their creation quota can still submit a link to a phishing page, so the system needs a second, content-aware control alongside the rate limiter.
- 5
Roll it out in shadow mode and watch it in production
4 guided stepsA rate limiter tuned on paper can still be wrong in production, a limit set too tight locks out legitimate power users on day one, and the only way to catch that before it happens is to watch what the limiter would have rejected before it actually starts rejecting.
What's inside when you start
You'll walk away with
This is portfolio-grade. Build it free.
Sign up to unlock every milestone step-by-step, the code skeletons, full reference solutions, and checkable tasks, with your progress saved as you build.
Start building