Back to path
AdvancedTradepost · Project 9 of 12 ~6h· 5 milestones

Cache it, rate limit it, and shed load before it buckles

Continues from the last build: The hot queries are indexed, joined properly and paginated by keyset; you have baselines.

One merchant's dashboard poller hits your aggregates endpoint fifty times a second.

Cache-aside pattern with RedisWrite-path cache invalidationHTTP conditional requests (ETag, If-None-Match)Sliding window rate limiting with Redis sorted setsLoad shedding and backpressure signalingReading autocannon p95/p99 outputDesigning Retry-After and X-RateLimit contractsReasoning about multi-instance state (why in-memory limiters fail)

What you'll build

You can absorb a runaway poller and a traffic spike without the database breaking a sweat, and you can prove the hit ratio and the p95 numbers with real commands. One noisy merchant now costs you a Redis lookup instead of a Postgres query, and a spike degrades predictably instead of catastrophically.

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:

src/lib/redis.tstypescript
import Redis from 'ioredis';

export const redis = new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379', {
  maxRetriesPerRequest: 3,
});

redis.on('error', (err) => {
  fastifyLoggerFallback('redis connection error', err);
});

function fastifyLoggerFallback(message: string, err: unknown) {
  console.error(message, err);
}

Reading this file

  • new Redis(process.env.REDIS_URL ?? 'redis://localhost:6379'Falls back to the local docker-compose Redis when REDIS_URL is unset.
  • maxRetriesPerRequest: 3Bounds how long a single command retries before failing, so a Redis blip does not hang requests indefinitely.
  • redis.on('error', (err) => {Logged rather than left to crash the process on a transient connection error.

One shared Redis client, reused by the cache-aside route, the invalidation helper, the rate limiter, and the load-shedding queue depth check.

That's 1 of 8 explained code blocks in this single project.

The build, milestone by milestone

  1. 1

    Cache-aside the aggregates endpoint

    6 guided steps

    The aggregates query is expensive and the poller calls it 50 times a second for numbers that change maybe once a minute. Every one of those calls that hits Postgres is wasted work competing with real writes for connections and buffer cache. A 60 second TTL means the worst case staleness is 60 seconds, which is fine for a dashboard, and it turns 50 queries a second into roughly one query a minute.

  2. 2

    Bust the cache on every write that changes it

    5 guided steps

    A cache with only a TTL is a cache that lies for up to 60 seconds after every write. A merchant who just paid an order and refreshes their dashboard should see the new total immediately, not on a coin flip depending on when the TTL happens to expire. Explicit invalidation on write makes correctness the default and the TTL just a backstop for invalidation bugs.

  3. 3

    Add ETag conditional GET on a read endpoint

    6 guided steps

    Redis caching helps your own server, but it does nothing for a CDN, a corporate proxy, or the client's own HTTP cache sitting in front of you. ETags let any standards-compliant cache, including ones you do not control, skip re-downloading a resource that has not changed, which cuts bandwidth and lets a 304 be served without your API doing any real work beyond a timestamp comparison.

  4. 4

    Rate limit per API key with a sliding window in Redis

    6 guided steps

    An in-memory counter (a plain JS Map keyed by API key) works fine with exactly one API instance and breaks the moment you run two. Each instance keeps its own count, so the real ceiling silently doubles per instance, and a restart resets everyone's counter to zero for free. Redis gives every instance a shared, authoritative view of how many requests a key has made recently, so the limit means the same thing no matter how many instances are running.

  5. 5

    Shed load before Postgres melts

    6 guided steps

    Rate limiting protects you from one misbehaving key, but a genuine traffic spike across many legitimate keys can still overwhelm the database. Without a saturation signal, every incoming request queues up behind an already-overloaded Postgres pool, latency climbs for everyone, and eventually requests start timing out instead of failing fast. A cheap, fast 503 with Retry-After costs almost nothing to check and lets well-behaved clients back off instead of piling on.

What's inside when you start

3 starter files, ready to clone
5 guided milestones
5 full reference solutions
8 code blocks explained line-by-line
5 "is it working?" checks
6 interview questions it prepares you for

You'll walk away with

Cache-aside aggregates endpoint with measured hit ratio above 90 percent under poller-like load
Write-path invalidation wired into every order and payment mutation route, with the TTL demoted to a safety net
ETag and If-None-Match support on at least one GET endpoint, returning real 304s
Per-API-key sliding window rate limiting in Redis, correct across two or more API instances
Load shedding based on queue depth, proven with autocannon numbers showing bounded p95 versus unbounded p95 without it

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