Performance gezielt optimieren
Die Terms Aggregation gehört zu den am häufigsten genutzten Elasticsearch-Features, wird aber bei hoher Feldkardinalität schnell zum Performance-Engpass. Der shard_size-Parameter, doc_count_error_upper_bound als Genauigkeitsindikator und eager global ordinals zur Vorberechnung teurer Datenstrukturen entscheiden gemeinsam darüber, ob eine Facetten-Query im einstelligen oder dreistelligen Millisekundenbereich antwortet. Dieser Beitrag zeigt, wie sich Terms Aggregationen bei Millionen eindeutiger Werte gezielt optimieren lassen, ohne die Ergebnisqualität aus dem Blick zu verlieren.
Inhaltsverzeichnis
- 1. Warum Terms Aggregation teuer werden kann
- 2. doc_count_error_upper_bound verstehen
- 3. shard_size richtig einstellen
- 4. Eager Global Ordinals
- 5. High-Cardinality-Felder aggregieren
- 6. execution_hint und Circuit Breaker
- 7. Composite statt Terms bei sehr vielen Buckets
- 8. Monitoring: Profile API und Slow Log
- 9. Standard- vs. optimierte Terms Aggregation im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Terms Aggregation teuer werden kann
Die Terms Aggregation ist konzeptionell einfach: einen Bucket pro eindeutigem Feldwert erzeugen und die Dokumentanzahl mitzählen. Die Kosten dieser scheinbar simplen Operation steigen jedoch nicht linear, sondern mit der Kardinalität des aggregierten Feldes und der Anzahl der beteiligten Shards. Jeder Shard muss zunächst lokal seine eigenen Top-Buckets ermitteln, bevor der koordinierende Knoten alle Shard-Ergebnisse zu einem finalen Ranking zusammenführt. Bei einem Feld mit wenigen hundert eindeutigen Werten ist das trivial, bei einem Feld mit Millionen eindeutiger Werte, etwa Produkt-SKUs oder E-Mail-Adressen, wird die lokale Vorsortierung pro Shard zur dominanten Kostenkomponente.
Ein zweiter Kostenfaktor ist der Aufbau der globalen Ordinals, einer internen Datenstruktur, die jedem eindeutigen Feldwert eine kompakte Zahl zuweist, um Vergleiche und Zählungen zu beschleunigen. Dieser Aufbau geschieht standardmäßig lazy, also beim ersten Zugriff, was die allererste Terms Aggregation nach einem Segment-Merge oder Neustart spürbar verlangsamt. Wer die Ursache eines einzelnen langsamen Requests inmitten sonst schneller Antworten sucht, findet die Erklärung häufig genau hier.
2. doc_count_error_upper_bound verstehen
Da jeder Shard nur seine eigenen Top-N-Buckets an den koordinierenden Knoten meldet, kann ein Wert, der auf einem Shard knapp außerhalb der Top-N liegt, auf einem anderen Shard aber sehr häufig vorkommt, in der finalen Zusammenführung fehlerhaft niedrig gezählt werden. Das Feld doc_count_error_upper_bound in der Antwort gibt eine obere Fehlerschranke für diese Ungenauigkeit an: den maximal möglichen Fehler, den ein nicht in den Top-N-Buckets eines Shards enthaltener Wert im schlimmsten Fall in der finalen Zählung aufweisen könnte.
Zusätzlich liefert jeder einzelne Bucket optional ein Feld doc_count_error, wenn show_term_doc_count_error: true gesetzt wird. Damit lässt sich pro Bucket einschätzen, wie verlässlich die gemeldete Anzahl tatsächlich ist. Ein hoher Fehlerwert bei einem angezeigten Bucket ist ein klares Signal, dass entweder shard_size erhöht oder die Anzahl beteiligter Shards reduziert werden sollte, um die Genauigkeit der Terms Aggregation zu verbessern.
POST /orders/_search
{
"size": 0,
"aggs": {
"top_products": {
"terms": {
"field": "product_sku.keyword",
"size": 10,
"show_term_doc_count_error": true
}
}
}
}
// Response excerpt:
// "top_products": {
// "doc_count_error_upper_bound": 42,
// "sum_other_doc_count": 918273,
// "buckets": [
// { "key": "SKU-1001", "doc_count": 5210, "doc_count_error": 3 },
// { "key": "SKU-1002", "doc_count": 4980, "doc_count_error": 12 }
// ]
// }
// A high doc_count_error_upper_bound signals shard_size is too low
3. shard_size richtig einstellen
Der Parameter shard_size bestimmt, wie viele Kandidaten-Buckets jeder einzelne Shard lokal ermittelt und an den koordinierenden Knoten weitergibt, bevor am Ende nur die obersten size Buckets zurückgegeben werden. Elasticsearch setzt standardmäßig shard_size auf einen Wert deutlich größer als size, in der Regel size * 1.5 + 10, um die Genauigkeit zu erhöhen, ohne dass der Nutzer diesen Parameter explizit setzen muss. Bei sehr ungleich verteilten Werten über die Shards hinweg, sogenannter Datenschiefe, reicht dieser Standardwert jedoch manchmal nicht aus.
Eine bewusste Erhöhung von shard_size verbessert die Genauigkeit der Terms Aggregation, verursacht aber pro Shard mehr Arbeit bei der lokalen Top-N-Ermittlung und mehr Netzwerk-Traffic zum koordinierenden Knoten, weil jeder Shard mehr Kandidaten überträgt. Die Faustregel: shard_size zunächst nur erhöhen, wenn doc_count_error_upper_bound in der Praxis tatsächlich zu hoch ausfällt, statt präventiv einen sehr hohen Wert zu setzen, der die Performance ohne messbaren Genauigkeitsgewinn verschlechtert.
POST /orders/_search
{
"size": 0,
"aggs": {
"top_products": {
"terms": {
"field": "product_sku.keyword",
"size": 10,
"shard_size": 100
}
}
}
}
// shard_size > size collects more candidates per shard
// before the final top-10 merge on the coordinating node
4. Eager Global Ordinals
Globale Ordinals sind eine index-weite, komprimierte Zuordnung von eindeutigen Feldwerten zu ganzzahligen IDs, die Terms Aggregationen und Sortierungen auf keyword-Feldern erheblich beschleunigt. Der Aufbau dieser Struktur erfolgt standardmäßig beim ersten Zugriff auf ein Segment, was bedeutet: Nach jedem Refresh, jedem Segment-Merge oder Cluster-Neustart ist die erste Terms Aggregation auf einem betroffenen Feld überproportional langsam, während nachfolgende Anfragen von der bereits aufgebauten Struktur profitieren.
Mit eager_global_ordinals: true im Mapping des betroffenen Feldes weist man Elasticsearch an, die globalen Ordinals proaktiv bei jedem Refresh im Hintergrund neu aufzubauen, statt auf den ersten Suchzugriff zu warten. Das verlagert die Kosten vom ersten Nutzer-Request auf den Indexierungs- und Refresh-Prozess und ist besonders bei Feldern sinnvoll, die in nahezu jeder Facetten-Query aggregiert werden, etwa Marke oder Kategorie in einem E-Commerce-Index. Bei selten aggregierten Feldern lohnt sich der zusätzliche Hintergrund-Aufwand dagegen meist nicht.
PUT /products/_mapping
{
"properties": {
"brand": {
"type": "keyword",
"eager_global_ordinals": true
}
}
}
// Rebuilds global ordinals on every refresh in the background
// Moves cost from first query to indexing/refresh cycle
5. High-Cardinality-Felder aggregieren
Felder mit sehr hoher Kardinalität, etwa E-Mail-Adressen, Session-IDs oder eindeutige Bestellnummern mit Millionen unterschiedlicher Werte, stellen die Terms Aggregation vor besondere Herausforderungen. Der Speicherbedarf für die globalen Ordinals wächst mit der Anzahl eindeutiger Werte, und die lokale Top-N-Ermittlung pro Shard muss über entsprechend mehr Kandidaten laufen. Bei solchen Feldern lohnt sich zunächst die fachliche Frage, ob wirklich alle eindeutigen Werte relevant sind, oder ob eine gröbere Gruppierung, etwa nach Domain statt vollständiger E-Mail-Adresse, das eigentliche Analyseziel bereits erfüllt.
Wenn die volle Kardinalität fachlich benötigt wird, aber nur die reine Anzahl eindeutiger Werte interessiert, nicht die einzelnen Buckets selbst, ist eine cardinality-Aggregation die deutlich günstigere Alternative zur Terms Aggregation, weil sie auf dem HyperLogLog++-Algorithmus basiert und keine vollständige Bucket-Liste materialisieren muss. Wird dagegen tatsächlich jeder einzelne Wert benötigt, etwa für einen vollständigen Export, ist die Terms Aggregation mit einem sinnvoll begrenzten size oder die im nächsten Beitrag behandelte Composite Aggregation der richtige Weg.
// Expensive: terms aggregation forces materializing every bucket
POST /orders/_search
{
"size": 0,
"aggs": {
"unique_emails": {
"terms": { "field": "customer_email.keyword", "size": 50000 }
}
}
}
// Risks circuit breaker, high memory usage
// Cheap: cardinality gives only the count, constant memory
POST /orders/_search
{
"size": 0,
"aggs": {
"unique_emails": {
"cardinality": { "field": "customer_email.keyword" }
}
}
}
6. execution_hint und Circuit Breaker
Der Parameter execution_hint steuert, welcher interne Algorithmus für die Terms Aggregation verwendet wird: map baut eine Hashtabelle der beobachteten Werte auf und eignet sich, wenn nur ein kleiner Teil der eindeutigen Werte im aktuellen Suchergebnis vorkommt, während global_ordinals, der Standard bei den meisten modernen Elasticsearch-Versionen, die zuvor beschriebene Ordinals-Struktur nutzt und bei wiederholten Aggregationen auf demselben Feld meist die bessere Wahl ist. In der Praxis überlässt man diese Entscheidung meist Elasticsearch selbst, da die automatische Heuristik in den allermeisten Fällen die richtige Wahl trifft.
Wichtiger im produktiven Betrieb ist der Umgang mit dem Request Circuit Breaker, der eine Aggregation abbricht, bevor sie den verfügbaren Heap-Speicher eines Knotens überlastet. Eine Terms Aggregation mit sehr hohem size-Wert auf einem hochkardinalitären Feld kann diesen Breaker auslösen, was zu einer circuit_breaking_exception statt eines langsamen, aber erfolgreichen Requests führt. Das ist ein Schutzmechanismus, kein Bug: Die richtige Reaktion ist, size zu reduzieren oder auf eine Composite Aggregation umzusteigen, statt den Breaker-Grenzwert pauschal zu erhöhen.
7. Composite statt Terms bei sehr vielen Buckets
Die Terms Aggregation ist für die Top-N-Ermittlung konzipiert, nicht für die vollständige Iteration über alle Buckets eines hochkardinalitären Feldes. Wird size künstlich sehr hoch gesetzt, um alle Werte zu erhalten, wächst der Speicherbedarf auf dem koordinierenden Knoten linear mit, weil alle Buckets im Antwort-Objekt gleichzeitig im Speicher gehalten werden müssen. Für den Anwendungsfall, wirklich jeden Bucket eines Feldes systematisch zu durchlaufen, etwa für einen vollständigen Export aller Werte, ist die Composite Aggregation mit ihrem after_key-Cursor-Mechanismus die deutlich robustere und speicherschonendere Lösung, die im folgenden Beitrag dieser Reihe im Detail behandelt wird.
Als Faustregel gilt: Terms Aggregation für Top-N-Anzeigen mit size im niedrigen zwei- bis dreistelligen Bereich, Composite Aggregation für vollständige, paginierte Iteration über potenziell tausende oder Millionen Buckets. Wer diese Grenze nicht kennt, versucht typischerweise, eine Terms Aggregation mit size: 100000 zu erzwingen, was fast immer zu Speicherproblemen und im schlimmsten Fall zu einem Node-Absturz führt.
8. Monitoring: Profile API und Slow Log
Um herauszufinden, ob eine langsame Terms Aggregation tatsächlich für die Antwortzeit verantwortlich ist, liefert die Profile API detaillierte Zeitmessungen pro Aggregations-Komponente, inklusive der Zeit für den Aufbau globaler Ordinals, die eigentliche Bucket-Sammlung und die finale Zusammenführung. Diese Analyse sollte gezielt in einer Staging-Umgebung erfolgen, da die Profile API selbst zusätzlichen Overhead erzeugt und in Produktion nicht dauerhaft aktiviert sein sollte.
Für den produktiven Dauerbetrieb eignet sich der Search Slow Log besser: Er protokolliert Requests, die eine konfigurierbare Zeitschwelle überschreiten, inklusive der vollständigen Query- und Aggregations-Struktur. Ein regelmäßiger Blick in den Slow Log deckt typischerweise auf, welche Kombinationen aus Feld, size und Filterbasis in der Praxis am häufigsten zu Ausreißern führen, und liefert damit konkrete Ansatzpunkte für gezieltes eager_global_ordinals- oder shard_size-Tuning.
PUT /products/_settings
{
"index.search.slowlog.threshold.query.warn": "2s",
"index.search.slowlog.threshold.query.info": "500ms",
"index.search.slowlog.threshold.fetch.warn": "1s"
}
// Enable Profile API in staging for per-component timing
POST /products/_search
{
"profile": true,
"size": 0,
"aggs": {
"brands": { "terms": { "field": "brand.keyword" } }
}
}
// Response includes a "profile" section with timing breakdown
9. Standard- vs. optimierte Terms Aggregation im Vergleich
Die folgende Übersicht zeigt, welche Einstellung bei welchem Problem greift.
| Problem | Standard-Verhalten | Optimierung |
|---|---|---|
| Ungenaue Bucket-Zählung | Automatisches shard_size, teils zu niedrig | shard_size gezielt erhöhen, doc_count_error prüfen |
| Langsamer erster Request | Lazy Aufbau globaler Ordinals | eager_global_ordinals: true im Mapping |
| Nur Anzahl eindeutiger Werte nötig | Terms mit sehr hohem size | cardinality-Aggregation statt Terms |
| Vollständige Bucket-Iteration | Terms mit size: 100000+ | Composite Aggregation mit after_key |
| Circuit Breaker Exception | Breaker-Grenzwert pauschal erhöhen | size reduzieren oder Composite verwenden |
Die überwiegende Mehrheit der Performance-Probleme bei Terms Aggregationen lässt sich auf genau diese fünf Muster zurückführen. Wer sie kennt, kann eine langsame Facetten-Query in wenigen Minuten diagnostizieren, statt pauschal Heap-Speicher oder Knoten hinzuzufügen.
Mironsoft
Elasticsearch-Performance-Tuning für hochkardinalitäre Aggregationen
Terms Aggregationen, die bei hoher Kardinalität nicht einbrechen?
Wir analysieren Slow-Log-Daten, tunen shard_size und eager global ordinals gezielt und ersetzen ungeeignete Terms-Aggregations-Muster durch performantere Alternativen für euren Elasticsearch-Cluster.
Performance-Audit
Profile API und Slow Log auswerten, Engpässe identifizieren
Mapping-Tuning
eager_global_ordinals gezielt für Facetten-Felder aktivieren
Query-Redesign
cardinality und Composite Aggregation als Ersatz für ungeeignete Terms-Queries
10. Zusammenfassung
Terms Aggregation Performance hängt an einem Zusammenspiel weniger, aber gezielt einzustellender Parameter. doc_count_error_upper_bound und show_term_doc_count_error zeigen, wann die verteilte Top-N-Ermittlung ungenau wird. shard_size beeinflusst gezielt die Genauigkeit auf Kosten von mehr Arbeit pro Shard und sollte reaktiv, nicht präventiv erhöht werden. eager_global_ordinals verlagert den teuren Aufbau der Ordinals-Struktur vom ersten Nutzer-Request auf den Refresh-Zyklus und lohnt sich für häufig aggregierte Felder.
Bei sehr hoher Feldkardinalität lohnt sich die fachliche Frage, ob wirklich alle Buckets benötigt werden. Für reine Anzahl eindeutiger Werte ist cardinality die günstigere Wahl, für vollständige Iteration über alle Buckets die Composite Aggregation. Terms Aggregation bleibt die richtige Wahl für Top-N-Facetten mit moderatem size, verliert aber ihre Effizienz, sobald sie für Aufgaben zweckentfremdet wird, für die sie nicht konzipiert wurde.
Terms Aggregation Performance, das Wichtigste auf einen Blick
Genauigkeit prüfen
doc_count_error_upper_bound und show_term_doc_count_error zeigen, ob shard_size zu niedrig ist.
eager_global_ordinals
Verlagert den Ordinals-Aufbau vom ersten Query auf den Refresh-Zyklus, ideal für häufige Facetten-Felder.
High-Cardinality-Felder
cardinality statt Terms bei reiner Anzahlfrage, Composite Aggregation für vollständige Iteration.
Circuit Breaker
Ein Breaker-Fehler ist ein Schutzmechanismus, size reduzieren statt den Grenzwert pauschal zu erhöhen.