โ† back to journal

Bigger Sketch, Same Miss

2026-10-08 ยท Kit Wickham

HyperLogLog counts distinct items in a fixed, small amount of memory: m registers, each remembering the longest run of leading zeros among the hashes that landed in it. Its error is usually quoted as one number, 1.04/โˆšm, which is 0.81% for the 16,384 registers Redis uses. That number is the limit for large counts. I measured the error across the whole range, from one item to 64 times the register count, for the original estimator and the four fixes that came after it.

The short version: the original 2007 estimator has a spike just above 2.5m, where its error jumps to about 2.5% no matter how many registers you give it. For 16,384 registers that's 3.1 times the advertised error; for 65,536 it's 6.2 times. The later fixes remove it, and the best one, Otmar Ertl's 2017 estimator, needs no tables at all. Up to about 5m, the good ones actually beat the advertised number.

The estimators

Each test was 10,000 independent runs per register count (1,024, 4,096, 16,384 and 65,536), each adding up to 64m distinct items and recording the estimates at several hundred checkpoints along the way. Hashes were ideal 64-bit random values. Two checks before trusting it: linear counting's spread matched Whang's closed form within 3%, and 200 runs that hashed actual strings with SHA-1 (4,096 registers) were indistinguishable from 200 ideal runs: for Ertl's estimator the ratio of their spreads stayed between 0.87 and 1.13 at every count from 64 up, and their means differed by more than two standard errors at 2.5% of checkpoints, about what chance gives.

Left: RMS error divided by the advertised 1.04 over root m, against the true count from m/64 to 64m on a log axis, for 16,384 registers. All four estimators sit near 0.7 below m and rise to 1.0 at 64m. The original HLL spikes to 3.1 just above 2.4m and falls back by about 3.5m. The Redis 3.2 patch rises to 1.12 just below 2.4m, then drops. HLL++ and Ertl stay at or under 1.0 everywhere. Right: peak RMS error between m and 5m for 1K, 4K, 16K and 64K registers. Original HLL: 4.3, 2.9, 2.5, 2.5 percent. Ertl: 2.9, 1.5, 0.73, 0.37 percent, just under the advertised line of 3.2, 1.6, 0.81, 0.41.
Left: 10,000 runs per point, 16,384 registers. LogLog-Beta is left off because it sits on top of Ertl. Right: the peak RMS error anywhere between m and 5m, by sketch size.

The spike, and why more registers don't help

The raw estimate is badly biased upward at small counts, which is why the original switches to linear counting. The trouble is where it switches back. Measured as a fraction of the true count, the raw estimate's bias is:

true countm2m2.5m3m4m5m
raw estimate bias+31.6%+5.5%+2.5%+1.0%+0.2%0.0%

Same to within 0.1 points at all four sizes.

So the original hands over to the raw estimate while it's still about 2.5% high. Because the switch is on the estimate rather than the true count, it happens at a true count of about 2.43m, and from there almost every run reports the biased raw number until the bias fades out past 3m. The bias is a fixed fraction of the count, the same for every sketch size, while the advertised error shrinks with โˆšm. More registers make the rest of the curve better and leave the spike where it was:

registersadvertisedoriginal, peakErtl, peak
1,0243.25%4.27% (1.3ร—)2.93%
4,0961.62%2.87% (1.8ร—)1.46%
16,3840.81%2.55% (3.1ร—)0.73%
65,5360.41%2.50% (6.2ร—)0.37%

Peak RMS error for true counts between m and 5m. For the original the peak is at about 2.43m to 2.48m, at the two larger sizes almost all of it bias; for Ertl it's near 5m, on the way up to the advertised value.

In counts: with 16,384 registers, every true count from about 39,800 to 48,300 has more than 1.5 times the advertised error, and at 40,700 the 99th-percentile miss is 3.9%. It's bias, not noise: at that count the sketch reads high in nearly every run.

The fixes

At 16,384 registers, Redis's polynomial patch took the worst error from 3.1ร— down to 1.12ร— the advertised value, with a small leftover bump just before the switch. LogLog-Beta with Redis's coefficients, my refit LogLog-Beta, HLL++ with my bias table, and Ertl's estimator all peaked at 1.00ร—, and only at 45m, where every estimator converges to the advertised value anyway. The other sizes look the same. At 16,384 registers their largest bias anywhere from m/64 to 64m: Ertl 0.016%, LogLog-Beta 0.03%, HLL++ 0.09%.

Two details I didn't expect:

And the good news hidden in the left chart: below m, every estimator except the uncorrected raw one runs at 0.66 to 0.83 times the advertised error. Small counts are the easy part.

What I'd use

Ertl's estimator. It's about twenty lines that run on a histogram of the register values, has no tables or thresholds to get right for each sketch size, and was the most accurate of everything I tested. If you're using a library, check which estimator it implements: anything that switches at 2.5m with no bias correction will be about 2.5% high for counts a little over 2.4 times its register count, however big you make it.

Limits: ideal hashing with 64-bit hashes, so no 32-bit large-range correction, and registers wide enough never to overflow; the real-hash check covered one size. I didn't test Ertl's maximum-likelihood estimator or HLL++'s sparse mode, which stores small sets exactly and does better than any of these at tiny counts. My HLL++ bias table is built the paper's way but isn't Google's.

Try it. The sketch sizer turns these curves into a table: give it the range of counts you expect and a target error, and it shows the worst error each register count really gets over that range, for the original estimator or a bias-corrected one.