Learn
Lesson · Queues and Utilisation · Shedding and overload

Token Buckets and Rate Limiting

A token bucket admits at most b + r·t in any window of length t: a sustained rate r with a burst allowance b on top of it. A fixed window of the same nominal limit admits nearly twice the limit across a boundary — on the lesson's trace, nine in one second while advertising five.

tokens += r·Δt, capped at b one token per admission admitted in any window of t at most b + r·t b is a burst allowance not a savings account: the cap is the point trace of 20, r = 5/s, b = 3 bucket admits 13; largest second 7 same trace, fixed window of 5/s admits 12; largest second 9
One limiter, and the counter it replaces

Core ideas

What this lesson establishes

A rate limit is a promise about every window, not about the windows a counter happens to be keeping.

1

A bucket accumulates permission, not requests

Tokens arrive at r a second and each admission spends one. Because the level carries over between arrivals, the limiter has memory of how much of its allowance has recently been used — which is what lets it be precise about every window rather than about aligned ones.

2

b is the burst you are choosing to allow

The level is capped at b, so an idle hour buys exactly b tokens and not one more. Without that cap a quiet night would pay for an unbounded morning and the b + r·t guarantee would be gone. The cap is the difference between a burst allowance and a savings account.

3

A fixed window is a weaker promise than it looks

Counting to limit and resetting at each boundary admits limit at the end of one window and limit again at the start of the next, so a client that straddles the boundary gets up to twice the advertised rate in one window's width. The advertised number is true only of the windows the counter is aligned to.

The material

The bucket, its bound, and the counter it replaces

How a bucket admits, what it guarantees over every window, and what the same trace does to a fixed-window counter.

Definition · Token bucket

A bucket has a capacity b and refills at r tokens per unit time. Between two arrivals separated by Δt, the level rises by r·Δt and is then capped at b. An arrival is admitted if at least one whole token is present, and it spends one; otherwise it is rejected and the level is unchanged.

Theorem · The bucket's guarantee

Over any window of length t, the number of admissions is at most b + r·t: the tokens that were in the bucket at the start, plus the tokens that arrived during the window.

That bound is the reason a bucket is used at all. It holds for every window — not for aligned windows, not on average — because every admission consumed a token, and the tokens available in an interval are the starting level plus the refill, with the starting level capped at b. With r = 5 a second and b = 3, no second anywhere in any trace carries more than 8 admissions.

trace: 20 arrivals over 2.2 seconds, bunched at 900 ms and 1000 ms r = 5 tokens / s b = 3 bucket admitted 13, rejected 7 largest second it allowed: 7 (bound is b + r = 8) fixed window at 5 per second, on the same trace admitted 12, rejected 8 largest second it allowed: 9 while advertising 5

The two limiters admit almost the same number of requests overall, which is what makes the comparison interesting: they differ in when. The window admitted fewer requests in total and let nine of them through in one second, because the trace was built to straddle a boundary. The bucket's worst second was seven, under its own bound of eight.

What the downstream service sees

The number that matters to whatever sits behind the limiter is the worst second, not the total: it is the one that sets the queue of the previous lessons, and a limiter advertising five that delivers nine has sized the downstream system's headroom incorrectly by a factor of nearly two. A bucket's worst case is b + r·t, which is a number you chose.

Example · Twenty idle seconds buy three tokens

With r = 1 a second and b = 3, a bucket left alone for twenty seconds holds three tokens, not twenty. Seven arrivals at once therefore see three admitted and four refused. The cap is what makes b mean “the burst I am willing to absorb” rather than “whatever has accumulated since the last quiet period”.

Choosing the two parameters is a design conversation with two separate questions in it. r is the sustained rate the downstream system can carry — a capacity question, answered by the earlier courses. b is how much bunching you are willing to pass through in one go, and its cost is paid by the queue behind it: the burst arrives, and everything this course has said about waiting says what that does to it.

Two smaller points worth knowing. Rate limiting is loss, not delay: a rejected request is refused immediately, which is a bounded, honest failure that a client can retry — and retries are exactly the c_a² that the variability lesson warns about, so they want jitter. And a bucket can also be run as a shaper rather than a limiter, holding arrivals until a token appears instead of refusing them, which converts the same bound back into waiting.

Token levels here are exact fractions of a token computed from integer millisecond gaps, not floating-point approximations, so a long trace cannot drift. That matters for a limiter more than for most arithmetic on this course: a drift of a thousandth of a token per arrival is a different limit after a million requests.

Interactive

Token buckets and rate limiting

b + rt in any window, and what a fixed window does instead

Token buckets and rate limitingb + rt in any window of t — which is not the same as r per second
admittedrejectedtokens in the bucketthe fixed window

Method

Choosing and checking a rate limit

Pick the sustained rate and the burst separately; they answer different questions.

1

Set r from what the downstream system can sustain

This is a capacity number, and it is the one the earlier courses compute. A limiter set above the sustained capacity is a limiter that does not limit.

2

Set b from the bunching you are willing to pass

The worst second the downstream service can see is b + r, and that is the number its headroom must cover. Choosing b is choosing that peak.

3

Run a real trace through it and count

Admissions, rejections and the largest burst in any window. A trace that straddles a boundary is the one worth running, because it is the case a fixed window fails.

4

Check the worst window against the bound

No window of length t may exceed b + r·t. If one does, the implementation is not a token bucket — most often because the level was allowed to exceed b.

Buckets and windows

Score 0 / 0

Twenty arrivals, a bucket and a fixed window

The trace bunches at 900 ms and again at 1000 ms, straddling a window boundary on purpose. Both limiters advertise five a second.

arrivals (ms) 0 200 400 600 800 900×5 1000×5 1400 1600 1800 2000 2200 token bucket r = 5 / s b = 3 admitted 13, rejected 7 pattern + + + + + + + − − − + − − − − + + + + + largest second allowed: 7 bound b + r·1 = 8 ✓ fixed window 5 per second, reset at each 1000 ms boundary admitted 12, rejected 8 largest second allowed: 9 advertised 5 ✗ the nine: four admitted late in the first window, at 200-800 ms, and five more the instant the counter reset at 1000 ms

Both limiters passed about the same volume. Only one of them kept its promise about every second, and the one that did not is the one that is easier to implement — a counter and a timestamp — which is why it is so common.

The bucket's worst second of seven is under its bound of eight rather than equal to it, because the trace's bunching does not line up perfectly with the refill. The bound is what you design against; seven is what this particular trace produced.

For a faded rehearsal, keep the trace and set b = 1. The supplied first move is that the bound becomes 1 + 5 = 6 per second. Predict whether the total admitted goes up or down, and what happens to the largest second.

Common mistakes

1
“100 per second” read as at most 100 in any one secondA fixed-window counter guarantees that only for windows aligned to its own boundaries; across a boundary it admits up to twice the limit, and on the lesson's trace a limit of five delivered nine. If the promise needs to hold for every second, the limiter has to be a bucket or a sliding window.
2
Letting the token level exceed bAn implementation that accumulates tokens without a cap turns a burst allowance into a savings account, and the b + r·t bound fails by however long the system was quiet. The cap is applied when the level is topped up, before the arrival is charged.
3
Sizing the downstream system for r rather than for b + rThe limiter's worst second is b + r·t, and that is the load the queue behind it actually sees. Sizing that queue for the sustained rate alone leaves it exposed to exactly the bursts the limiter was configured to permit.

Check your understanding

✓
Finish when you would ask “over which window?” before accepting any rate limit.

You should be able to run a trace through a bucket by hand, state the b + r·t bound and check a trace against it, show how a fixed window doubles its advertised limit across a boundary, and choose r and b from two separate design questions.

Note: Limiters and buffers both assume the overload is something to refuse. The last lesson takes the other case: the surge is legitimate, it is accepted, and “Transient Overload and Draining the Backlog” measures what it leaves behind and how long the system takes to give it back.

Kept in this browser only, for you. Nothing is sent anywhere and nothing checks it. Carry it to another device.

Feedback and recommendations

Something to change about this lesson? Write it down, tick it off when it is handled, and export the list when you want to act on it. Kept in this browser only: nothing is sent anywhere, and nobody is notified.

No recommendations yet for this lesson.