One pass, and no room

The counter with no window in it

A counter that fades by half every H settles, on a steady stream, at exactly the count of a window of 1.44H. That correspondence holds in the mean, on a steady stream, and nowhere else — and it is the reason a decayed counter is not an estimate of a windowed count for any window.

The sliding window is not the only way to stop counting the distant past, and on the evidence of what is deployed it is not the usual one.

A monitoring system almost always runs an exponentially decayed counter. Every count fades by half every HH seconds; there is no WW, no ring buffer, no timestamp per item, and no moment at which anything is expired. The state per key is a number and the time it was last touched, and that is the whole structure.

It is a different question with a similar-sounding answer, and this essay is about keeping the two apart.

One key, decayed and windowed, through bursts and silenceThe blue curve is an exponentially decayed counter with a half-life of 4 s. The second curve is an exact count over a window of 5.77 s — the window chosen so that the two agree in the mean on a steady stream. They do not agree here and the shape of the disagreement is the point: the windowed count is a step function that drops the moment an arrival leaves, and the decayed value is smooth and never reaches zero. Through a silence the window empties and the counter fades; through a burst the window jumps and the counter climbs behind it. Neither is wrong. They are answers to different questions, and a dashboard showing one labelled as the other is the ordinary case.decayedwindowed060120secondscount attributed to key 0half-life 4 s · window 5.77 s · burstythe same key, two questions
Fig. 1 One key through a bursty stream, counted two ways. The decayed counter has a half-life of four seconds; the window is 5.77 seconds long, chosen so that the two agree in the mean on a steady stream. Neither is wrong and they are not the same curve.

The one place they agree

Start with the correspondence, because it is exact and it is the reason the two get confused.

Take a steady stream at λ\lambda arrivals per tick. Each arrival adds one to the counter, and everything already in the counter fades by 21/H2^{-1/H} per tick. In the limit the counter settles where the inflow balances the fade:

v=λ121/Hv_\infty = \frac{\lambda}{1 - 2^{-1/H}}

For any half-life worth having — many ticks — the denominator is very close to ln2/H\ln 2 / H, so vλH/ln2=1.44λHv_\infty \approx \lambda H / \ln 2 = 1.44\,\lambda H. That is the count a window of 1.44H1.44H would hold on the same steady stream.

So a decayed counter with half-life HH and a window of 1.44H1.44H give the same number, in the mean, on a steady stream.

half-life equivalent window, exactly 1.44 H
0.5 s 0.72 s 0.72 s
2 s 2.89 s 2.89 s
8 s 11.54 s 11.54 s
60 s 86.56 s 86.56 s

The site’s gate checks that the exact expression and the 1.44H1.44H approximation agree to within a per cent at every half-life the figures use, which is the sort of thing worth asserting rather than trusting, because the approximation fails when HH approaches a single tick and that is a regime a badly configured system can reach.

The window a decayed counter is not quite equivalent toA counter that fades by half every H and takes one increment per arrival settles, on a steady stream, at λ/(1 − 2^(−1/H)) — which is λH/ln 2 to within a per cent for any half-life worth having. So a decayed counter and a count over a window of H/ln 2 ≈ 1.44H agree in the mean, on a steady stream, and that is the whole of the correspondence. They do not agree on any particular stream, they do not agree at all once the rate moves, and no D makes the decayed counter an estimator of a windowed count — it is a weighted total in which every arrival ever seen still contributes something. The bars are the exact equivalent window; the marks are 1.44H.0.5s1s2s4s8s16shalf-life Hequivalent window, in ticksbars: exactrings: 1.44 Hagreeing in the mean,on a steady stream,and nowhere else1 ms clock · mean gap 10 ticksλ/(1 − 2^(−1/H))
Fig. 2 The equivalent window against the half-life, exactly and approximately. The bars are λ/(1 − 2^(−1/H)) and the rings are 1.44H, and the plate asserts they agree rather than remarking that they do.

And every place they do not

That table invites a substitution — a half-life of D/1.44D/1.44 is a window of DD — and the substitution is wrong in three separate ways, each worth its own sentence.

It holds in the mean and not on a run. The steady-state value is a limit of an expectation. On any particular stream the decayed counter fluctuates around it, and on a bursty stream it fluctuates a long way: the opening plate shows it climbing through a burst and falling through a silence while the windowed count jumps and drops. The two curves cross repeatedly and are rarely equal.

It holds on a steady stream and not otherwise. The derivation assumes a constant λ\lambda. When the rate moves — which is a window that is a duration’s whole subject — the decayed counter is tracking a moving quantity with a lag of about HH, and the windowed count is tracking it with a hard cutoff at DD. Those are different filters and they respond differently to the same change.

And it is a correspondence of values, not of questions. This is the important one. A window’s count is how many arrived in the last DD, and it is a count: an integer, exactly determined by the window’s contents, and it is zero when nothing arrived. A decayed counter’s value is a weighted total in which every arrival ever seen still contributes something, and it is never zero after the first arrival. There is no DD for which the second is an estimator of the first, because the second is not estimating anything — it is computing a different function, exactly.

One key, decayed and windowed, through a rate that rises and fallsThe blue curve is an exponentially decayed counter with a half-life of 4 s. The second curve is an exact count over a window of 5.77 s — the window chosen so that the two agree in the mean on a steady stream. They do not agree here and the shape of the disagreement is the point: the windowed count is a step function that drops the moment an arrival leaves, and the decayed value is smooth and never reaches zero. Through a silence the window empties and the counter fades; through a burst the window jumps and the counter climbs behind it. Neither is wrong. They are answers to different questions, and a dashboard showing one labelled as the other is the ordinary case.decayedwindowed064127secondscount attributed to key 0half-life 4 s · window 5.77 s · driftingthe same key, two questions
Fig. 3 The same comparison on a stream whose rate drifts. The lag is visible: the decayed curve is a smoothed version of the windowed one, delayed by about a half-life, and the gap between them is largest exactly where the rate is changing fastest.

The weighting, drawn out

It helps to write down what a decayed counter is computing, because the phrase a weighted total is doing a lot of work.

At time tt the counter’s value is

v(t)=i2(tti)/Hv(t) = \sum_{i} 2^{-(t - t_i)/H}

summed over every arrival ii that ever happened, at its own time tit_i. Every term is positive. An arrival from ten half-lives ago contributes 2100.0012^{-10} \approx 0.001, an arrival from thirty contributes 10910^{-9}, and none of them contributes zero.

A window’s count is the same sum with a different weight: one inside the window, zero outside. Both are weighted sums over the whole history; they differ in the shape of the weight, and the shapes are a box and an exponential.

That framing makes several things obvious at once. The box has a hard edge, which is why a windowed count steps; the exponential has none, which is why a decayed value is smooth. The box has bounded support, so it can be computed from bounded state; the exponential does not, which is why the decayed counter must be computed recursively rather than by remembering its terms — and why its state is one number rather than an occupancy.

And it makes the correspondence legible: 1.44H1.44H is simply the width of the box with the same area as the exponential. Two filters with the same total weight, and every other property different.

What the decayed family is actually for

Given all that, the fact that decay is what gets deployed needs explaining, and the explanation is not that anybody confused it with a window.

It has no expiry. A sliding window has to remove things, and removing means touching state that nothing has asked about. A decayed counter’s value at any instant is a function of its stored value and the elapsed time, so it can be computed on the way past — which is the fading nobody computes’s subject and is worth an order of magnitude.

Its state is constant and small. A number and a timestamp per key, whatever the arrival rate. An exact time window’s state is the occupancy, which the window that is not full measured swinging by a factor of twenty-two on an ordinary stream. A decayed counter meets that stream without growing.

And its answer is smooth, which is what a threshold wants. A windowed count is a step function: an arrival leaving the window changes the answer discontinuously, so an alert on a windowed count near its threshold flaps. A decayed value moves continuously, so the same alert has hysteresis for free.

Those are three real advantages and none of them is accuracy. The decayed counter is not a cheaper approximation to a window. It is a different summary whose properties happen to suit the job a window is usually asked to do.

The same 17-bit stamp, at four arrival ratesA window of 60 s on a 1 ms clock costs 17 bits per stamp at every rate on this plate, because the width comes from the duration and the resolution and nothing else. What changes is how many stamps there are: 60 at 1 Hz and 60,000 at 1000 Hz. In the arrival-counted model these two numbers are one parameter — W sets both the occupancy and the stamp width — and the model therefore cannot express the case this plate is about: a quiet stream watched at fine resolution, where the clock is 35% of every entry and the structure holds almost nothing.1 Hz60 entries10 Hz600 entries100 Hz6,000 entries1,000 Hz60,000 entrieskeysstamps — 17 bits each, at every ratestate held by an exact time windowD = 60 s · 1 ms clock · key 32 bitsstamp 17 bits, whatever the rate
Fig. 4 What the alternative costs, for comparison. An exact time window’s state is one entry per item held, and the item count moves with the rate — a decayed counter’s is one entry per key, and does not.

What it costs in bits

The state accounting is short enough to give in full, and it is where the decayed family looks best.

Per key: a value and a timestamp. The value is a float — thirty-two bits is generous, since the quantity is an approximation of nothing and its precision requirement comes from the ratio between the largest and smallest counts anybody will compare. The timestamp needs enough range to distinguish instants within a few half-lives, because beyond that the fade has taken the contribution to nothing anyway.

So a table of kk decayed counters is k×(key+value+stamp)k \times (\text{key} + \text{value} + \text{stamp}), and it does not move with the arrival rate, the window length, the burstiness or the occupancy. Every one of those moved something in the two previous essays.

Compare against the exact time window it corresponds to. At a hundred arrivals a second and a 5.77-second equivalent window, the exact window holds about 577 entries of a key and a stamp each; the decayed table holds kk, and kk is whatever the operator chose — thirty-two in the figures here. Eighteen times smaller, on a steady stream, and unboundedly smaller on a stream that spikes, because the window’s state follows the spike and the table’s does not.

That is the trade in one line: the window can answer exactly and its size is the stream’s business; the decayed table answers a different question and its size is the operator’s.

Eighteen times is the wrong ratio

The state comparison above puts 577 window entries against 32 table entries and calls it eighteen times smaller. The two numbers count different things, and correcting for that changes both the figure and what it depends on.

The window’s 577 are items: one entry per arrival still inside the window, whatever key it belongs to. The table’s 32 are keys: one entry each, however many times each arrived. So the window is holding every key that arrived in the last 5.77 seconds and the table is holding thirty-two of them — and on the stream these figures use, whose key space is sixty-four, the table is evicting at nearly every arrival.

A structure that has dropped half the key space is not answering the same question more cheaply. It is answering a smaller question.

Hold the coverage fixed and the arithmetic changes. A decayed table covering all sixty-four keys needs k=64k = 64, and 577 against 64 is nine times, not eighteen. The other nine were bought by shrinking the table, and could have been bought the same way in either family.

Written generally, the saving is

arrivals in the windowdistinct keys in the window,\frac{\text{arrivals in the window}}{\text{distinct keys in the window}},

which is the mean number of arrivals per key over the equivalent window — nine here, and a property of the workload rather than of either structure.

That reads the advantage the opposite way round from the usual telling. A decayed table is cheapest against a stream where each key arrives many times inside a window, because each of those arrivals costs the window an entry and costs the table nothing. And on a stream of mostly-unique keys — where the mean is near one — there is no state saving at all: the window holds one entry per key and so does the table, and the table additionally has to evict.

Which is worth saying plainly, because a stream of mostly-unique keys is precisely the case where a windowed structure’s state looks alarming and somebody reaches for decay to fix it. It does not fix it. The occupancy swing the window that is not full measures is still real, and it is a swing in the number of keys on that kind of stream — which the decayed table meets by evicting rather than by growing, so the state stays flat and the answer quietly stops covering the stream.

The flat state is a cap, not a compression. The three advantages this page lists — no expiry, constant state, a smooth answer — survive; the second is a decision to hold kk keys rather than a discovery that kk suffice, and the ratio to a window is whatever a window that is a duration’s occupancy divided by its distinct keys happens to be.

The half-life is not an accuracy parameter

One more distinction, because it is the one that decides how a decayed counter is configured.

Every other structure in this collection has an accuracy dial: more registers, more counters, a smaller ε\varepsilon, more bits. Turn it and the answer gets closer to the truth. The decayed counter has no such dial, and HH is not one.

HH decides what question is asked. A half-life of a second and a half-life of an hour are both exact — the value each computes is the exactly correct weighted sum for its own weighting — and they answer different questions about the same stream. There is no setting of HH at which the counter is more nearly right, because it is never wrong.

That has a consequence for how such a system fails. A structure with an accuracy dial set too coarse produces answers that are visibly noisy, and somebody turns the dial. A decayed counter with HH set wrong produces answers that are perfectly smooth, perfectly stable and about the wrong timescale, and nothing about them looks like a misconfiguration. The failure mode of a parameter that is not an accuracy parameter is that it never looks like an error.

The family, and the ones this essay is not about

Exponentially decayed names a family rather than a structure, and it is worth marking the boundaries so the claims above are read at the right scope.

The counter here decays by a fixed factor per unit of time. That is the version monitoring systems run, and it is what everything above measures.

A forward-decay scheme decays relative to a landmark rather than relative to now — weights are computed against a fixed origin and the origin is advanced periodically. It computes the same thing without the drift in floating-point that the fixed version accumulates, and it pays for it with a rescaling event.

And an exponentially decayed sample keeps items rather than counts, giving a sample of the stream biased toward the recent, which supports quantiles and not merely totals. It is a different object with the same adjective.

All three share the property this essay is about — the weight is exponential in age, so nothing is ever fully forgotten — and they differ in what they hold and in when they do work. What is measured here is the first, and the arithmetic of the correspondence applies to all three because it is a property of the weight function rather than of the implementation.

What the correspondence is good for anyway

None of this makes the 1.44H1.44H relation useless, and it is worth saying what it is good for, since it is the only bridge between the two families.

It is a sizing relation. If a system currently runs a five-minute window and wants to move to decay for the operational reasons above, H=D/1.443.5H = D/1.44 \approx 3.5 minutes gives a counter whose steady-state value matches the window’s count, so the existing thresholds keep roughly their existing meanings. That is a genuinely useful migration and it is the reason to know the constant.

What it is not is a licence to describe the result as a five-minute window. The thresholds carry over; the semantics do not, and a report generated from the new counter and compared against a report from the old window will differ by whatever the stream was doing, in the ways above.

The window a decayed counter is not quite equivalent toA counter that fades by half every H and takes one increment per arrival settles, on a steady stream, at λ/(1 − 2^(−1/H)) — which is λH/ln 2 to within a per cent for any half-life worth having. So a decayed counter and a count over a window of H/ln 2 ≈ 1.44H agree in the mean, on a steady stream, and that is the whole of the correspondence. They do not agree on any particular stream, they do not agree at all once the rate moves, and no D makes the decayed counter an estimator of a windowed count — it is a weighted total in which every arrival ever seen still contributes something. The bars are the exact equivalent window; the marks are 1.44H.0.5s2s8s32shalf-life Hequivalent window, in ticksbars: exactrings: 1.44 Hagreeing in the mean,on a steady stream,and nowhere else1 ms clock · mean gap 10 ticksλ/(1 − 2^(−1/H))
Fig. 5 The bridge, at four half-lives an operational system might choose. It is arithmetic rather than measurement — the value comes out of the geometric series — and the plate exists to make the constant available rather than to establish it.

Where the smoothness is a liability

The smoothness is listed above as an advantage and it is worth turning over, because it costs something specific.

A windowed count forgets completely. Five minutes after a spike, a five-minute window contains no trace of it, and a query answered then is a query about a period the spike is not in. That is often exactly what is wanted: is it happening now has a clean answer.

A decayed counter never forgets completely. Five minutes after a spike at a three-minute half-life, about a third of the spike is still in the number, and a threshold crossed during the spike may still be crossed. The alert that fired during the incident keeps firing into the recovery, and the operator watching it cannot tell from the number whether the thing is still happening.

Which behaviour is wanted depends on the question, and the two are not interchangeable:

  • “Is the error rate above 1% right now?” wants the box. The exponential answers a question about the recent past weighted by recency, and it will say yes for a while after the answer is no.
  • “Has this key been consistently hot?” wants the exponential. The box answers only about the last DD, and a key that has been hot for an hour looks exactly like a key that became hot DD ago.

A dashboard showing one labelled as the other is the ordinary case, and the reason it survives is that on a steady stream — which is what most systems look like most of the time — the two agree. They diverge exactly during an incident, which is when the number is being read.

The parameter a reader should be told

If a decayed counter’s half-life decides what question is asked, then the half-life is not an implementation detail and belongs next to the number wherever it is shown.

That is a small editorial rule with a real consequence. A graph labelled requests per second computed from a decayed counter is labelled wrongly — it is a decayed weighted total divided by a normalising constant, and its relationship to any rate depends on the half-life and on how steady the traffic was. A graph labelled requests per second, three-minute half-life is honest and is no longer than the wrong one.

The same rule has been applied throughout this collection to every structure with a model parameter in it: a cache miss count without a line size is not a measurement, an ε without a denominator is not an error, and a decayed value without a half-life is not a rate. The parameter is part of the quantity, not part of how it was computed.

The multiplications nobody performsOver 12,000 arrivals into 32 decayed counters, the lazy structure computed 382,976 fades and an implementation that aged every counter on every tick would have computed 3,834,720 — a factor of 10. This is the whole reason the decayed family is what monitoring systems run: a sliding window has to expire, and expiring means touching state nothing has asked about, while a decayed counter's value is a function of when it was last touched and can be computed on the way past. The one moment laziness costs something is an eviction, which has to compare every counter at a common instant; there were 11,968 of those, and they are most of the left-hand bar.fades a lazy table computed382,976fades an eager table would compute3,834,720fade multiplications (log scale)11,968 evictions, each comparing all 32 counters at one instant32 counters · half-life 4 s · even10× fewer
Fig. 6 And the implementation is part of what it costs. A decayed table on an evenly spaced stream, computing a tenth of the fades an eager one would — which is the next essay’s subject and is unrelated to what the counter answers.

What is not established

No bound is proved here. The steady-state identity is exact and is derived rather than fitted. Everything else on this page is a measurement of specific streams: how far the two curves separate on a bursty stream, how much lag appears on a drifting one. Neither is a bound on the difference and neither could be, since the difference is unbounded on a stream that is sufficiently uneven.

One key, followed. The trace figures follow a single key through the stream. A table of decayed counters has a further problem — evicting the smallest requires comparing counters at a common instant — and that is measured in the next essay rather than this one.

And the arithmetic assumes exact reals. The fade is a floating-point multiply, and a value faded across many half-lives becomes denormal and then zero. That is a second kind of forgetting, it is not the decay, and it is the other half of what the next essay measures.

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

Every essay whose body links to this one.

The objects this essay names

Each one links to every other essay that touches it.

Arrival processEstimatorExponential decayGuaranteeHalf lifeSliding windowState bitsStreaming modelTime windowTimestamp