Answers · Where the data lives
How do you count unique visitors without remembering every visitor?
In shortTurn each visitor identifier into a random-looking code, keep only a small summary of the rarest pattern seen, and scale it up; repeats change nothing and memory stays fixed.
Hash each visitor identifier to a random-looking value and keep only a small summary statistic of those hashes. Identical identifiers produce identical hashes, so a repeat changes nothing; a fixed number of small counters then estimates how many distinct visitors there were without storing any identifier. The answer is an estimate, not an exact list, and its memory is chosen by the error you can tolerate rather than by the number of visitors.
Why duplicates vanish
The hash is the same every time the same visitor arrives. If visitor A hashes to a value beginning 0101 and visitor B to one beginning 0001, then A returning later brings 0101 again, not a new value. A sketch that keeps only a statistic of hashes, such as the longest run of leading zeros seen, therefore cannot be affected by repetition. It never needs a set of seen identifiers, a bloom filter, or a database key, because repetition is invisible by construction.
A uniform hash begins with a zero with probability one half, with two zeros with probability one quarter, and with k zeros with probability . Turn that round: if the longest run of leading zeros observed is k, the number of distinct hashes drawn is probably around . Seeing seven leading zeros suggests about distinct visitors; eleven suggests about . The estimate moves in powers of two, so one such counter is far too coarse to report, but it is a real estimate and it uses one small integer.
| step | item | its hash begins | leading zeros | longest so far | estimate | what happened |
|---|---|---|---|---|---|---|
| 1 | first visitor | 0101... | 1 | 1 | 2 | One leading zero is unremarkable, so the estimate is tiny. |
| 2 | second visitor | 0001... | 3 | 3 | 8 | Three zeros is a one in eight pattern, so a handful of draws is suggested. |
| 3 | the first visitor again | 0101... | 1 | 3 | 8 | The same item hashes to the same value, so the state does not change. |
| 4 | a later visitor | 00000001... | 7 | 7 | 128 | A one in a hundred and twenty-eight pattern appears, so the estimate jumps. |
The natural guess is that an exact distinct count needs every identifier, so the only way round it is a bigger machine or a disk. That does not remove the requirement; it postpones it. A hundred million identifiers at sixteen bytes each is 1.6 billion bytes, about 1.6 gigabytes, and a billion is sixteen gigabytes. Accepting an estimate changes the requirement: memory is then set by the error you want, not by how many visitors arrive.
Averaging many small counters
One counter has enormous variance, so split the hash instead of hashing many times. Take the first fourteen bits as a bucket number, giving buckets, and use the rest of the hash for the leading-zero count inside that bucket. Each bucket sees roughly one sixteen-thousandth of the distinct visitors and holds one small integer. Combining the buckets with a harmonic mean, rather than an arithmetic mean, stops one wild bucket from dominating, and a constant correction removes the remaining bias.
The error depends on the number of buckets and not on the number of visitors. With 16384 buckets and c about 1.04, , which is 0.8125 per cent, rounded to about one per cent. At six bits per register, 16384 registers need bits, and bytes, about twelve kilobytes. That size stays twelve kilobytes whether the stream has a thousand distinct visitors or a billion.
| buckets | error, per cent, rounded | bytes of state | |
|---|---|---|---|
| 256 buckets | 256 | 7 | 192 |
| 1024 buckets | 1024 | 3 | 768 |
| 4096 buckets | 4096 | 2 | 3072 |
| 16384 buckets | 16384 | 1 | 12288 |
| 262144 buckets | 262144 | 0 | 196608 |
The estimate stops being acceptable when the number is used as an account rather than a judgement. A bill, a regulatory return, or a contractual commitment cannot be based on a number that is sometimes one per cent high. A threshold that sits within the error is also a bad fit: if the limit is ten thousand and the estimate is ten thousand and fifty with a one per cent error, the reading does not tell you whether the limit was crossed. Small counts are the third exception, because below a few thousand distinct visitors most buckets are empty and the estimate is at its least reliable, while an exact structure costs almost nothing.
There is one more property worth knowing before choosing this for daily unique visitors. Sketches merge: the union of two sketches is the element-wise maximum of their registers, which is the state you would have reached by processing both streams together. So hourly sketches can be rolled up into a daily or monthly distinct estimate without rereading events, and an exact distinct count cannot do that. Report the error beside the number; a one per cent estimate is useful for a judgement and unacceptable for an invoice.
Worth keeping
- An exact distinct count must remember every value it has seen, so its memory grows with the number of visitors.
- An estimate within about one per cent needs roughly twelve kilobytes, and that size does not depend on how many visitors arrive.
- Duplicates cost nothing in a hash-based sketch because the same identifier always produces the same hash.
Where this goes next
This question is one step of Counting Things as They Arrive, a course that works through its subject from the beginning. The page that teaches this part of it is Close Enough, in Far Less Room, and it reads the whole way through whether or not you have an account.
What an account changes is the practice: problems standing under every idea, marked with a reason rather than a tick, in a copy of the course that is yours.
Read that pageThe whole courseOther questions people ask about this
- Why does my count come out doubled after the job restarts?Your count doubled because the restart replayed events the saved counter already held: the state and input position were not saved as one snapshot.
- Why can't I just average the old average with the new number to get a running average?Averaging the old average with the new number gives that single new value half the weight, when it deserves one share out of however many values came before.
- Should I use a sliding window or a fixed window?Use a fixed window when the question names a period that must add up, and a sliding one when it asks about the recent past as of now. Questions about episodes want neither.
- How does a streaming system know when a window is finished?It cannot know. It guesses: it takes the latest event time it has seen, subtracts an allowance you choose, and treats every window ending before that point as finished.
- Why is the date off by one day after I save it and read it back?You saved a calendar date as if it were a precise moment, so showing it in another time zone can move it to the day before or after.
- Is UTF-8 the same thing as Unicode, or what's the difference?No: Unicode assigns a number to each character, while UTF-8 is a way of writing those numbers as bytes, using one to four bytes per character.