Sketches and Sampling: Counting Big Streams in Small Memory

Algorithms
57 / 68

When a stream is too big to store, keep a small summary instead. HyperLogLog estimates distinct counts, a count-min sketch estimates frequencies, Misra-Gries finds the heavy hitters, a Bloom filter answers 'seen before?', and reservoir sampling keeps a fair random sample. Each trades a small, known error for fixed memory.

The Analogy (Read This First)

A pollster cannot interview a whole city, but a carefully chosen sample, plus a few tally sheets for the most common answers, is enough to report the result with a margin of error.

Deep Dive Analysis

Why summaries. Counting exactly how many different visitors saw a page needs a set of every visitor id, which grows with the audience. Counting every word's frequency needs a counter per word. On a stream of billions of events, memory and merging across machines are the real limits. A sketch has a fixed size, can be updated in one pass, and can be combined across servers.

HyperLogLog (distinct count). Hash every item to a random-looking bit string. If you have seen a hash that starts with k zeros, you have probably seen about 2^k distinct items, because that pattern is rare. One such guess is noisy, so the sketch splits items into m buckets by the first bits and keeps the longest run of zeros per bucket, then averages. The relative error is about 1.04 divided by the square root of m. With m = 16,384 buckets (about 12 KB) the error is about 0.8%, whether the stream holds a thousand or a billion distinct items. Two sketches merge by taking the larger value in each bucket.

Count-min sketch (frequency). Keep a small grid of counters with several rows, each row with its own hash function. To count an item, add one to one counter in every row. To estimate it, read its counter in every row and take the smallest. Collisions only ever add to a counter, so the estimate never undercounts and can overcount. More columns reduce the overcount; more rows reduce the chance of a bad one.

Misra-Gries (heavy hitters). To find items that make up more than 1/k of the stream, keep k minus 1 counters. If the item has a counter, add one. If there is a free counter, start one. Otherwise subtract one from every counter and drop those that reach zero. Any item that occurs more than n/k times is guaranteed to still be there at the end. The counts are lower bounds, so a second pass (or a count-min sketch) can confirm them.

def misra_gries(stream, k):
    counters = {}
    for x in stream:
        if x in counters:
            counters[x] += 1
        elif len(counters) < k - 1:
            counters[x] = 1
        else:
            for key in list(counters):
                counters[key] -= 1
                if counters[key] == 0:
                    del counters[key]
    return counters

stream = list('aabacabadaeaabaa')   # 16 items: 'a' appears 10 times
print(misra_gries(stream, k=3))      # {'a': 7}   'a' is above 16/3, so it survives (true count 10)

Bloom filter (seen before?). A bit array and k hash functions. Adding an item sets its k bits; checking an item says 'maybe present' only if all k of its bits are set, and 'definitely not present' otherwise. It can give false positives but never false negatives. With about 10 bits per item and 7 hash functions, the false positive rate is roughly 1%. A deduplication service uses one to skip the expensive lookup for items it has definitely never seen, and an LSM tree uses one to skip files that cannot hold a key.

Reservoir sampling (a fair sample). To keep k items chosen uniformly from a stream of unknown length, fill a reservoir with the first k. For the item at position i (counting from 1), pick a random number from 1 to i; if it is at most k, the item replaces a random slot in the reservoir. Every item ends up in the sample with probability exactly k/i at the end, and you never need to know the length ahead of time.

import random

def reservoir(stream, k, rng):
    sample = []
    for i, x in enumerate(stream, start=1):
        if i <= k:
            sample.append(x)
        else:
            j = rng.randint(1, i)
            if j <= k:
                sample[j - 1] = x
    return sample

rng = random.Random(1)
hits = [0] * 10
for _ in range(20000):
    for x in reservoir(range(10), 3, rng):
        hits[x] += 1
print([round(h / 20000, 2) for h in hits])   # every item lands near 3/10 = 0.3

Choosing. Use HyperLogLog for unique visitors, distinct users and cardinality in a database. Use a count-min sketch plus a small heap of the top items for trending topics. Use Misra-Gries when you want the heavy items with a guarantee and almost no memory. Use reservoir sampling for fair samples of logs or traces. In an interview, say the error is the price of the fixed memory and name how you would merge sketches from many servers.

Key Bullets
  • A sketch gives up exactness for fixed memory, one-pass updates and easy merging.
  • HyperLogLog counts distinct items from the longest run of leading zeros; error is about 1.04 over the square root of the bucket count.
  • A count-min sketch never undercounts; collisions can overcount.
  • Misra-Gries with k minus 1 counters keeps every item above n/k occurrences.
  • A Bloom filter has no false negatives; about 10 bits per item gives a false positive rate near 1%.
  • Reservoir sampling gives each item the same chance of being in the sample without knowing the length.
Trade-offs
HyperLogLog: tiny and mergeable | Only a count, you cannot list the itemsCount-min sketch: constant memory per stream | Overcounts, and needs extra structure to name the top itemsMisra-Gries: strong guarantee for heavy items | Lower-bound counts and nothing about light itemsBloom filter: tiny membership test | False positives, and items cannot be removed in the basic formReservoir sample: unbiased and simple | A fixed-size sample can miss rare events
Real-World Examples
Unique visitors per page across hundreds of web serversTrending hashtags in the last hourA fair sample of requests for debugging from a firehose of logs

Curated Curation & Deep Insights

ScaleDojo Certified
Video Tutorial Pending

Our system architects are vetting high-quality, authorized video guides for Sketches and Sampling: Counting Big Streams in Small Memory with zero third-party platform links.

Verification in Progress
Reading Material Pending

We are preparing premium, zero-competitor deep-dives for Sketches and Sampling: Counting Big Streams in Small Memory. Authorized reading references will appear automatically.

Verification in Progress