91 captures
28 Jan 2018 - 02 Mar 2026
Apr MAY Jun
25
2021 2022 2023
success
fail

About this capture

COLLECTED BY

Collection: Common Crawl

Web crawl data from Common Crawl.
TIMESTAMPS

The Wayback Machine - http://web.archive.org/web/20220525152039/https://odino.org/my-favorite-data-structure-hyperloglog/
 



Home

About

Conferences

Archives

RSS
 

My favorite algorithm (and data structure): HyperLogLog

   
Every now and then I bump into a concept thats so simple and powerful that I want to stab my brain for missing out on such an incredible and beautiful idea.

I discovered HyperLogLog (HLL) a couple years ago and fell in love with it right after reading how redis decided to add a HLL data structure: the idea behind HLL is devastatingly simple but extremely powerful, and its what makes it such a widespread algorithm, used by giants of the internet such as Google and Reddit.

So, whats your phone number?


My friend Tommy and I planned to go to a conference and, while heading to its location, decide to wager on who will talk to the most strangers. So once we reach the place we start conversing around and keep a counter of how many people we talk to.


At the end of the event Tommy comes to me with his figure (17) and I tell him that I had a word with 46 people: I clearly am the winner, but Tommys frustrated as he thinks Ive counted the same people multiple times, as he only saw me with 15/20 people in total. So, the wagers off and we decide that, for our next event, well be taking down names instead, so that were sure were going to be counting unique people, and not just the total number of conversations.

At the end of the following conference we enthusiastically meet each other with a very long list of names and, guess what, Tommy had a couple more encounters than I did! We both laugh it off and while discussing our approach to counting uniques, Tommy comes up with a great idea:

Alex, you know what? We cant go around with pen and paper and track down a list of names, its really impractical!
Today I spoke to 65 different people and counting their names on this paper was a real pain in the backI lost count 3 times and had to start from scratch!

Yeah, I know, but do we even have an alternative?

What if, for our next conference, instead of asking for names, we ask people the last 5 digits of their phone number?
Now, follow me: instead of winning by counting their names, the winner will be the one who spoke to someone with the longest sequence of leading zeroes in those digits.

Wait Tommy, youre going too fast! Slow down a second and give me an example

Sure, just ask people for those last 5 digits, ok? Lets suppose you get 54701.
No leading zero, so the longest sequence of zeroes for you is 0.
The next person you talk to tells you its 02561  thats a leading zero! So your longest sequence comes to 1.

Youre starting to make sense to me

Yeah, so if we speak to a couple people, chances are that are longest zero-sequence will be 0. But if we talk to ~10 people, we have more chances of it being 1.
Now, imagine you tell me your longest zero-sequence is 5  you must have spoken to thousands of people to find someone with 00000 in their phone number!

Dude, youre a damn genius!

And that, my friends, is how HyperLogLog fundamentally works: it allows us to estimate uniques within a large dataset by recording the longest sequence of zeroes within that set. This ends up creating an incredible advantage over keeping track of each and every element in the set, making it an incredibly efficient way to count unique values with relatively high accuracy:

The HyperLogLog algorithm can estimate cardinalities well beyond 10^9 with a relative accuracy (standard error) of 2% while only using 1.5kb of memory.


Since this is the usual me oversimplifying things that I find hard to understand, lets have a look at some more details of HLL.

More HLL details


HLL is part of a family of algorithms that aim to address cardinality estimation, otherwise known as count-distinct problem, which are extremely useful for lots of todays web applications  for example when you want to count how many unique views an article on your site has generated.

When HLL runs, it takes your input data and hashes it, turning into a bit sequence:

1
2
3
IP address of the viewer: 54.134.45.789

HLL hash: 010010101010101010111010...

Now, an important part of HLL is to make sure that your hashing function distributes bits as evenly as possible, as you dont want to use a weak function such as:

1
2
3
4
5
6
7
8
9
function hash(ip) {
  let h = ''

  ip.replace(/\D/g,'').split('').forEach(number => {
    h += number < 5 ? 0 : 1
  })

  return h
}

A HLL using this hashing function would return biased results if, for example, the distribution of your visitors is tied to a specific geographic region.

The original paper has a few more details on what a good hashing function means for HLL:

All known efficient cardinality estimators rely on randomization, which is ensured by the use of hash functions.
The elements to be counted belonging to a certain data domain D, we assume given a hash function, h : D  {0, 1}; that is, we assimilate hashed values to infinite binary strings of {0, 1}, or equivalently to real numbers of the unit interval.
[]
We postulate that the hash function has been designed in such a way that the hashed values closely resemble a uniform model of randomness, namely, bits of hashed values are assumed to be independent and to have each probability [0.5] of occurring.


Now, after weve picked a suitable hash function we need to address another pitfall: variance.

Going back to our example, imagine that the first person you talk to at the conference tells you their number ends with 00004  jackpot! You might have won a wager against Tommy, but if you use this method in real life chances are that specific data in your set will negatively influence the estimation.

Fear no more, as this is a problem HLL was born to solve: not many are aware that Philippe Flajolet, one of the brains behind HLL, was quite involved in cardinality-estimation problems for a long time, long enough to have come up with the Flajolet-Martin algorithm in 1984 and (super-)LogLog in 2003, which already addressed some of the problems with outlying hashed values by dividing measurements into buckets, and (somewhat) averaging values across buckets.

If you got lost here, let me go back to our original example: instead of just taking the last 5 digits of a phone number, we take 6 of them and store the longest sequence of leading zeroes together with the first digit (the bucket). This means that our data will look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Input:
708942 --> in the 7th bucket, the longest sequence of zeroes is 1
518942 --> in the 5th bucket, the longest sequence of zeroes is 0
500973 --> in the 5th bucket, the longest sequence of zeroes is now 2
900000 --> in the 9th bucket, the longest sequence of zeroes is 5
900672 --> in the 9th bucket, the longest sequence of zeroes stays 5

Buckets:
0: 0
1: 0
2: 0
3: 0
4: 0
5: 2
6: 0
7: 1
8: 0
9: 5

Output:
avg(buckets) = 0.8

As you see, if we werent employing buckets we would instead use 5 as the longest sequence of zeroes, which would negatively impact our estimation: even though I simplified the math behind buckets (its not just a simple average), you can totally see how this approach makes sense.

Its interesting to see how Flajolet addresses variance throughout his works:

While weve got an estimate thats already pretty good, its possible to get a lot better. Durand and Flajolet make the observation that outlying values do a lot to decrease the accuracy of the estimate; by throwing out the largest values before averaging, accuracy can be improved.
Specifically, by throwing out the 30% of buckets with the largest values, and averaging only 70% of buckets with the smaller values, accuracy can be improved from 1.30/sqrt(m) to only 1.05/sqrt(m)! That means that our earlier example, with 640 bytes of state and an average error of 4% now has an average error of about 3.2%, with no additional increase in space required.
Finally, the major contribution of Flajolet et al in the HyperLogLog paper is to use a different type of averaging, taking the harmonic mean instead of the geometric mean we just applied. By doing this, theyre able to edge down the error to 1.04/sqrt(m), again with no increase in state required.

HLL in the wild


So, where can we find HLLs? Two great web-scale examples are:


BigQuery, to efficiently count uniques in a table (APPROX_COUNT_DISTINCT())

Reddit, where its used to calculate how many unique views a post has gathered


In particular, see how HLL impacts queries on BigQuery:

1
2
3
4
5
6
7
SELECT COUNT(DISTINCT actor.login) exact_cnt
FROM `githubarchive.year.2016`
> 6,610,026 (4.1s elapsed, 3.39 GB processed, 320,825,029 rows scanned)

SELECT APPROX_COUNT_DISTINCT(actor.login) approx_cnt
FROM `githubarchive.year.2016`
> 6,643,627 (2.6s elapsed, 3.39 GB processed, 320,825,029 rows scanned)

The second result is an approximation (with an error rate of ~0.5%), but takes a fraction of the time.

Long story short: HyperLogLog is amazing! You now know what it is and when it can be used, so go out and do incredible stuff with it!

Just before you leave


One thing Id like to clarify is that even though Ive referred to HLL as a data structure before, it should be noted that it is an algorithm first, while some databases (eg. Redis, Riak, BigQuery) have implemented their own data structures based on HLL (so while saying HLL is a data structure is technically incorrect, its also not entirely wrong).

Further readings



HyperLogLog on Wikipedia

the original paper

HyperLogLog++, Googles improved implementation of HLL

Redis new data structure: the HyperLogLog

Damn Cool Algorithms: Cardinality Estimation

HLL data types in Riak

HyperLogLog and MinHash



In the mood for some more reading?  



MySQL features I cant wait for them to happen (07 May 2022)

Dell XPS 13 9310: USB-C port not recognizing external devices (25 July 2021)

Book review: Working Backwards (24 July 2021)

fwupd is the best thing that ever happened to Linux (15 April 2021)

Avoid battery draining on your Linux-flavored Dell XPS (31 January 2021)

Combining two numbers into a unique one: pairing functions (05 December 2020)

Running CI tests in Kubernetes through Github Actions (20 March 2020)
 

...or check the archives.