Definition · Batched per-item costLet F be the cost charged once per batch and v the cost charged per item. For a batch of B items the per-item cost is
F/B + v,
which is strictly decreasing in B, is bounded below by v, and reaches v only in the limit.
The two facts worth carrying are that it always falls and that it never reaches the floor. Together they mean there is no batch size at which the cost stops improving, and therefore no batch size that a cost argument alone will ever choose. Something else has to stop you, and that something is the wait.
F = 2 000 µ$, v = 100 µ$, λ = 500 items/s
B F/B + v fixed share mean wait first item
1 2 100 µ$ 95.2% 0 ms 0 ms
2 1 100 µ$ 90.9% 1 ms 2 ms
10 300 µ$ 66.7% 9 ms 18 ms
100 120 µ$ 16.7% 99 ms 198 ms
800 102.5 µ$ 2.4% 799 ms 1.6 s
Read the last two rows against each other. Going from B = 100 to B = 800 saves 17.5 µ$ an item — a further 14.6% — and multiplies the wait by eight. Going from B = 1 to B = 100 saved 1 980 µ$ an item, a factor of 17.5, for 99 ms. Same curve, and the two ends of it are not remotely the same trade.
Definition · Fill waitWith Poisson-free, evenly spaced arrivals at rate λ and a batch that closes when it is full, the k-th item to arrive waits (B − k)/λ for the batch to fill. The first waits (B − 1)/λ, the last waits nothing, and the mean over the batch is (B − 1)/(2λ).
B/λ is what gets quoted, and it is the time to collect B items rather than the time any item spends waiting. The difference is one inter-arrival time and it does not shrink as B grows: at λ = 500 it is 2 ms at every batch size. At B = 100 that is 200 ms quoted against 198 ms actual, which is a one per cent error; at B = 2 it is 2 ms quoted against 2 ms actual for the first item and 1 ms on average, which is not.
A flush timer is the other half of the design
A batch that closes only when full stalls forever if arrivals stop, so real systems close on whichever comes first: B items, or a timer of T. The timer bounds the wait no matter what the traffic does; the size bounds it when traffic is heavy. At λ = 500, B = 100 and a 200 ms timer the size wins, which is why the wait in the table is the fill wait rather than the timer.
Where the batch size actually comes from
“Choose B where the curve flattens” is a judgement, and the lab makes it a reproducible one by naming the threshold: it picks the smallest B at which the fixed share F/B has fallen to a stated fraction of v. At twenty per cent of v = 100 µ$ that is F/B ≤ 20, so B = 100. Stating the threshold is what turns a judgement into something a second person can check.
It is still a judgement, and there is an exact answer available once you are willing to price the latency rather than merely observe it. Give waiting a cost per item-second and the total becomes fixed cost per item plus holding cost per item, which is the economic order quantity — minimised in closed form, with no calculus, in “The EOQ Formula Without Calculus” in the Operations Research course “Inventory Models”. Latency plays the holding cost’s part exactly.
Example · The same bargain, one layer downA storage engine that calls fsync once per commit pays a fixed charge per call and amortises it across whatever transactions are waiting; the transactions wait for the group to form. That is F/B + v against (B − 1)/(2λ) with different units, and it is priced in “fsync and Group Commit” on “Storage Engines and Indexes”. Recognising the shape is most of the value of this lesson: it recurs at every layer that has a per-operation charge.
One warning about the floor. If v is large relative to F, batching is nearly pointless however large B gets: the curve starts close to its floor and has nowhere to fall. The quantity that decides whether batching is worth designing for is F/v, and it should be computed before the batch size is argued about.