Composite Aggregation für Pagination großer Ergebnismengen
AI generated
_doc
_index
Elasticsearch · OpenSearch · Composite Aggregation · Pagination
Composite Aggregation
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.

19 Min. Lesezeit after_key · composite · sources · Pagination Elasticsearch 8.x · OpenSearch 2.x

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.

11. FAQ: Composite Aggregation für Pagination

1Warum keine Terms Aggregation für vollständige Exporte?
Bei hohem size müssen alle Buckets gleichzeitig im Speicher gehalten werden. Bei Millionen Werten drohen lange Antwortzeiten oder Circuit Breaker.
2Was ist der after_key?
Identifiziert den letzten Bucket der Antwort. Als after-Parameter im nächsten Request liefert er genau die nächste Seite.
3Wie erkenne ich das Ende der Iteration?
buckets-Array ist leer oder kleiner als size. Dann sind keine weiteren Buckets mehr vorhanden.
4Nach Metrik sortieren?
Nein, nur nach dem zusammengesetzten Schlüssel. Sortierung nach Kennzahl muss im Anwendungscode nach der Iteration erfolgen.
5Wie viele Sources kombinierbar?
Technisch mehrere, praktisch zwei bis drei. Jede zusätzliche Source erhöht die Kombinationszahl und die Gesamtseitenanzahl.
6Robust bei Verbindungsabbrüchen?
Ja, jeder Request ist zustandslos. Mit dem letzten after_key lässt sich die Iteration nahtlos fortsetzen.
7Sub-Aggregationen innerhalb Composite?
Ja, Metric-Aggregationen pro Bucket sind Standard. Tiefe Bucket-Verschachtelung vergrößert die Antwort pro Seite deutlich.
8Composite vs. Scroll oder search_after?
Scroll und search_after paginieren Dokumente, Composite paginiert Buckets. Composite braucht keinen offenen Server-Kontext.
9Welcher size-Wert ist sinnvoll?
Zwischen 100 und 1000 für die meisten Fälle. Zu klein bedeutet viele Requests, zu groß erhöht Antwortgröße und Speicherbedarf.
10Für Echtzeit-UI-Facetten geeignet?
Nur bedingt. Terms Aggregation bleibt einfacher für Top-N. Composite lohnt sich bei vollständiger Verarbeitung aller Werte.