für Pagination großer Ergebnismengen
Wer versucht, alle Buckets eines hochkardinalitären Feldes über eine Terms Aggregation mit riesigem size-Wert zu exportieren, stößt unweigerlich an Speichergrenzen. Die Composite Aggregation löst dieses Problem mit einem after_key-Cursor, der Seite für Seite durch beliebig viele Buckets iteriert, ohne jemals mehr als eine Seite gleichzeitig im Speicher zu halten. Dieser Beitrag zeigt, wie sich vollständige Exporte, Reports und Data-Pipeline-Iterationen mit der Composite Aggregation robust und speicherschonend umsetzen lassen.
Inhaltsverzeichnis
- 1. Warum Terms-Aggregation-Pagination nicht skaliert
- 2. Composite Aggregation: Grundkonzept
- 3. Der after_key-Cursor im Detail
- 4. Sources kombinieren: terms, histogram, date_histogram
- 5. Vollständige Iteration: alle Buckets exportieren
- 6. Sortierung und Performance bei Composite
- 7. Composite mit Sub-Aggregationen kombinieren
- 8. Anwendungsfälle: Exporte, Reports, Data Pipelines
- 9. Composite vs. Terms Pagination im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum Terms-Aggregation-Pagination nicht skaliert
Die Terms Aggregation kennt keinen echten Pagination-Mechanismus. Der einzige Weg, mehr als die obersten size Buckets zu erhalten, ist, size selbst zu erhöhen und alle Buckets in einer einzigen Antwort zurückzubekommen. Bei einem Feld mit tausend eindeutigen Werten funktioniert das noch akzeptabel, bei einem Feld mit einer Million eindeutiger Werte muss der koordinierende Knoten eine Million Buckets gleichzeitig im Speicher halten, bevor die Antwort überhaupt serialisiert werden kann. Das führt zu langen Antwortzeiten, hohem Garbage-Collection-Druck und im schlimmsten Fall zum bereits erwähnten Circuit Breaker, der die Anfrage komplett abbricht.
Ein zweites, subtileres Problem: Terms Aggregation mit sehr hohem size liefert trotzdem keine Garantie für Vollständigkeit oder stabile Reihenfolge zwischen zwei aufeinanderfolgenden Requests, insbesondere wenn sich der zugrunde liegende Datenbestand zwischendurch ändert. Für Anwendungsfälle wie einen vollständigen CSV-Export aller Markenwerte oder eine Data-Pipeline, die systematisch jeden Bucket verarbeiten muss, ist die Terms Aggregation deshalb strukturell die falsche Wahl. Genau für diesen Anwendungsfall wurde die Composite Aggregation entwickelt.
2. Composite Aggregation: Grundkonzept
Die Composite Aggregation liefert pro Request nur eine begrenzte, konfigurierbare Anzahl von Buckets über den Parameter size, verhält sich also zunächst ähnlich wie eine normale Bucket-Aggregation. Der entscheidende Unterschied liegt in der Antwort: Jede Composite-Antwort enthält zusätzlich ein Feld after_key, das den letzten zurückgegebenen Bucket eindeutig identifiziert. Wird dieser after_key im nächsten Request als Parameter after mitgegeben, liefert Elasticsearch genau die nächste Seite von Buckets, direkt im Anschluss an den zuletzt gesehenen Wert.
Dieses Cursor-basierte Pagination-Modell ist speichertechnisch grundlegend anders als das size-basierte Modell der Terms Aggregation: Der koordinierende Knoten muss zu keinem Zeitpunkt mehr als eine Seite von Buckets gleichzeitig im Speicher halten. Die Composite Aggregation kombiniert dazu eine oder mehrere sources, die jeweils ein Bucket-Kriterium wie terms, histogram oder date_histogram definieren, und erzeugt daraus eine zusammengesetzte, eindeutig sortierte Bucket-Sequenz.
POST /products/_search
{
"size": 0,
"aggs": {
"all_brands": {
"composite": {
"size": 100,
"sources": [
{ "brand": { "terms": { "field": "brand.keyword" } } }
]
}
}
}
}
// Response excerpt:
// "all_brands": {
// "after_key": { "brand": "Lenovo" },
// "buckets": [
// { "key": { "brand": "Dell" }, "doc_count": 340 },
// { "key": { "brand": "HP" }, "doc_count": 251 },
// { "key": { "brand": "Lenovo" }, "doc_count": 298 }
// ]
// }
3. Der after_key-Cursor im Detail
Um die nächste Seite von Buckets abzurufen, wird der after_key-Wert aus der vorherigen Antwort unverändert als after-Parameter in den nächsten Request übernommen. Elasticsearch sortiert die zusammengesetzten Bucket-Schlüssel intern eindeutig und deterministisch, sodass jeder Bucket exakt einmal über die gesamte Iteration hinweg zurückgegeben wird, auch wenn sich der Datenbestand zwischen zwei Requests geringfügig ändert. Diese Eigenschaft unterscheidet die Composite Aggregation fundamental von naivem Offset-basiertem Paging, bei dem sich Verschiebungen im Datenbestand direkt auf die Konsistenz der Seiten auswirken.
Das Ende der Iteration ist erreicht, sobald eine Antwort ein leeres buckets-Array liefert oder die Anzahl zurückgegebener Buckets kleiner als der angeforderte size-Wert ist. Anwendungscode, der die Composite Aggregation nutzt, implementiert deshalb typischerweise eine einfache Schleife: Request senden, Buckets verarbeiten, after_key für den nächsten Request übernehmen, wiederholen, bis keine weiteren Buckets mehr zurückkommen.
POST /products/_search
{
"size": 0,
"aggs": {
"all_brands": {
"composite": {
"size": 100,
"after": { "brand": "Lenovo" },
"sources": [
{ "brand": { "terms": { "field": "brand.keyword" } } }
]
}
}
}
}
// Continues exactly after the last bucket from the previous page
// Loop until "buckets" comes back empty or shorter than size
4. Sources kombinieren: terms, histogram, date_histogram
Der eigentliche Mehrwert der Composite Aggregation zeigt sich, wenn mehrere sources gleichzeitig kombiniert werden. Jede zusätzliche Source verfeinert den zusammengesetzten Bucket-Schlüssel um eine weitere Dimension, etwa Marke und Kategorie gemeinsam, oder Marke kombiniert mit einem monatlichen date_histogram für Zeitreihen-Reports. Die Reihenfolge der sources im Array bestimmt dabei sowohl die Sortierreihenfolge der zusammengesetzten Schlüssel als auch die Priorität bei der internen Sortierung.
Anders als bei verschachtelten Bucket-Aggregationen erzeugt die Composite Aggregation keine hierarchische Baumstruktur, sondern eine flache Liste zusammengesetzter Schlüssel, bei der jeder Bucket-Schlüssel alle Source-Werte gleichzeitig enthält. Das macht die Verarbeitung im Anwendungscode einfacher, weil keine rekursive Traversierung verschachtelter Buckets nötig ist, sondern eine einfache flache Liste iteriert werden kann.
POST /orders/_search
{
"size": 0,
"aggs": {
"sales_by_brand_month": {
"composite": {
"size": 500,
"sources": [
{ "brand": { "terms": { "field": "brand.keyword" } } },
{ "month": { "date_histogram": { "field": "order_date", "calendar_interval": "month" } } }
]
},
"aggs": {
"revenue": { "sum": { "field": "total_price" } }
}
}
}
}
// Each bucket key combines brand AND month, e.g.
// { "brand": "Dell", "month": "2026-06-01T00:00:00.000Z" }
5. Vollständige Iteration: alle Buckets exportieren
Für einen vollständigen Export aller Bucket-Werte, etwa alle jemals verwendeten SKU-Werte in einem Bestellindex, implementiert man eine Schleife im Anwendungscode: Der erste Request enthält keinen after-Parameter, jede weitere Iteration übernimmt den after_key der vorherigen Antwort. Diese Schleife läuft, bis ein leeres Bucket-Array signalisiert, dass keine weiteren Werte mehr existieren. Anders als bei Scroll-basierten Ansätzen benötigt die Composite Aggregation keinen serverseitigen Kontext, der offen gehalten werden muss, und ist deshalb robust gegenüber Verbindungsabbrüchen zwischen einzelnen Requests.
Ein praktischer Vorteil: Da jeder Request unabhängig und zustandslos ist, kann eine unterbrochene Iteration jederzeit mit dem zuletzt gespeicherten after_key fortgesetzt werden, ohne von vorne beginnen zu müssen. Das ist besonders bei langlaufenden Export-Jobs relevant, die stunden- oder tagelang über sehr große Datenmengen iterieren und bei einem Absturz nicht kostspielig neu starten sollen.
6. Sortierung und Performance bei Composite
Die Composite Aggregation sortiert Bucket-Schlüssel standardmäßig aufsteigend, kann aber pro Source mit dem Parameter order: "desc" individuell umgekehrt werden. Wichtig: Die Sortierung erfolgt ausschließlich über die zusammengesetzten Schlüssel selbst, nicht über doc_count oder eine Sub-Aggregation, wie es bei der Terms Aggregation möglich ist. Wer Buckets nach einer berechneten Kennzahl sortiert benötigt, muss diese Sortierung nach Abschluss der vollständigen Iteration im Anwendungscode selbst vornehmen.
Performance-technisch skaliert die Composite Aggregation deutlich besser als eine Terms Aggregation mit künstlich hohem size, weil pro Request nur eine begrenzte Anzahl Buckets materialisiert werden muss. Der Preis dafür sind mehrere sequenzielle Requests statt eines einzelnen: Bei einer Million eindeutiger Werte und einer Seitengröße von 1000 sind tausend Requests nötig, um vollständig zu iterieren, was bei zeitkritischen Anwendungsfällen berücksichtigt werden sollte.
POST /products/_search
{
"size": 0,
"aggs": {
"all_brands": {
"composite": {
"size": 100,
"sources": [
{
"brand": {
"terms": { "field": "brand.keyword", "order": "desc" }
}
}
]
}
}
}
}
// Sorting applies only to the composite key itself,
// not to doc_count or any sub-aggregation value
7. Composite mit Sub-Aggregationen kombinieren
Genau wie andere Bucket-Aggregationen akzeptiert die Composite Aggregation einen eigenen aggs-Block für Metric-Aggregationen pro Bucket. Das erlaubt, während der vollständigen Iteration nicht nur die Bucket-Schlüssel, sondern auch berechnete Kennzahlen wie Umsatz oder Durchschnittspreis pro Kombination aus Marke und Monat mitzuliefern, ohne für jeden Bucket einen separaten Request stellen zu müssen. Diese Kombination ist der Kern der meisten Report-Generierungs-Pipelines, die auf der Composite Aggregation aufbauen.
Eine Einschränkung besteht darin, dass verschachtelte Bucket-Aggregationen innerhalb einer Composite Aggregation zwar technisch möglich sind, aber die Antwortgröße pro Seite entsprechend vergrößern und die Vorteile der begrenzten Speichernutzung teilweise wieder aufheben können. In der Praxis bleibt man deshalb meist bei einer Kombination aus Composite Aggregation und einfachen Metric-Sub-Aggregationen, statt tief verschachtelte Bucket-Strukturen innerhalb jeder Composite-Seite aufzubauen.
8. Anwendungsfälle: Exporte, Reports, Data Pipelines
Der klassische Anwendungsfall der Composite Aggregation ist der vollständige Export aller eindeutigen Werte eines Feldes, etwa zur Synchronisation eines externen Empfehlungssystems mit allen Produktkategorien eines Katalogs. Ein zweiter häufiger Anwendungsfall ist die Generierung periodischer Reports, die über alle Kombinationen aus mehreren Dimensionen iterieren müssen, etwa Umsatz pro Marke und Monat für die letzten drei Jahre, wo eine einzelne Terms Aggregation mit verschachtelter Sub-Aggregation an der schieren Anzahl der Kombinationen scheitern würde.
Ein dritter, zunehmend wichtiger Anwendungsfall ist die Nutzung als Datenquelle für ETL-Pipelines und Machine-Learning-Feature-Extraktion, bei denen systematisch jede Kombination aus Nutzer-Segment und Zeitfenster verarbeitet werden muss. Da die Composite Aggregation zustandslos und wiederaufnehmbar ist, eignet sie sich gut für solche langlaufenden, batch-orientierten Prozesse, die in produktiven Data-Pipelines regelmäßig ausgeführt werden.
// Pseudocode for a resumable full export loop
let afterKey = null;
do {
const body = {
size: 0,
aggs: {
export: {
composite: {
size: 500,
after: afterKey,
sources: [
{ sku: { terms: { field: "sku.keyword" } } }
]
}
}
}
};
const response = search("/products/_search", body);
processBuckets(response.aggregations.export.buckets);
afterKey = response.aggregations.export.after_key;
} while (response.aggregations.export.buckets.length > 0);
// Save afterKey after each page to resume on failure
9. Composite vs. Terms Pagination im Vergleich
Die folgende Tabelle stellt die beiden Ansätze für die Iteration über viele Buckets gegenüber.
| Aspekt | Terms Aggregation (size hoch) | Composite Aggregation |
|---|---|---|
| Speicherverbrauch | Alle Buckets gleichzeitig im Speicher | Nur eine Seite gleichzeitig im Speicher |
| Pagination | Kein nativer Mechanismus | after_key-Cursor pro Request |
| Vollständigkeitsgarantie | Nicht garantiert bei hoher Kardinalität | Garantiert bei vollständiger Iteration |
| Sortierung nach Metrik | Möglich über order-Parameter | Nur nach Bucket-Schlüssel selbst |
| Typischer Einsatz | Top-N-Facetten in der UI | Vollständige Exporte und Reports |
Die Wahl zwischen beiden Ansätzen ist eine Frage des Anwendungsfalls, nicht der generellen Überlegenheit: Für Top-N-Anzeigen in der Nutzeroberfläche bleibt die Terms Aggregation die richtige, einfachere Wahl. Sobald aber wirklich jeder Bucket verarbeitet werden muss, ist die Composite Aggregation die einzige robuste Lösung, die auch bei Millionen eindeutiger Werte stabil bleibt.
Mironsoft
Elasticsearch-Exporte, Reports und Data-Pipeline-Architektur
Vollständige Datenexporte, die nicht am Speicher scheitern?
Wir bauen robuste, wiederaufnehmbare Export- und Report-Pipelines auf Basis der Composite Aggregation, die auch bei Millionen Buckets zuverlässig und speichereffizient laufen.
Export-Design
after_key-basierte Iteration für vollständige, speicherschonende Exporte
Report-Pipelines
Composite Aggregation mit Sub-Aggregationen für mehrdimensionale Reports
Migration von Terms
Bestehende überlastete Terms-Aggregations-Exporte auf Composite umstellen
10. Zusammenfassung
Die Composite Aggregation löst ein strukturelles Problem, das die Terms Aggregation bei hoher Kardinalität nicht lösen kann: die vollständige, speichereffiziente Iteration über beliebig viele Buckets. Der after_key-Cursor liefert nach jedem Request genau die nächste Seite von Buckets, ohne dass der koordinierende Knoten jemals mehr als eine Seite gleichzeitig im Speicher halten muss. Mehrere sources lassen sich zu einem zusammengesetzten, eindeutig sortierten Bucket-Schlüssel kombinieren, etwa Marke und Monat gemeinsam.
Für Top-N-Facetten in der Nutzeroberfläche bleibt die Terms Aggregation die einfachere, richtige Wahl. Sobald aber ein vollständiger Export, ein mehrdimensionaler Report oder eine Data-Pipeline systematisch jeden Bucket verarbeiten muss, ist die Composite Aggregation die robustere Lösung: zustandslos, wiederaufnehmbar und ohne die Speicherprobleme, die eine künstlich hoch gesetzte size bei der Terms Aggregation verursacht.
Composite Aggregation für Pagination, das Wichtigste auf einen Blick
after_key-Cursor
Jede Antwort liefert einen after_key, der als after-Parameter die nächste Seite abruft.
Speichereffizienz
Nur eine Seite von Buckets gleichzeitig im Speicher, unabhängig von der Gesamtanzahl.
Mehrere Sources
terms, histogram und date_histogram lassen sich zu einem zusammengesetzten Schlüssel kombinieren.
Einsatzgebiet
Vollständige Exporte, mehrdimensionale Reports und Data-Pipeline-Iterationen statt Top-N-Anzeigen.