ELK Stack — Aggregations
Introduction
Aggregations are Elasticsearch’s analytics engine. While queries find documents, aggregations summarize, group, and compute statistics over your data. They power the charts, histograms, and dashboards you see in Kibana.
Elasticsearch supports three types of aggregations:
┌─────────────────────────────────────────────────────────────┐
│ Aggregation Types │
├───────────────────┬─────────────────────────────────────────┤
│ Metric │ Compute numbers from field values │
│ Aggregations │ avg, sum, min, max, stats, cardinality │
├───────────────────┼─────────────────────────────────────────┤
│ Bucket │ Group documents into buckets │
│ Aggregations │ terms, date_histogram, range, filters │
├───────────────────┼─────────────────────────────────────────┤
│ Pipeline │ Compute over other aggregation results │
│ Aggregations │ derivative, moving_avg, cumulative_sum │
└───────────────────┴─────────────────────────────────────────┘
The size: 0 Pattern
When you only need aggregation results (no individual documents), set size: 0 to skip the hits array entirely. This makes the query faster and the response smaller.
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"avg_response_time": {
"avg": { "field": "response_time" }
}
}
}'
{
"took": 2,
"hits": {
"total": { "value": 6, "relation": "eq" },
"max_score": null,
"hits": []
},
"aggregations": {
"avg_response_time": {
"value": 968.6333312988281
}
}
}
Metric Aggregations
Metric aggregations calculate a single numeric value from document field values.
avg Aggregation
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"avg_response": {
"avg": { "field": "response_time" }
}
}
}'
{
"aggregations": {
"avg_response": {
"value": 968.6333312988281
}
}
}
sum Aggregation
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"total_response_time": {
"sum": { "field": "response_time" }
}
}
}'
{
"aggregations": {
"total_response_time": {
"value": 5811.799987792969
}
}
}
min and max Aggregations
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"fastest_response": {
"min": { "field": "response_time" }
},
"slowest_response": {
"max": { "field": "response_time" }
}
}
}'
{
"aggregations": {
"fastest_response": {
"value": 15.100000381469727
},
"slowest_response": {
"value": 5020.0
}
}
}
stats Aggregation
Returns count, min, max, avg, and sum in one call:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"response_stats": {
"stats": { "field": "response_time" }
}
}
}'
{
"aggregations": {
"response_stats": {
"count": 6,
"min": 15.100000381469727,
"max": 5020.0,
"avg": 968.6333312988281,
"sum": 5811.799987792969
}
}
}
cardinality Aggregation
Approximate count of distinct values (like SQL COUNT(DISTINCT ...)):
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"unique_methods": {
"cardinality": { "field": "method" }
},
"unique_ips": {
"cardinality": { "field": "client_ip" }
}
}
}'
{
"aggregations": {
"unique_methods": {
"value": 3
},
"unique_ips": {
"value": 3
}
}
}
Bucket Aggregations
Bucket aggregations group documents into buckets. Each bucket holds a set of documents that match a criterion.
terms Aggregation
Groups documents by distinct values of a field — like SQL GROUP BY:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"by_method": {
"terms": { "field": "method" }
}
}
}'
{
"aggregations": {
"by_method": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{ "key": "GET", "doc_count": 4 },
{ "key": "DELETE", "doc_count": 1 },
{ "key": "POST", "doc_count": 1 }
]
}
}
}
Control the number of buckets with size:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"top_2_methods": {
"terms": {
"field": "method",
"size": 2
}
}
}
}'
{
"aggregations": {
"top_2_methods": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 1,
"buckets": [
{ "key": "GET", "doc_count": 4 },
{ "key": "DELETE", "doc_count": 1 }
]
}
}
}
date_histogram Aggregation
Groups documents into time-based buckets — essential for time-series dashboards:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"requests_over_time": {
"date_histogram": {
"field": "@timestamp",
"calendar_interval": "hour"
}
}
}
}'
{
"aggregations": {
"requests_over_time": {
"buckets": [
{
"key_as_string": "2026-04-01T08:00:00.000Z",
"key": 1743494400000,
"doc_count": 5
},
{
"key_as_string": "2026-04-01T09:00:00.000Z",
"key": 1743498000000,
"doc_count": 1
}
]
}
}
}
Use fixed_interval for exact durations:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"requests_per_10_min": {
"date_histogram": {
"field": "@timestamp",
"fixed_interval": "10m"
}
}
}
}'
range Aggregation
Groups documents into custom numeric ranges:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"response_time_ranges": {
"range": {
"field": "response_time",
"ranges": [
{ "key": "fast", "to": 100 },
{ "key": "normal", "from": 100, "to": 500 },
{ "key": "slow", "from": 500 }
]
}
}
}
}'
{
"aggregations": {
"response_time_ranges": {
"buckets": [
{ "key": "fast", "to": 100.0, "doc_count": 2 },
{ "key": "normal", "from": 100.0, "to": 500.0, "doc_count": 3 },
{ "key": "slow", "from": 500.0, "doc_count": 1 }
]
}
}
}
filters Aggregation
Creates named buckets based on query filters:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"status_groups": {
"filters": {
"filters": {
"success": { "range": { "status_code": { "gte": 200, "lt": 300 } } },
"client_error": { "range": { "status_code": { "gte": 400, "lt": 500 } } },
"server_error": { "range": { "status_code": { "gte": 500 } } }
}
}
}
}
}'
{
"aggregations": {
"status_groups": {
"buckets": {
"success": { "doc_count": 4 },
"client_error": { "doc_count": 1 },
"server_error": { "doc_count": 1 }
}
}
}
}
histogram Aggregation
Groups by fixed-width numeric intervals:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"response_histogram": {
"histogram": {
"field": "response_time",
"interval": 100,
"min_doc_count": 1
}
}
}
}'
{
"aggregations": {
"response_histogram": {
"buckets": [
{ "key": 0.0, "doc_count": 1 },
{ "key": 100.0, "doc_count": 2 },
{ "key": 200.0, "doc_count": 1 },
{ "key": 300.0, "doc_count": 1 },
{ "key": 5000.0, "doc_count": 1 }
]
}
}
}
Nested / Sub-Aggregations
Bucket aggregations can contain sub-aggregations — metrics computed per bucket. This is where aggregations become truly powerful.
Metrics Inside Buckets
Average response time per HTTP method:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"by_method": {
"terms": { "field": "method" },
"aggs": {
"avg_response": {
"avg": { "field": "response_time" }
},
"max_response": {
"max": { "field": "response_time" }
}
}
}
}
}'
{
"aggregations": {
"by_method": {
"buckets": [
{
"key": "GET",
"doc_count": 4,
"avg_response": { "value": 110.39999961853027 },
"max_response": { "value": 210.69999694824219 }
},
{
"key": "DELETE",
"doc_count": 1,
"avg_response": { "value": 5020.0 },
"max_response": { "value": 5020.0 }
},
{
"key": "POST",
"doc_count": 1,
"avg_response": { "value": 350.20001220703125 },
"max_response": { "value": 350.20001220703125 }
}
]
}
}
}
Multi-Level Nesting
Group by method, then by status code range, with stats at each level:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"by_method": {
"terms": { "field": "method" },
"aggs": {
"by_status_group": {
"range": {
"field": "status_code",
"ranges": [
{ "key": "2xx", "from": 200, "to": 300 },
{ "key": "4xx", "from": 400, "to": 500 },
{ "key": "5xx", "from": 500, "to": 600 }
]
},
"aggs": {
"response_stats": {
"stats": { "field": "response_time" }
}
}
}
}
}
}
}'
{
"aggregations": {
"by_method": {
"buckets": [
{
"key": "GET",
"doc_count": 4,
"by_status_group": {
"buckets": [
{
"key": "2xx",
"doc_count": 3,
"response_stats": { "count": 3, "min": 95.3, "max": 210.7, "avg": 142.17, "sum": 426.5 }
},
{
"key": "4xx",
"doc_count": 1,
"response_stats": { "count": 1, "min": 15.1, "max": 15.1, "avg": 15.1, "sum": 15.1 }
},
{
"key": "5xx",
"doc_count": 0,
"response_stats": { "count": 0, "min": null, "max": null, "avg": null, "sum": 0.0 }
}
]
}
},
{
"key": "DELETE",
"doc_count": 1,
"by_status_group": {
"buckets": [
{ "key": "2xx", "doc_count": 0, "response_stats": { "count": 0 } },
{ "key": "4xx", "doc_count": 0, "response_stats": { "count": 0 } },
{
"key": "5xx",
"doc_count": 1,
"response_stats": { "count": 1, "min": 5020.0, "max": 5020.0, "avg": 5020.0, "sum": 5020.0 }
}
]
}
}
]
}
}
}
Pipeline Aggregations
Pipeline aggregations work on the output of other aggregations rather than document fields.
cumulative_sum
Running total of request counts over time:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"requests_over_time": {
"date_histogram": {
"field": "@timestamp",
"fixed_interval": "10m"
},
"aggs": {
"request_count": {
"value_count": { "field": "method" }
},
"cumulative_requests": {
"cumulative_sum": {
"buckets_path": "request_count"
}
}
}
}
}
}'
{
"aggregations": {
"requests_over_time": {
"buckets": [
{ "key_as_string": "2026-04-01T08:00:00.000Z", "doc_count": 2, "request_count": { "value": 2 }, "cumulative_requests": { "value": 2.0 } },
{ "key_as_string": "2026-04-01T08:10:00.000Z", "doc_count": 2, "request_count": { "value": 2 }, "cumulative_requests": { "value": 4.0 } },
{ "key_as_string": "2026-04-01T08:20:00.000Z", "doc_count": 1, "request_count": { "value": 1 }, "cumulative_requests": { "value": 5.0 } },
{ "key_as_string": "2026-04-01T09:00:00.000Z", "doc_count": 1, "request_count": { "value": 1 }, "cumulative_requests": { "value": 6.0 } }
]
}
}
}
derivative
Compute the rate of change between buckets:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"aggs": {
"requests_over_time": {
"date_histogram": {
"field": "@timestamp",
"fixed_interval": "10m"
},
"aggs": {
"avg_response": {
"avg": { "field": "response_time" }
},
"response_time_change": {
"derivative": {
"buckets_path": "avg_response"
}
}
}
}
}
}'
{
"aggregations": {
"requests_over_time": {
"buckets": [
{ "key_as_string": "2026-04-01T08:00:00.000Z", "avg_response": { "value": 235.35 } },
{ "key_as_string": "2026-04-01T08:10:00.000Z", "avg_response": { "value": 55.2 }, "response_time_change": { "value": -180.15 } },
{ "key_as_string": "2026-04-01T08:20:00.000Z", "avg_response": { "value": 5020.0 }, "response_time_change": { "value": 4964.8 } },
{ "key_as_string": "2026-04-01T09:00:00.000Z", "avg_response": { "value": 210.7 }, "response_time_change": { "value": -4809.3 } }
]
}
}
}
Combining Queries with Aggregations
Aggregations run on the documents matched by the query. This lets you scope your analytics:
curl -s -X GET "localhost:9200/web-logs/_search?pretty" -H 'Content-Type: application/json' -d '{
"size": 0,
"query": {
"bool": {
"filter": [
{ "term": { "method": "GET" } }
]
}
},
"aggs": {
"status_breakdown": {
"terms": { "field": "status_code" }
},
"avg_response": {
"avg": { "field": "response_time" }
}
}
}'
{
"took": 2,
"hits": { "total": { "value": 4, "relation": "eq" } },
"aggregations": {
"status_breakdown": {
"buckets": [
{ "key": 200, "doc_count": 3 },
{ "key": 404, "doc_count": 1 }
]
},
"avg_response": {
"value": 110.39999961853027
}
}
}
Only GET requests are included in the aggregations.
Quick Reference
| Aggregation | Type | Purpose |
|---|---|---|
avg |
Metric | Average value |
sum |
Metric | Total sum |
min / max |
Metric | Minimum / maximum value |
stats |
Metric | All basic stats in one call |
cardinality |
Metric | Approximate distinct count |
value_count |
Metric | Count of values |
terms |
Bucket | Group by distinct values |
date_histogram |
Bucket | Group by time intervals |
range |
Bucket | Group by custom numeric ranges |
filters |
Bucket | Group by named query filters |
histogram |
Bucket | Group by fixed numeric intervals |
cumulative_sum |
Pipeline | Running total across buckets |
derivative |
Pipeline | Rate of change between buckets |
size: 0 |
Pattern | Skip hits, return only agg results |
Next: Sorting and Pagination — Control result ordering, pagination strategies, and source filtering.