Feature
Aggregations
Send an aggs block alongside a search to get value counts,
distinct-value estimates, or a paged bucket walk back in the same response.
Aggregations run over a segment's columnar filter store, which shapes both what
they can group on and — today — what they count.
{
"query_text": "breach",
"aggs": {
"by_status": {
"terms": {"field": "status", "size": 10}
}
}
}
Read this before building on aggregations
Aggregations are the least finished feature in Phase 1. They are computed
per segment and not merged across segments — a namespace
with more than one segment returns one segment's numbers, not the
namespace's. They are also computed over the segment's full contents rather
than the query's matched set, so counts do not narrow when
query_text or filter does.
Treat them as usable on single-segment namespaces and as a preview everywhere else. The rest of this page documents what the API accepts and what it currently does, including where those diverge.
terms
Counts documents per distinct value of a field, ordered by count descending.
{
"aggs": {
"by_status": {"terms": {"field": "status", "size": 10}}
}
}
{
"results": [ … ],
"total_hits": 42,
"aggregations": {
"per_document": {
"buckets": [
{"key": "published", "doc_count": 31},
{"key": "archived", "doc_count": 11}
]
}
}
}
size is accepted but not currently applied — every distinct value
is returned. Truncate client-side until it is enforced.
cardinality
The number of distinct values of a field.
{
"aggs": {
"distinct_tenants": {
"cardinality": {"field": "tenant", "precision_threshold": 40000}
}
}
}
{
"aggregations": {
"total_documents": {"value": 17}
}
}
The count is currently exact, computed from a hash set rather
than a sketch, so precision_threshold is accepted and ignored. That
is more accurate than OpenSearch's HyperLogLog and more memory-hungry — it is
fine at the cardinalities a single segment holds and is the reason the parameter
exists in the API at all, for when the implementation switches to a sketch.
composite
A paged walk over buckets, for when you need to enumerate every value rather than see the top ones.
{
"aggs": {
"walk": {
"composite": {
"size": 100,
"sources": [
{"by_tenant": {"terms": {"field": "tenant"}}}
]
}
}
}
}
{
"aggregations": {
"matched_docs": {
"buckets": [
{"key": {"tenant": "acme"}, "doc_count": 9},
{"key": {"tenant": "globex"}, "doc_count": 8}
],
"after_key": {"tenant": "globex"}
}
}
}
size is honoured here — it caps the bucket list — and
after_key comes back as the last bucket's key. The
after request parameter that would consume it is accepted but not
yet applied, so passing it back does not currently advance the walk. Only the
first entry in sources is used; multi-source composites are not
implemented.
Response shape
The response does not key results by the name you gave the aggregation. Each aggregation type writes to a fixed slot instead:
| Aggregation | Response key | Payload |
|---|---|---|
terms | per_document | { buckets: [{key, doc_count}] } |
cardinality | total_documents | { value } |
composite | matched_docs | { buckets: [{key, doc_count}], after_key } |
Because the slots are fixed, requesting two aggregations of the same type in one search is not useful — only one result survives. Requesting one of each type is fine, since they occupy different slots. Name your aggregations for readability; the name does not appear in the response.
What you can group on
All three aggregations read the string column, which means they
work on Keyword, Date, Boolean, and
Text fields. Grouping on an Integer or
Float field returns nothing — those live in numeric columns the
aggregation path does not consult. If you need to facet on a number, also index
it as a Keyword.
There are no metric aggregations — no sum, avg,
min, max, or histograms — and no nested
sub-aggregations.
Using them today
-
Check your segment count first.
GET /v1/namespaces/{ns}/statsreports it. Atsegments: 1the numbers describe the whole namespace; above that they describe one segment of it. -
Do not filter and expect the counts to follow. A
filternarrowsresultsbut not the buckets. If you need counts for a subset, the reliable route today is a narrower namespace. -
Pair with
max_results: 0when you only want the buckets, to avoid materialising a page of documents you are going to discard.