One pass, and no room

The summary that has to forget

Every structure in this field so far accumulates. Ask instead for the count over only the last thousand arrivals and no counter will do, because a counter has no record of which of its increments are old — and the repair is a row of buckets whose whole error is the oldest one.

Every structure in this field so far has one thing in common that nothing has drawn attention to: it only ever accumulates. A counter goes up, a register bank’s registers only rise, a sketch’s cells take another addition. Even the turnstile model’s decrements are additions of negative numbers.

Now change the question. Not “how many ones have there been” but “how many ones were there in the last thousand arrivals”.

A counter cannot answer it. Not because it is too small — a counter is exact and costs seventeen bits — but because it has no record of which of its increments are about to become old. To decrement correctly at time tt it would have to know what arrived at time tWt - W, and it did not keep that.

An exponential histogram over a window of 400: 29 buckets, 200 against 200Each box is one bucket: its label is how many ones it covers, always a power of two, and its position is the span of arrivals it summarises. At most 6 buckets of any one size are allowed, and the 1,764 merges so far are what has kept that true. The estimate counts every bucket in full except the oldest, which is counted half — the shaded one, which straddles the edge of the window with part of it already outside and nothing recorded to say how much. That single bucket is the entire error of the structure: here it is 32 of 215, so the estimate is out by at most 16 against a true 200, and it is out by 1. The structure holds 406 bits against the 400 the window itself would take.3,600 — the window opens4,000 — now32161616884the window edge215 in buckets−16 for the oldest= 200true 2000.3% outsliding-window model · ε = 0.2, k = 5 · 1,764 merges406 bits against 400
Fig. 1 The structure that does answer it. Each box is a bucket holding a power-of-two number of ones and the timestamp of the most recent of them. Everything is counted in full except the oldest, shaded, which straddles the edge of the window — part of it has already left and nothing recorded says how much.

The model, and why it is different from the others

The sliding-window model is the fourth dial in this phase and it is the only one that changes what the structure has to be rather than how well it does.

Turning on signed updates left Count-Min working and broke its guarantee. Weakening the hash family left the tug-of-war estimator working and broke its variance. Turning on expiry leaves nothing working at all: an exact counter, a Count-Min sketch and a HyperLogLog are all equally unable to answer a windowed question, and no amount of extra state fixes it, because the missing information was discarded at arrival time rather than compressed.

So this is the only model in the phase whose structures are new constructions rather than old ones re-examined.

The obvious repairs, and why they fail

Keep the window. WW bits for a bit stream, exact, and the next essay proves that is optimal for an exact answer. At W=106W = 10^6 it is a megabit per counter and there are usually thousands of counters.

Keep a counter and subtract on expiry. This requires knowing the expiring item, which is keeping the window under another name.

Keep a counter per block of time. Divide the stream into blocks of W/kW/k arrivals, keep the last k+1k+1 block counts, and report the last kk plus a share of the oldest. This works and it is the right idea, and the state is kk counters of logW\log W bits each with error 1/k1/k. The exponential histogram is this idea improved, and the improvement is worth the extra machinery: fixed blocks spend the same number of bits on the recent past as on the distant past, and the distant past is the only part with any uncertainty in it.

The structure

Datar, Gionis, Indyk and Motwani’s exponential histogram keeps a list of buckets, newest last. Each holds a size, always a power of two, and the timestamp of the most recent arrival it covers.

On a one: append a bucket of size 1 with the current timestamp. Then, while more than k+1k+1 buckets share a size, merge the two oldest of that size into a single bucket of twice the size carrying the newer of their two timestamps. Then discard any bucket whose timestamp has left the window.

On a query: sum every bucket’s size, and subtract what is genuinely unknown about the oldest.

Three properties fall straight out of that.

Sizes are powers of two, because merges only ever combine two equal buckets. There are at most k+1k+1 buckets of each size, which is the invariant the merging maintains. And sizes grow geometrically towards the past, so the number of buckets is about klog2(N/k)k \log_2(N/k) where NN is the count in the window.

Here is one, in full: window of 400, ε=0.2\varepsilon = 0.2, so k=5k = 5.

32 · 16 16 16 16 16 16 · 8 8 8 8 8 8 · 4 4 4 4 4 4 · 2 2 2 2 2 · 1 1 1 1 1

Twenty-nine buckets, after 1,764 merges. They sum to 200; the estimate is 199.5; the true count in the window is 200. The structure holds 406 bits against the window’s 400 — at this window length it is not yet a saving, which is the subject of a later essay.

Twenty arrivals, in full

At k=2k = 2 — at most three buckets of any size — the first twenty ones produce this, oldest bucket on the left:

after buckets
3 1 1 1
4 2 1 1
6 2 2 1 1
8 2 2 2 1 1
9 2 2 2 1 1 1
10 4 2 2 1 1
14 4 4 2 2 1 1
18 4 4 4 2 2 1 1
20 4 4 4 2 2 2 1 1

Twelve merges in twenty arrivals. The interesting steps are the ones where a row is shorter than the row above it: at the fourth arrival a fourth bucket of size 1 would have appeared, so the two oldest merged into a 2. At the tenth, that merge produced a fourth 2, which merged into a 4 — one arrival, two merges, a cascade.

The list is a number written in a strange base. Reading it as sizes, 4 4 4 2 2 2 1 1 is twenty, and the constraint that at most k+1k+1 buckets share a size is what stops the representation from being either the unary one (all ones, exact, NN buckets) or the binary one (one bucket per power, logN\log N buckets, and a hopeless oldest bucket). kk interpolates between those two extremes and the interpolation is the whole design.

Why powers of two

Nothing forces the sizes to be powers of two, and the choice is worth defending because a different ratio is available.

Merging two equal buckets doubles the size, so the sizes that arise are powers of two, and the number of distinct sizes is log2\log_2 of the count. A ratio of rr instead — merge rr buckets at a time — gives logrN\log_r N distinct sizes and therefore fewer buckets, at the cost of a coarser oldest bucket. It is the same trade a dynamic array makes when it chooses its growth factor, and it comes out the same way: two is a good default and nothing dramatic happens on either side of it.

The part that is not a free choice is the kk buckets per size. That number is what bounds the oldest bucket as a share of the total, and it is the accuracy dial. Changing the ratio changes the state by a logarithmic factor; changing kk changes the error directly.

The whole error is one bucket

This is the part worth internalising, because it makes the accuracy argument trivial.

Every bucket except the oldest lies entirely inside the window: its timestamp is inside, and all its ones arrived no later than that. The oldest bucket is the only one that straddles the boundary — its timestamp is inside, so it has not been discarded, but the ones it absorbed may have arrived before the window opened.

So the only uncertainty in the answer is how many of the oldest bucket’s ones have already left, which is somewhere between zero and its size minus one. The estimate splits the difference, and the error is at most half the oldest bucket’s size.

The invariant then finishes the argument. There are at least kk buckets of every size smaller than the oldest one’s, so the total is at least kk times the oldest bucket’s size, so the error is at most 1/(2k)1/(2k) of the total. Set k=1/εk = \lceil 1/\varepsilon \rceil and the relative error is at most ε/2\varepsilon/2.

That last factor of two is not in the usual statement of the bound and it is exactly what the measurements show.

ε\varepsilon kk bound worst measured error
0.5 2 50% 20.6%
0.2 5 20% 9.8%
0.1 10 10% 4.8%
0.05 20 5% 2.5%
0.02 50 2% 1.0%

Every row is at or just under half the bound, across a window of a thousand and twenty thousand arrivals. A bound that is loose by exactly two, at every setting, is a bound that has dropped a factor rather than one that is being generous.

Error against the length of the windowThe worst relative error of each histogram across every position of the window. The bound is 1/k with k = ⌈1/ε⌉, and the measured error sits at about half of it, because the estimate already gives away half the oldest bucket. Both axes are logarithmic.1,0000.1window length W, in arrivalsworst relative errorε = 0.2ε = 0.1ε = 0.05sliding-window model · 40,000 arrivals · state from the shape of the structureworst 9.9%
Fig. 2 The measured worst error against the window length. The lines are flat: the accuracy is a property of kk and not of WW at all, which is what “the error is the oldest bucket over the total” means once the window is long enough to hold several buckets of every size.

A half that should have been a half of one less

The estimate above subtracts (size1)/2(\text{size} - 1)/2 from the total, and the textbook form subtracts size/2\text{size}/2. The difference is invisible almost everywhere and is wrong.

A bucket of size one holds exactly one arrival. If its timestamp is inside the window then that arrival is inside the window, and there is nothing uncertain about it. Charging half of it is charging uncertainty to a bucket that has none.

On a busy window this is a rounding error. On a quiet one it is not: a window holding five ones has all of them in buckets of size one, the structure knows the answer exactly, and the textbook estimate reports 4.5 — a 10% error from a structure that is keeping every arrival separately.

It was found by a sweep whose smallest window was quiet, and it is the only place in this field where a measured error exceeded what the bound permitted. Both forms are correct as bounds; only one of them is correct as an estimate.

An exponential histogram over a window of 1,000: 59 buckets, 536 against 548Each box is one bucket: its label is how many ones it covers, always a power of two, and its position is the span of arrivals it summarises. At most 11 buckets of any one size are allowed, and the 1,763 merges so far are what has kept that true. The estimate counts every bucket in full except the oldest, which is counted half — the shaded one, which straddles the edge of the window with part of it already outside and nothing recorded to say how much. That single bucket is the entire error of the structure: here it is 32 of 551, so the estimate is out by at most 16 against a true 548, and it is out by 13. The structure holds 885 bits against the 1,000 the window itself would take.3,000 — the window opens4,000 — now323232161616888the window edge551 in buckets−16 for the oldest= 536true 5482.3% outsliding-window model · ε = 0.1, k = 10 · 1,763 merges885 bits against 1,000
Fig. 3 A longer window at a tighter tolerance: k=10k = 10, so ten buckets of each size before a merge, and a finer staircase towards the past. The oldest bucket is proportionally smaller, which is the whole of the accuracy improvement.
An exponential histogram over a window of 200: 14 buckets, 92 against 85Each box is one bucket: its label is how many ones it covers, always a power of two, and its position is the span of arrivals it summarises. At most 3 buckets of any one size are allowed, and the 1,779 merges so far are what has kept that true. The estimate counts every bucket in full except the oldest, which is counted half — the shaded one, which straddles the edge of the window with part of it already outside and nothing recorded to say how much. That single bucket is the entire error of the structure: here it is 32 of 107, so the estimate is out by at most 16 against a true 85, and it is out by 7. The structure holds 182 bits against the 200 the window itself would take.3,800 — the window opens4,000 — now321616888the window edge107 in buckets−16 for the oldest= 92true 857.6% outsliding-window model · ε = 0.5, k = 2 · 1,779 merges182 bits against 200
Fig. 4 And the coarse end: k=2k = 2, so at most three buckets of each size and a very short list. The oldest bucket is a large share of the total and the estimate is correspondingly rough — this is what buying accuracy with kk looks like from the other side.

Read the two together and the parameter stops being a dial and becomes a picture. kk decides how many buckets of each size are allowed to stand before two of them merge, so it decides how large the oldest bucket is as a share of the window — a tenth of it at k=10k = 10, half of it at k=2k = 2 — and the half of that bucket the estimate cannot place is the 1/(2k)1/(2k) the invariant bounds. The staircase in each of these plates is the bound, drawn.

An exponential histogram over a window of 600: 27 buckets, 290 against 312Each box is one bucket: its label is how many ones it covers, always a power of two, and its position is the span of arrivals it summarises. At most 5 buckets of any one size are allowed, and the 2,763 merges so far are what has kept that true. The estimate counts every bucket in full except the oldest, which is counted half — the shaded one, which straddles the edge of the window with part of it already outside and nothing recorded to say how much. That single bucket is the entire error of the structure: here it is 64 of 321, so the estimate is out by at most 32 against a true 312, and it is out by 23. The structure holds 405 bits against the 600 the window itself would take.5,400 — the window opens6,000 — now64323232328the window edge321 in buckets−32 for the oldest= 290true 3127.2% outsliding-window model · ε = 0.25, k = 4 · 2,763 merges405 bits against 600
Fig. 5 A middle setting, six thousand arrivals into a window of six hundred. The staircase of sizes towards the past is the structure’s signature, and the shaded box at the left is the only part of the picture the answer is uncertain about.

What the merging costs

Merges are frequent — 1,764 of them over four thousand arrivals in the histogram above — and each is a constant amount of work, so the amortised cost per arrival is a small constant.

The accounting is the same one a dynamic array’s doubling uses, and it comes out the same way: a bucket of size 2j2^j can only be created by merging two of size 2j12^{j-1}, so the total number of merges over a stream of NN ones is at most Nj2j<NN \sum_j 2^{-j} < N. Fewer than one merge per arrival, on average, however the arrivals fall.

The worst individual arrival is a cascade: a one arrives, forces a merge at size 1, which overflows size 2, which overflows size 4, all the way up. That is O(logN)O(\log N) work for one arrival and it is the same spike a doubling array shows at a power of two — an amortised bound and a per-operation bound describing genuinely different quantities, one field along.

The recent past is exact and the distant past is not

There is a property of this structure that the error bound does not convey and that matters in practice: the resolution is not uniform in time.

The newest buckets are of size one, so the structure knows the last few arrivals exactly. Buckets double towards the past, so an arrival from long ago is known only to within the size of the bucket it landed in. Everything the structure has forgotten, it forgot about the distant past.

For a windowed count that is precisely the right allocation, because the only uncertainty in the answer is at the boundary and the boundary is old. For a query about when something happened it is the wrong allocation and the structure has nothing to offer: it can say the window holds two hundred ones and cannot say whether they were spread evenly or arrived in a burst forty arrivals ago.

The block-counter version of this idea has the opposite profile — uniform resolution everywhere, which spends bits on recent detail nobody asked for. The measured comparison at equal error is that the histogram holds klog2(N/k)k \log_2(N/k) buckets where blocks hold kk counters of log2W\log_2 W bits, and which is smaller depends on NN against WW; the histogram wins when the ones are sparse, because it does not pay for empty blocks.

An exponential histogram over a window of 800: 47 buckets, 235 against 236Each box is one bucket: its label is how many ones it covers, always a power of two, and its position is the span of arrivals it summarises. At most 11 buckets of any one size are allowed, and the 849 merges so far are what has kept that true. The estimate counts every bucket in full except the oldest, which is counted half — the shaded one, which straddles the edge of the window with part of it already outside and nothing recorded to say how much. That single bucket is the entire error of the structure: here it is 16 of 242, so the estimate is out by at most 8 against a true 236, and it is out by 2. The structure holds 705 bits against the 800 the window itself would take.3,200 — the window opens4,000 — now16161616168844the window edge242 in buckets−8 for the oldest= 235true 2360.6% outsliding-window model · ε = 0.1, k = 10 · 849 merges705 bits against 800
Fig. 6 A sparse stream — ones at about one arrival in six — over a window of 800. The bucket list is short because there is little to record, and the state a fixed block scheme would hold is unchanged by sparsity while this one falls with it.

Which timestamp a merge keeps, and why

A merge combines two buckets and keeps one timestamp — the newer of the two. That is stated above as part of the rule and it is a decision with a direction, worth pulling out because it settles which way the structure is wrong.

A bucket covers arrivals from its oldest to its newest. Stamping it with the newest means it survives until every one of its arrivals has left the window, so it is retained for a while after some of them have expired — the structure counts ones that are gone, and the estimate is high. Stamping it with the oldest would discard it as soon as its first arrival expired, so ones still inside the window would be thrown away and the estimate would be low.

Both are bounded by the same quantity — the bucket’s size — and the choice is purely which direction to be wrong in. The published rule takes the newer, so the raw sum of the buckets is an over-estimate, and the subtraction of half the oldest bucket is what turns a one-sided answer into a centred one.

That is the same decision as everywhere else in this field, arriving in an unexpected place. A structure whose caller must never under-count — a rate limiter, a quota, an alarm on a threshold — should report the raw sum and keep the one-sidedness the timestamp rule already provides. A structure whose answer is a display should subtract and be centred. The two answers differ by half a bucket and are available from the same state, and only one of them is usually offered.

The oldest bucket could carry a second timestamp

The whole error is one bucket, which makes the target for any improvement obvious: shrink what is unknown about the oldest bucket without keeping more of them.

There is a way, and it costs one timestamp. Store, for the oldest bucket only, the stamp of its earliest arrival as well as its latest. The bucket’s ones then have a known time span, and the fraction of that span lying inside the window is computable — so instead of assuming half the bucket has expired, the estimate can interpolate.

On a stream whose arrivals are roughly evenly spread the interpolation is much better than the half. A bucket of thirty-two spanning two hundred arrivals of which forty are outside the window has lost about a fifth of its ones, not a half, and the difference is six counts on an answer of two hundred.

Two honest caveats, and they are the reason this is not simply an improvement.

It is not a bound. The ones inside a bucket may be clustered at either end, so the interpolation can be wrong in either direction by up to the bucket’s size — which is exactly the uncertainty the half was there to acknowledge. The right arrangement keeps both: report the interpolation as the point estimate and the untouched [totalsize, total][\,\text{total} - \text{size},\ \text{total}\,] as the bracket, which is the same two-numbers-per-answer arrangement a counter-based summary can offer and for the same reason.

And it costs a stamp on one bucket rather than on all of them, which is thirteen bits against a structure of four hundred. That is a favourable trade only because the error is concentrated in one place; a structure whose uncertainty was spread over every bucket would have to pay for every one, and the concentration this essay is about is what makes the repair cheap.

What it is not

It is not linear. The state is not a sum of updates: it is a set of buckets with timestamps, and merging two histograms of different windows is not defined. So the model list for this structure is the sliding window and nothing else, which is exactly the opposite of the sketches two essays back — they are valid in every model that does not expire, and invalid in the one that does.

It does not count keys. Everything above is about a bit stream: how many ones in the last WW. Windowed versions of the frequency and cardinality questions exist and are built from this idea plus per-key or per-register timestamps, and they are not implemented here.

It cannot be un-asked. The window length is baked into the expiry rule, so the same arrivals processed under two window lengths need two structures. Where several windows are wanted — the last minute, hour and day, which is the ordinary requirement — that is three histograms and three times the state, and there is no sharing between them worth having, because the coarse structure has already merged away the detail the fine one needs.

It cannot be asked about an arbitrary interval. The window is fixed when the structure is built. A query for “the last hundred” from a structure built for the last thousand has no answer better than a proportion, because the buckets that would resolve it have already been merged.

Which structure is valid in which model, and what the violation costsA filled cell is a structure that declares itself valid in that model. An outlined cell is one that will run there without complaint and whose guarantee says nothing about it — and the number in it is what that costs, measured on a general turnstile stream of 40,000 updates over 2,048 keys with a deletion rate of 0.5, on which 796 keys end with a negative count. Count-Min came back BELOW the true count on 1,758 of 1,895 keys, which its own theorem forbids and which nothing in the returned number reveals; Count-Sketch, which promises nothing about the direction of its error, came back below on 48% of them, as it is designed to. The models differ in four dials: whether an update may be negative, whether a count may end negative, whether items expire, and how many passes are allowed.exponential histogram0 bitsCount-Min8,192 bitsGreenwald–Khanna0 bitscash register+strict turnstile±general turnstile± may go negativesliding window+ expiresdeclareddeclareddeclared93% underdeclared40,000 updates · deletion rate 0.5 · every count exact1,758 of 1,895 under
Fig. 7 The dial that changes what a structure is. Nothing valid in the first three columns is valid in the fourth, and the histogram is valid in the fourth and nothing else — expiry is the only one of the four dials that partitions this table rather than shading it.

What this makes readable

Essays that name this one as a prerequisite.

Named alongside this one

Essays reaching for the same objects. Nobody chose these; they are what the concept index makes visible.

What links here

The 8 essays that link to this one and share the most of its objects, of 9 that link here.

The objects this essay names

Each one links to every other essay that touches it.

Amortised analysisBucket mergeExpiryExponential histogramGuaranteeMeasurementOne passRelative errorSketchSliding windowState bitsStream modelStreaming algorithmTrade off