Numbers at scale

The Elasticsearch essays · February 2026

Search asks "which documents match?" Analytics asks something else entirely: summarize a field across millions of documents. This essay is about the second question: the columnar layout that makes it cheap, the bucketing engine behind every dashboard, and the beautiful algorithm that trades a sliver of accuracy for almost all of the memory.

The inverted index is the wrong tool for that: it maps terms to documents, so reading one field across millions of documents would mean hopping all over the disk. This is what doc values are for, every field stored again as its own contiguous column:

–
cells touched, reading documents
–
cells touched, reading the column
–
extra work, documents vs column
5
Same question, avg(cpu). The document read is shown; run the column read to compare, then drag the fields slider: as documents get wider, the document read's waste grows without bound, while the column read doesn't care how wide the documents are. Real log documents have hundreds of fields; imagine the slider at 400.

On columns, Elasticsearch builds aggregations: sort documents into buckets, by time, by term, by range, and compute metrics inside each. It's a histogram engine living inside the search engine, distributed like everything else. Here are 240 log events, bucketed live:

10m
Drag the interval: the same events regroup live. This is a date_histogram with a terms sub-aggregation, the query behind nearly every dashboard.

Some summaries, though, resist this treatment. "How many distinct users?" seems to require remembering every user you've seen, unbounded memory. Elasticsearch's cardinality aggregation refuses to pay that and uses HyperLogLog instead: hash every value and remember only the most surprising hash patterns seen, in a small, fixed amount of memory. Feed it:

0
events seen
0
true distinct users
0
HyperLogLog estimate
–
error
exact set memory
sketch memory
256 bytes, forever
A real 256-register HyperLogLog running in your browser; the slider sweeps from 100 to 1,000,000 distinct users, logarithmically. Drag it slowly: the exact set's memory grows five thousand-fold; the sketch stays at 256 bytes and its error stays around the theoretical ±6.5%, no matter the scale. That indifference to scale is the whole trick. Elasticsearch runs the HyperLogLog++ variant with many more registers: about precision_threshold × 8 bytes (24 KB at the default 3,000), counting near-exactly up to the threshold and within a few percent beyond it.

Percentiles get the same treatment as counting. "What is the p99 latency?" seems to require keeping every value and sorting; the t-digest keeps a bounded set of centroids instead: small, precise clusters near the tails where precision matters, big coarse ones in the middle where it doesn't. One knob trades memory for accuracy:

–
centroids kept
–
estimated p99 (ms)
–
error vs exact
100
50,000 latency samples summarized. Each dot is a centroid, placed by the share of data it summarizes and sized by how many samples it holds: tiny and crowded at both tails, where p1 and p99 live, big and sparse in the boring middle. Drag compression down to 10 and the estimate wobbles; at 300 it is near-exact for a few kilobytes. The default is 100, and now you know what that number buys.

This is a deliberate philosophy: at sufficient scale, a fast answer that is 99% right beats an exact answer you cannot afford.

Significant, not just popular

Every aggregation so far counts what is common. But "common" is usually boring: the most frequent user-agent among your errors is Chrome, because Chrome is the most frequent user-agent, period. The interesting question is what is common in your errors compared to everything else. That is the significant-terms aggregation, and it is a different animal entirely:

Trading certainty for speed, explicitly

The approximations so far, HyperLogLog and t-digest, are baked into their aggregations. There is also a knob that approximates any aggregation: run it on a random sample of matching documents and scale up. The bargain is lopsided in your favor: the speedup grows with the sampling rate, the error only with its square root:

–
speedup
–
typical error of the estimate
One hundred runs of the same average-latency aggregation on 10 million documents, each dot one run on a fresh random sample. At 100% the answer is exact and slow. At 1%, a hundred times faster, and the cloud of answers still hugs the true line: sampling error shrinks with √n, and a 1% sample of ten million is still a hundred thousand documents. Exploratory dashboards over huge datasets have no business being exact. (The random_sampler aggregation accepts probabilities below 50%, or exactly 100%; the speedup is close to linear on large datasets.)

One kind of numeric data is so extreme, so relentless, that it outgrew even columns: metrics. That story has its own essay.