backendgym backendgym · projects Sign in with GitHubSign in

projects /03-one-limit-three-servers

# The quota that multiplied

Three API instances each kept their own count, and one merchant blew through its limit. Make the limit hold across all of them.

Intermediate ~4h suggested:Python RedisDocker 2 phases

practises rate-limiting, key-value-stores, load-testing, trade-offs

one-limit-three-servers / spec.md v1.0

What the carrier saw

ParcelMint is a shipping marketplace. Merchants price labels through GET /v1/quotes before checkout; about 4,800 integrations call it, and each API key is promised 10 requests per 10-second window.

During a carrier sale, one merchant sent a burst through the load balancer. ParcelMint runs three API instances, and each one enforced the promise from its own memory, so that merchant got far more than 10 requests through in a single window. The carrier's quotation service throttled us, 8.4% of customers couldn't buy a label, and the carrier asked us to cut traffic until the afternoon.

What you're changing

The quota belongs to the key, whichever instance happens to take the request. A caller who is over the limit should be told how long to wait.

What the starter gives you

The quotes API (Python, with the redis client already in its requirements) running as three instances on ports 8091, 8092 and 8093, and a Redis they can all reach. Each instance counts requests in its own memory. make up starts it all.

make load sends 30 requests for one key, one at a time, round-robin across the three instances, and prints how many got through. make burst sends 60 at the same moment, 20 to each instance. make retry plays a well-behaved client: it fills a key across the three instances, reads the 429's Retry-After if there is one, waits that long and tries again. make contract checks the API still answers as it did. Run make load before you change anything.

You can port the service to another language: keep its routes, port 8080 inside the container, INSTANCE_ID and REDIS_URL, and run make contract until it passes.

Four questions stay open for your README: what quotes do while Redis is unreachable, whose clock defines the window, managed Redis failover and quota administration.

starter Tserewara/starter-one-limit-three-servers · you generate your copy when you start

The work, in 2 phases

Each phase says what must be true and the drill that proves it. You close a phase when its drills pass.

P1 · See the quota multiply

One key, three instances, one window. Count what gets in.

  • D1 R1 , not recorded yet

    Send 11 requests for one key to a single instance (http://localhost:8091/v1/quotes with an X-Api-Key header) within 10 seconds and look at the responses.

    expectThe first 10 are admitted and the 11th gets HTTP 429.

  • D2 R1 , not recorded yet

    Before changing anything, run make load and write down admitted= as the baseline.

    expectYou have the baseline: how many of the 30 got through across the three instances.

P2 · Make admission global

One quota per key across all three, decided in one step, with an answer the caller can act on.

  1. R1
    Across all three API instances, one API key gets at most 10 admitted requests in any 10-second sliding window.
    ask the stakeholder

    start the project to ask

  2. R2
    hidden until you ask the stakeholder
    ask the stakeholder

    start the project to ask

  3. R3
    Concurrent requests can't slip past the shared admission decision and push a key over its limit.
  • D3 R1 , not recorded yet

    After your change, run make load again and read admitted=.

    expectadmitted=10 for the key across all three instances.

  • D4 R3 , not recorded yet

    Run make burst a few times and read admitted= on each line.

    expectEvery burst admits 10 and no more.

  • D5 R2 , not recorded yet

    Run make retry and read what it prints.

    expectThe 429 carries a whole-second Retry-After between 1 and 10, and the retry after waiting exactly that long is admitted.

  • done when`make contract` still passes after your change.

Technical guidance

  • ▸Send one key through all three instances and count what gets in during one window before you change anything.
  • ▸Write down what "the window" means for your limiter before you choose how to store it.
  • ▸Look for the moment between checking a key's count and recording the new request. That gap is where a burst gets in.

Done when

Every drill above passes, and:

  • The README explains the window representation you chose, where the atomic boundary sits, and your answers to the four open questions.
Solutions

No solutions published yet. Ship the project and yours will be the first.