Skip to content
About
Main content

Buffering

Why does a bigger buffer not help?

A buffer decouples the 's rate from the 's rate for a bounded amount of work. Think of the waiting room of a clinic: it lets patients arrive in a burst while the doctor works at a steady speed. The number that matters is not the buffer's size in bytes but its capacity in seconds:

how much the buffer holds (events)
how much work comes in per second
how much work the consumer can finish per second
work waiting after t seconds of overload
600,000 events6,000,000 events
Data table
How long a buffer lasts, by how far arrivals outrun service
Overload λ − μ (events per second)seriesSurvival time
10600,000 events60,000 s
11.2600,000 events53,500 s
12.6600,000 events47,700 s
14.1600,000 events42,500 s
15.8600,000 events37,900 s
17.8600,000 events33,700 s
20600,000 events30,100 s
22.4600,000 events26,800 s
25.1600,000 events23,900 s
28.2600,000 events21,300 s
31.6600,000 events19,000 s
35.5600,000 events16,900 s
39.8600,000 events15,100 s
44.7600,000 events13,400 s
50.1600,000 events12,000 s
56.2600,000 events10,700 s
63.1600,000 events9,510 s
70.8600,000 events8,480 s
79.4600,000 events7,550 s
89.1600,000 events6,730 s
100600,000 events6,000 s
112600,000 events5,350 s
126600,000 events4,770 s
141600,000 events4,250 s
158600,000 events3,790 s
178600,000 events3,370 s
200600,000 events3,010 s
224600,000 events2,680 s
251600,000 events2,390 s
282600,000 events2,130 s
316600,000 events1,900 s
355600,000 events1,690 s
398600,000 events1,510 s
447600,000 events1,340 s
501600,000 events1,200 s
562600,000 events1,070 s
631600,000 events951 s
708600,000 events848 s
794600,000 events755 s
891600,000 events673 s
1,000600,000 events600 s
1,120600,000 events535 s
1,260600,000 events477 s
1,410600,000 events425 s
1,580600,000 events379 s
1,780600,000 events337 s
2,000600,000 events301 s
2,240600,000 events268 s
2,510600,000 events239 s
2,820600,000 events213 s
3,160600,000 events190 s
3,550600,000 events169 s
3,980600,000 events151 s
4,470600,000 events134 s
5,010600,000 events120 s
5,620600,000 events107 s
6,310600,000 events95.1 s
7,080600,000 events84.8 s
7,940600,000 events75.5 s
8,910600,000 events67.3 s
10K600,000 events60 s
11.2K600,000 events53.5 s
12.6K600,000 events47.7 s
14.1K600,000 events42.5 s
15.8K600,000 events37.9 s
17.8K600,000 events33.7 s
20K600,000 events30.1 s
22.4K600,000 events26.8 s
25.1K600,000 events23.9 s
28.2K600,000 events21.3 s
31.6K600,000 events19 s
35.5K600,000 events16.9 s
39.8K600,000 events15.1 s
44.7K600,000 events13.4 s
50.1K600,000 events12 s
56.2K600,000 events10.7 s
63.1K600,000 events9.51 s
70.8K600,000 events8.48 s
79.4K600,000 events7.55 s
89.1K600,000 events6.73 s
100K600,000 events6 s
106,000,000 events600,000 s
11.26,000,000 events535,000 s
12.66,000,000 events477,000 s
14.16,000,000 events425,000 s
15.86,000,000 events379,000 s
17.86,000,000 events337,000 s
206,000,000 events301,000 s
22.46,000,000 events268,000 s
25.16,000,000 events239,000 s
28.26,000,000 events213,000 s
31.66,000,000 events190,000 s
35.56,000,000 events169,000 s
39.86,000,000 events151,000 s
44.76,000,000 events134,000 s
50.16,000,000 events120,000 s
56.26,000,000 events107,000 s
63.16,000,000 events95,100 s
70.86,000,000 events84,800 s
79.46,000,000 events75,500 s
89.16,000,000 events67,300 s
1006,000,000 events60,000 s
1126,000,000 events53,500 s
1266,000,000 events47,700 s
1416,000,000 events42,500 s
1586,000,000 events37,900 s
1786,000,000 events33,700 s
2006,000,000 events30,100 s
2246,000,000 events26,800 s
2516,000,000 events23,900 s
2826,000,000 events21,300 s
3166,000,000 events19,000 s
3556,000,000 events16,900 s
3986,000,000 events15,100 s
4476,000,000 events13,400 s
5016,000,000 events12,000 s
5626,000,000 events10,700 s
6316,000,000 events9,510 s
7086,000,000 events8,480 s
7946,000,000 events7,550 s
8916,000,000 events6,730 s
1,0006,000,000 events6,000 s
1,1206,000,000 events5,350 s
1,2606,000,000 events4,770 s
1,4106,000,000 events4,250 s
1,5806,000,000 events3,790 s
1,7806,000,000 events3,370 s
2,0006,000,000 events3,010 s
2,2406,000,000 events2,680 s
2,5106,000,000 events2,390 s
2,8206,000,000 events2,130 s
3,1606,000,000 events1,900 s
3,5506,000,000 events1,690 s
3,9806,000,000 events1,510 s
4,4706,000,000 events1,340 s
5,0106,000,000 events1,200 s
5,6206,000,000 events1,070 s
6,3106,000,000 events951 s
7,0806,000,000 events848 s
7,9406,000,000 events755 s
8,9106,000,000 events673 s
10K6,000,000 events600 s
11.2K6,000,000 events535 s
12.6K6,000,000 events477 s
14.1K6,000,000 events425 s
15.8K6,000,000 events379 s
17.8K6,000,000 events337 s
20K6,000,000 events301 s
22.4K6,000,000 events268 s
25.1K6,000,000 events239 s
28.2K6,000,000 events213 s
31.6K6,000,000 events190 s
35.5K6,000,000 events169 s
39.8K6,000,000 events151 s
44.7K6,000,000 events134 s
50.1K6,000,000 events120 s
56.2K6,000,000 events107 s
63.1K6,000,000 events95.1 s
70.8K6,000,000 events84.8 s
79.4K6,000,000 events75.5 s
89.1K6,000,000 events67.3 s
100K6,000,000 events60 s

The four decisions about any buffer

1. Where does it live?

LocationCapacitySurvives a crash?Cost
In-process memoryMB to GBNofastest; puts pressure on the garbage collector; lost on restart
Local disk (spill)GB to TBYes (same machine only)disk I/O; tied to one machine
TB and moreYesinfrastructure to run; a network hop
Object storeeffectively unlimitedYes; cost per request

Moving down the table, durability goes up and speed goes down. That is no surprise. But note one thing: If a record is acknowledged into an in-memory buffer and the process restarts, the record is lost. This is silent data loss, and no metric shows it. If your producer is told "OK" before the buffer is durable, you have chosen delivery (a message may be lost but never repeated) without saying so. This is the second half of Ravi's problem: the lost in-flight work.

2. Bounded or unbounded?

Always bounded. An unbounded is a . It accepts work that it will never process in time, so records reach the consumer already stale and useless. The out-of-memory kill comes anyway, only later and with more work destroyed. The only thing an unbounded buffer buys is a longer wait before the failure, and the failure is total instead of partial.

3. What is the full-policy?

There are four choices:

  • Block the producer (this becomes , below).
  • Drop-newest (reject the new arrival; this is , below).
  • Drop-oldest (prefer fresh data over complete data).
  • Spill to a bigger, slower place.

Drop-oldest is underused. Use it when a newer record makes an older one worthless: metrics, sensor readings, cache invalidations, "who is online" status, price ticks. Dropping the head of the queue (the oldest) is then strictly better than dropping the tail (the newest). For a keyed buffer where only the latest value per key matters, dropping the oldest per key is better still: you reduce volume without losing any entity (a product, a user, a sensor).

4. Head-of-line blocking?

In a single FIFO (first in, first out) buffer, one slow item stops everything behind it. Giving each key or each tenant (customer) its own sub-buffer fixes this, at the cost of managing N buffers. This is the idea of bulkheading.

Bufferbloat: the thing most people get wrong

Bigger buffers feel safer. Past a certain size, they are not.

A large buffer that stays full gives you the latency of a full buffer all the time, and no extra . Throughput was always μ. The buffer never changed it.

The correct way to think about sizing: Use bandwidth-delay-product logic (the amount of data "in the pipe" during one ): the buffer must cover the length of the burst plus the reaction time of whatever downstream mechanism will actually fix the imbalance. If your buffer is chronically more than about half full, it is not protecting you. It is a queue on which you pay extra latency all the time. The right move is a smaller buffer plus an earlier signal, not a bigger buffer.

Two mitigations are worth knowing by name:

  • CoDel (controlled delay). Measure the minimum queueing delay over a sliding time interval. If even the minimum stays above a target, the queue never drains: it is a , not a burst. Then start dropping. This is elegant because it separates a good burst-absorbing buffer from a bad standing one, which raw queue depth cannot do.
  • Deadline-aware buffering. Stamp each record with an expiry time when it enters the buffer. When you take it out, drop it if it is already useless. This saves the consumer's scarce capacity for records somebody will still use. It is cheap to build and surprisingly effective.