Combining Bucket and Metric Aggregation Correctly
Anyone using Elasticsearch only for full text search is leaving most of its analytical power on the table. Bucket aggregations group documents by field values, value ranges or time intervals, while metric aggregations compute concrete numbers such as average, sum or minimum from those groups. Only when both aggregation types work together do you get the dashboards, facets and reports that separate a production search application from a simple keyword search.
Table of Contents
- 1. What Aggregations Actually Do in Elasticsearch
- 2. Bucket Aggregations: Grouping Documents
- 3. Metric Aggregations: Computing Values
- 4. Nesting Aggregations: Combining Sub-Aggregations
- 5. The Terms Aggregation in Detail: size and order
- 6. Range and Histogram Aggregation for Distributions
- 7. Performance: doc_values and Filter Context
- 8. Common Mistakes When Combining Bucket and Metric
- 9. Bucket vs. Metric Aggregation Compared
- 10. Summary
- 11. FAQ
1. What Aggregations Actually Do in Elasticsearch
An aggregation in Elasticsearch is an analytics framework that computes summarized data from the currently queried set of documents instead of returning individual hits. Coming from a relational background, you can think of aggregations as a combination of GROUP BY and aggregate functions like COUNT, AVG or SUM, with the crucial difference that Elasticsearch computes the result across distributed shards in near real time, even over billions of documents. Aggregations run in the same request as a normal search and can be returned alongside hits, which unifies filtering and analysis in a single call.
The Elasticsearch documentation distinguishes three categories of aggregations: bucket aggregations, metric aggregations and pipeline aggregations. A bucket aggregation creates one or more containers, called buckets, into which documents are sorted based on a criterion, such as the value of a field or a value range. A metric aggregation, on the other hand, computes a numeric value across a set of documents, for example the average price or the total sum. Pipeline aggregations further process the output of other aggregations, for example moving averages, but stay on the margin in this article because the focus is on the fundamental interplay between bucket and metric aggregation.
2. Bucket Aggregations: Grouping Documents
A bucket aggregation answers the question: which groups does my document set fall into? The best known variant is the terms aggregation, which creates one bucket per unique value of a field and returns the number of matching documents as doc_count. Beyond that, there is the range aggregation for custom value ranges, the histogram aggregation for equally sized numeric intervals, the date histogram aggregation for time windows, and the filters aggregation for named, arbitrarily complex filter criteria. Every bucket aggregation ultimately returns a list of buckets, where each bucket contains at least a key and a document count.
The practical benefit of a bucket aggregation shows up in faceted navigation and reporting: a terms aggregation on the field brand.keyword returns, in a single request, the list of all brands in the current search result together with their hit count, without the application having to count anything itself. It is important that terms aggregations only work on fields with doc_values, typically keyword, numeric or date fields, not on analyzed text fields without a matching sub-field.
POST /products/_search
{
"size": 0,
"query": {
"match": { "category": "notebooks" }
},
"aggs": {
"brands": {
"terms": {
"field": "brand.keyword",
"size": 10
}
}
}
}
// Response excerpt:
// "aggregations": {
// "brands": {
// "doc_count_error_upper_bound": 0,
// "sum_other_doc_count": 12,
// "buckets": [
// { "key": "Dell", "doc_count": 340 },
// { "key": "Lenovo", "doc_count": 298 },
// { "key": "HP", "doc_count": 251 }
// ]
// }
// }
3. Metric Aggregations: Computing Values
A metric aggregation reduces a set of documents to one or more numeric values. Single-value metrics like avg, sum, min, max and cardinality return exactly one value. Multi-value metrics like stats and extended_stats return several related numbers in one call, for example minimum, maximum, average, sum and document count at once. In practice that saves multiple requests, because stats returns exactly the values a typical price filter widget needs.
Unlike bucket aggregations, a metric aggregation does not create new buckets itself. It either runs at the top level of a query and then returns a single global metric across all hits, or it is placed as a sub-aggregation inside a bucket and then returns a metric per bucket. That nesting is exactly the core of productive Elasticsearch analytics and is explored in the next section.
POST /products/_search
{
"size": 0,
"aggs": {
"price_stats": {
"stats": { "field": "price" }
},
"unique_brands": {
"cardinality": { "field": "brand.keyword" }
}
}
}
// Response excerpt:
// "aggregations": {
// "price_stats": {
// "count": 4210, "min": 19.99, "max": 2499.00,
// "avg": 412.37, "sum": 1736078.70
// },
// "unique_brands": { "value": 47 }
// }
4. Nesting Aggregations: Combining Sub-Aggregations
The real value emerges when a bucket aggregation and a metric aggregation are nested inside each other. Every bucket aggregation accepts its own aggs block, which can contain further bucket or metric aggregations to any depth. That makes it possible to answer, in a single request: how many products exist per brand, and what is the average price for each one? Without nesting, the application would have to send a separate filtered request per brand, which is not practical with hundreds of brands.
Nesting depth is technically not limited, but in practice bounded by the combinatorics of buckets: three nested terms aggregations with size: 20 each can produce up to 8000 buckets in the worst case, each with its own sub-metrics. Elasticsearch computes such nested bucket aggregations and metric aggregations in a single scan of the relevant documents per shard, which is considerably more efficient than multiple sequential requests, but still requires significant memory and CPU on data nodes at high cardinality and deep nesting.
POST /products/_search
{
"size": 0,
"aggs": {
"by_brand": {
"terms": { "field": "brand.keyword", "size": 20 },
"aggs": {
"avg_price": { "avg": { "field": "price" } },
"by_category": {
"terms": { "field": "category.keyword", "size": 5 },
"aggs": {
"total_stock": { "sum": { "field": "stock_qty" } }
}
}
}
}
}
}
// Three-level nesting: brand -> category -> stock sum
// Each bucket carries its own metric sub-aggregation
5. The Terms Aggregation in Detail: size and order
The size parameter determines how many buckets the terms aggregation returns after the shard-level merge, ten by default. If size is chosen too small, relevant but rarer values get lost; if it is chosen too large, memory consumption on data nodes rises noticeably, because each shard internally has to hold more candidate buckets before the final merge happens on the coordinating node. The sum_other_doc_count field in the response shows how many documents sit in buckets that were not returned, an important signal for whether size was chosen large enough.
The order parameter controls the criterion buckets are sorted by, descending by doc_count by default. Alternatively you can sort by the bucket key itself ({ "_key": "asc" }) or, particularly useful combined with sub-aggregations, by the result of a nested metric aggregation, for example to find the ten brands with the highest average price. This is exactly where the tight coupling between bucket aggregation and metric aggregation becomes visible: the metric does not only determine the value inside a bucket, it can also control the selection of buckets itself.
6. Range and Histogram Aggregation for Distributions
While the terms aggregation creates a separate bucket for every discrete value, range and histogram aggregation are meant for continuous numeric distributions. The range aggregation defines explicit, named boundaries, for example price tiers from 0 to 50, 50 to 200 and above 200 euros, and is the right choice for business-defined categories, as commonly used in e-commerce price filter widgets. The histogram aggregation, in contrast, automatically creates equally wide intervals based on an interval parameter and is better suited to exploratory data analysis where the sensible boundaries are not known in advance.
Both bucket aggregations, just like the terms aggregation, can be combined with metric aggregations. A histogram aggregation with interval: 100 on the price field yields buckets for 0 to 100, 100 to 200 and so on; a nested avg aggregation then directly shows whether there are significant differences in another metric within each price band, for example the average rating score. The min_doc_count: 0 parameter additionally forces empty intervals to appear in the response too, which matters for gapless charts in frontend visualizations.
POST /products/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 50 },
{ "from": 50, "to": 200 },
{ "from": 200 }
]
},
"aggs": {
"avg_rating": { "avg": { "field": "rating" } }
}
},
"price_histogram": {
"histogram": {
"field": "price",
"interval": 100,
"min_doc_count": 0
}
}
}
}
// Range: named business buckets with own labels
// Histogram: fixed-width buckets, min_doc_count keeps empty ones
7. Performance: doc_values and Filter Context
Aggregations are only as fast as the data structure they operate on. Elasticsearch computes bucket and metric aggregations based on doc_values, a column-oriented data structure that is created by default at index time for all fields except analyzed text. Without doc_values, Elasticsearch would have to convert the entire inverted index into an in-memory structure called fielddata for every aggregation, which consumes heap memory and can lead to cluster instability on large fields. That is why every field that is regularly aggregated on should be mapped as keyword or a numeric type, if necessary via a keyword sub-field of a text field.
A second performance lever is the filter context. If an aggregation is scoped inside a query block using bool.filter instead of bool.must, relevance scoring for the underlying document set is skipped, and Elasticsearch can use the filter cache. Since aggregations do not need relevance scores anyway, the filtering query underneath a bucket or metric aggregation should almost always be formulated in filter context. For recurring aggregation requests with an identical filter base, for example on a category page, that noticeably reduces response time because the underlying document set comes from cache.
8. Common Mistakes When Combining Bucket and Metric
The most common mistake is trying to run a terms aggregation directly on an analyzed text field. Since text fields have no doc_values by default, the request either fails with an exception, or, if fielddata was manually enabled, triggers a slow and memory-intensive computation. The correct fix is a keyword multi-field in the mapping, so that both full text search on brand and aggregation on brand.keyword are possible.
// WRONG: mapping without keyword sub-field
PUT /products
{
"mappings": {
"properties": {
"brand": { "type": "text" }
}
}
}
// Aggregation on "brand" fails or falls back to slow fielddata
// RIGHT: multi-field mapping for search AND aggregation
PUT /products
{
"mappings": {
"properties": {
"brand": {
"type": "text",
"fields": {
"keyword": { "type": "keyword", "ignore_above": 256 }
}
}
}
}
}
// Search: match on "brand"
// Aggregate: terms on "brand.keyword"
A second common mistake is missing "size": 0 at the top level when only aggregation results are needed. Without that parameter, Elasticsearch additionally returns ten hit documents that unnecessarily burden network bandwidth and response time. A third mistake concerns nested metric aggregations on empty buckets: computing avg on a bucket with zero documents returns null, which, without an explicit check on the frontend, leads to display errors like "NaN" or empty chart bars.
9. Bucket vs. Metric Aggregation Compared
The following overview summarizes the key differences between bucket aggregation and metric aggregation and shows when each aggregation type is the right choice.
| Aspect | Bucket Aggregation | Metric Aggregation |
|---|---|---|
| Purpose | Split documents into groups | Compute a metric over a document set |
| Return value | List of buckets with key and doc_count | One or several numeric values |
| Examples | terms, range, histogram, filters |
avg, sum, stats, cardinality |
| Nesting | Can contain its own sub-aggregations | Terminal, creates no further buckets |
| Performance driver | Bucket count, size parameter, cardinality | Field type, doc_values availability |
| Typical use | Facet navigation, reporting groups | KPI widgets, price filter boundaries |
In practice the two aggregation types rarely stand alone. Almost every production faceted navigation combines a bucket aggregation for grouping with one or more metric aggregations to enrich each group. Once you internalize this relationship, you read every Elasticsearch query structurally faster: buckets answer "which groups exist", metrics answer "what do the numbers look like inside these groups".
Mironsoft
Elasticsearch and OpenSearch architecture, aggregations and search performance
Aggregations that stay fast even with millions of documents?
We review existing Elasticsearch mappings and aggregation queries, surface expensive bucket and metric combinations, and build faceted search that stays stable under load.
Mapping Review
Check doc_values, keyword sub-fields and aggregation readiness
Query Optimization
Adjust filter context, nesting depth and bucket sizes
Facet Design
Design faceted navigation and KPI widgets on top of aggregations
10. Summary
Bucket aggregation and metric aggregation are the two fundamental building blocks of Elasticsearch analytics. A bucket aggregation groups documents by field values, value ranges or time intervals and returns a document count for each group. A metric aggregation computes a numeric value over a document set, either globally or inside a single bucket. Only nesting both types as a sub-aggregation turns simple counting into real analysis: average price per brand, average rating per price band, stock quantity per category.
For stable performance, remember: aggregation fields must have doc_values, so they need to be mapped as keyword or numeric, and the underlying query should be formulated in filter context. The size and order parameters control how many buckets come back and in what order, where a size value chosen too large can quickly become a memory problem at high cardinality, a topic explored further in the next article on terms aggregation performance.
Bucket vs. Metric Aggregation, the Key Points at a Glance
Bucket Aggregation
Groups documents by a criterion, for example terms, range or histogram. Returns buckets with key and doc_count.
Metric Aggregation
Computes metrics like avg, sum or stats, globally or inside a bucket as a sub-aggregation.
Mapping Requirement
Aggregation fields need doc_values, so keyword or numeric type instead of plain analyzed text field.
Performance
Use filter context instead of must query, choose the size parameter deliberately, limit nesting depth.