Kosha docs
Documentation menu

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.

POST /v1/namespaces/{namespace}/search
{
  "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.

request
{
  "aggs": {
    "by_status": {"terms": {"field": "status", "size": 10}}
  }
}
response
{
  "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.

request
{
  "aggs": {
    "distinct_tenants": {
      "cardinality": {"field": "tenant", "precision_threshold": 40000}
    }
  }
}
response
{
  "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.

request
{
  "aggs": {
    "walk": {
      "composite": {
        "size": 100,
        "sources": [
          {"by_tenant": {"terms": {"field": "tenant"}}}
        ]
      }
    }
  }
}
response
{
  "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:

AggregationResponse keyPayload
termsper_document{ buckets: [{key, doc_count}] }
cardinalitytotal_documents{ value }
compositematched_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}/stats reports it. At segments: 1 the numbers describe the whole namespace; above that they describe one segment of it.
  • Do not filter and expect the counts to follow. A filter narrows results but not the buckets. If you need counts for a subset, the reliable route today is a narrower namespace.
  • Pair with max_results: 0 when you only want the buckets, to avoid materialising a page of documents you are going to discard.