Warm-up

Break the fixed window

10 minjunior012 yrs

One concept, guided. Near-impossible to fail.

What this teaches

  • A limiter is a function of time, so it can only be tested against a clock you control
  • The fixed window's reset is the bug, and clock-aligned traffic finds it
  • Token bucket has no timer — elapsed time is the refill
  • Capacity and refill rate are separate dials, and capacity is the burst you chose

Starter

Starter.java
import java.util.*;

/**
 * Warm-up: make a correct-looking rate limiter allow twice its limit.
 *
 * The one rule that makes any of this testable: the limiter reads a clock you
 * pass in, never the wall clock. A limiter is a function of time, so a test
 * against System.currentTimeMillis() is a test against whatever the machine
 * happened to be doing that second.
 */
public class Starter {

    /** A clock you advance by hand. */
    static final class Ticker {
        private long millis;
        Ticker(long start) { this.millis = start; }
        long now() { return millis; }
        void advance(long ms) { millis += ms; }
    }

    /** Counts requests per aligned window. The reset is the bug. */
    static final class FixedWindow {
        private final int limit; private final long windowMs; private final Ticker clock;
        private long window = Long.MIN_VALUE; private int count;
        FixedWindow(int limit, long windowMs, Ticker clock) {
            this.limit = limit; this.windowMs = windowMs; this.clock = clock;
        }
        boolean allow() {
            long w = Math.floorDiv(clock.now(), windowMs);
            if (w != window) { window = w; count = 0; }
            if (count < limit) { count++; return true; }
            return false;
        }
    }

    public static void main(String[] args) {

        // TODO 1: build a FixedWindow(100, 60_000, clock) and fire 100 requests
        // at t = 59s. Count how many are allowed. Predict first.

        // TODO 2: advance the clock by 1 second and fire 100 more. How many
        // were allowed across those two seconds? Write down the number and
        // compare it to the limit the API documentation would state.

        // TODO 3: name three sources of traffic that arrive exactly on a
        // minute boundary. This is why TODO 2 is not an edge case.

        // TODO 4: write a SlidingLog — keep an ArrayDeque of timestamps, drop
        // the ones older than the window, allow while size < limit. Run the
        // same two bursts through it. It should refuse the second one.

        // TODO 5: print how many timestamps your log is holding. Multiply by
        // a million users and a limit of 10,000. That number is the reason
        // the exact algorithm is not the default.

        // TODO 6: write a TokenBucket(capacity, perSecond, clock). No timer
        // and no background thread — compute the refill from elapsed time when
        // a request arrives:
        //
        //   tokens = min(capacity, tokens + (now - last) * ratePerMs)
        //
        // Fire 15 at once against capacity 10, rate 5/sec. Then advance one
        // second and fire 15 again.

        // TODO 7: advance the clock by 60 seconds and check the token count.
        // It should be 10, not 300. Say what would go wrong if it were 300.

        // TODO 8: your bucket uses the injected Ticker. In production it would
        // read a real clock. Say why that must be System.nanoTime() and not
        // System.currentTimeMillis(), and what breaks in each direction when
        // NTP corrects the clock.
    }
}

Run it locally:

cd exercises/java/traffic-management/rate-limiting/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Done when

  • You made a 100/min fixed window allow 200 requests inside one second
  • You showed a sliding log refusing the same burst
  • You built a token bucket and demonstrated a bounded burst after a long idle
  • You can say why System.nanoTime is the right clock source

← Back to How would you implement a rate limiter?