A deep dive into approximation algorithms for massive data streams,
featuring the elegant CVM algorithm for counting distinct elements.
Based on: Chakraborty, Vinodchandran, and Meel. "Distinct Elements in Streams: An Algorithm for the (Text) Book." ESA 2022.
Why can't we just store everything?
Imagine you're an engineer at Hotstar during an IPL final...
A data stream is a sequence \(\langle a_1, a_2, \ldots, a_m \rangle\) where:
Compute statistics about the stream using space \(O(\text{polylog}(m, n))\) — exponentially less than storing everything!
Key trade-off: We sacrifice exactness for space efficiency. We compute approximate answers with high probability.
Here's a taste of what we can solve efficiently in the streaming model:
| Problem | Question | Application |
|---|---|---|
| Heavy Hitters | Which items appear most frequently? | Trending hashtags, DDoS detection |
| Quantiles | What's the median? 95th percentile? | Latency monitoring, salary analysis |
| Frequency Moments | How "spread out" is the data? | Entropy estimation, anomaly detection |
| Distinct Elements | How many unique items? | Unique visitors, cardinality estimation |
Find all elements that appear more than \(m/k\) times in a stream of length \(m\).
k−1 = 3 slots to track heavy hitters
The common thread across all streaming algorithms:
An algorithm provides an \((\varepsilon, \delta)\)-approximation for quantity \(Q\) if:
$$\Pr\left[ (1-\varepsilon)Q \leq \hat{Q} \leq (1+\varepsilon)Q \right] \geq 1 - \delta$$where \(\hat{Q}\) is the algorithm's estimate.
How close are we to the true answer?
Smaller \(\varepsilon\) = more accurate.
How likely is the guarantee to hold?
Smaller \(\delta\) = more confident.
Typical setting: \(\varepsilon = 0.01\) (1% error), \(\delta = 0.01\) (99% confidence)
The \(F_0\) problem and the elegant CVM algorithm
Given a stream \(\langle a_1, a_2, \ldots, a_m \rangle\) with elements from \([n]\),
estimate \(F_0\) = the number of distinct elements in the stream.
Given a stream \(\langle a_1, a_2, \ldots, a_m \rangle\) with elements from \([n]\),
estimate \(F_0\) = the number of distinct elements in the stream.
The naïve approach: Store all elements seen so far.
Space needed: \(O(F_0 \cdot \log n)\) bits — potentially \(O(n)\)!
Can we do exponentially better?
If we pick every ball in a bin with probability \(p\) and end up with \(k\) balls in our bucket, then \(k/p\) is a good estimate of the number of balls in the bin.
Repeated elements inflate \(|\mathcal{B}|\) — elements appearing more often are more likely to be sampled!
Each distinct element \(x\) is in \(\mathcal{B}\) with probability exactly \(p\), independent of how many times it appeared.
Sample each element with probability \(p\). If we see \(|S|\) distinct elements in our sample,
estimate \(F_0 \approx |S| / p\).
Analysis: Let \(X_i = 1\) if the \(i\)-th distinct element is sampled.
To get a \((1 \pm \varepsilon)\)-approximation, we need \(p \cdot F_0 = \Omega(1/\varepsilon^2)\).
But we don't know \(F_0\) in advance — that's what we're trying to compute!
Start with \(p = 1\) (keep everything). When the sample gets too large, subsample by half and halve \(p\).
At all times, every distinct element seen so far is in our sample independently with probability \(p\).
Why does subsampling preserve the invariant?
This is the essence of the CVM algorithm (Chakraborty-Vinodchandran-Meel, ESA 2022).
When element \(a\) arrives and \(a \in X\), why do we remove it before possibly re-adding?
If we just kept \(a\) when it's already in \(X\), the probability that \(a\) is in \(X\) would depend on how many times we've seen \(a\).
This breaks the independence property!
By always "re-rolling" — removing and then adding with probability \(p\) — we ensure:
Each distinct element is in \(X\) with probability exactly \(p\), independent of history.
This is the key to the simple analysis!
Stream (remaining):
Buffer X (max size s):
We need to prove two things:
With buffer size \(s = O\left(\frac{1}{\varepsilon^2} \log \frac{1}{\delta}\right)\), the CVM algorithm:
Key tools:
At all times during the algorithm, for the current values of \(X\) and \(p\):
Each distinct element seen so far is in \(X\) independently with probability \(p\).
Proof by induction:
Given the invariant, \(|X|\) is the sum of \(F_0\) independent Bernoulli(\(p\)) random variables.
\(\mathbb{E}[|X|] = p \cdot F_0\)
For sum of independent Bernoulli r.v.s with mean \(\mu\):
$$\Pr[|X - \mu| > \varepsilon \mu] < 2\exp\left(-\frac{\varepsilon^2 \mu}{3}\right)$$At the end, \(\mu = p \cdot F_0\). We want the estimate \(|X|/p\) to be within \((1 \pm \varepsilon)F_0\).
This happens when \(|X|\) is within \((1 \pm \varepsilon) \cdot p \cdot F_0\).
Failure probability: \(2\exp\left(-\frac{\varepsilon^2 \cdot p \cdot F_0}{3}\right)\)
If \(p \cdot F_0 \geq \frac{3}{\varepsilon^2} \ln(2/\delta)\), failure probability \(\leq \delta\). ✓
We need \(p \cdot F_0\) to be large enough for accuracy. But if it's too large, the buffer overflows!
After subsampling, \(\mathbb{E}[|X|] = p \cdot F_0\).
The buffer size \(s\) is chosen so that with high probability, \(|X|\) stays close to its expectation.
If \(s = c \cdot \frac{1}{\varepsilon^2} \log(1/\delta)\) for suitable constant \(c\):
A union bound over all \(O(\log m)\) possible values of \(p\) gives the final guarantee.
The CVM algorithm with buffer size
$$s = O\left(\frac{1}{\varepsilon^2} \log\frac{1}{\delta} + \log n\right)$$uses \(O(s \cdot (\log n + \log m))\) bits of space and returns an estimate \(\hat{F}_0\) such that with probability at least \(1 - \delta\):
$$(1 - \varepsilon)F_0 \leq \hat{F}_0 \leq (1 + \varepsilon)F_0$$Each dot represents one run of CVM. The horizontal band shows the \((1 \pm \varepsilon)\) range.
Context, applications, and what's next
The distinct elements problem has a rich 40-year history:
| Year | Algorithm | Key Innovation |
|---|---|---|
| 1983 | Flajolet-Martin | Hash + count trailing zeros |
| 1996 | AMS (Alon-Matias-Szegedy) | Frequency moments framework |
| 2007 | HyperLogLog | Near-optimal, practical |
| 2010 | Kane-Nelson-Woodruff | Optimal space lower bound |
| 2022 | CVM | Simple + teachable + near-optimal |
CVM doesn't improve the space bound, but it simplifies the algorithm and proof dramatically. Donald Knuth called it "a real gem."
Where are these algorithms actually used?
Open-source library implementing streaming algorithms: HLL, KLL (quantiles), Theta (set operations), Frequent Items, and more.
Streaming algorithms connect to many areas of TCS:
Lower bounds for streaming often come from communication complexity.
Example: \(F_0\) needs \(\Omega(\log n)\) bits — proved via indexing!
Random projections, JL lemma, compressed sensing — all related ideas.
Testing properties with sublinear samples. Related query models.
Making irrevocable decisions with limited information.
Streaming is an active research area. Some open directions:
What if we can make 2 or 3 passes over the data? Can we do better?
Only count elements from the last \(W\) time steps. The "forgetting" makes things harder!
Data arrives at multiple sites. How do we coordinate with minimal communication?
What if the stream is chosen adversarially based on our random bits? Differential privacy helps!
References:
Let's discuss!
Slides available at: [your URL here]
Jupyter notebook with implementations: streaming_algorithms.ipynb