Nested Aggregationen für facettierte Suche
AI generated
_doc
_index
Elasticsearch · OpenSearch · Nested Aggregation · Facetten
Nested Aggregationen
für facettierte Suche auf Produktvarianten

Sobald ein Produkt mehrere Varianten mit eigener Größe, Farbe und eigenem Preis besitzt, versagt die klassische Aggregation still und zählt falsch. Die nested Aggregation arbeitet auf einem separaten, isolierten Lucene-Index innerhalb des Parent-Dokuments und liefert dadurch korrekte Facetten auf Varianten-Ebene. Mit reverse_nested lässt sich zusätzlich wieder in den Parent-Kontext zurückspringen, um Facetten zu bauen, die sowohl Varianten- als auch Produktebene korrekt verbinden.

19 Min. Lesezeit nested · reverse_nested · Mapping · Facetten Elasticsearch 8.x · OpenSearch 2.x

1. Warum facettierte Suche nested Aggregationen braucht

Facettierte Suche lebt davon, dass Filterwerte korrekt gezählt werden: Wie viele T-Shirts gibt es in Größe M, wie viele in Blau? Sobald diese Attribute nicht direkt am Produkt-Dokument hängen, sondern an einzelnen Varianten innerhalb des Produkts, entsteht ein strukturelles Problem. Speichert man Varianten als einfaches Array von Objekten, verschmelzt Elasticsearch beim Indexieren alle Werte jedes Feldes intern zu je einer flachen Liste pro Feld. Die Beziehung zwischen "Größe M" und "Farbe Blau" innerhalb derselben Variante geht dabei komplett verloren.

Das Ergebnis: Eine Terms Aggregation auf variants.color kombiniert mit einem Filter auf variants.size: M liefert auch Farben zurück, die nur bei ganz anderen Größen existieren, weil Elasticsearch nicht weiß, welche Farbe zu welcher Größe gehört. Genau dieses Problem löst der Feldtyp nested in Kombination mit der Nested Aggregation: Jede Variante wird als eigenständiges, verstecktes Lucene-Dokument gespeichert, wodurch die Beziehung zwischen den Attributen einer einzelnen Variante erhalten bleibt und Facetten korrekt gezählt werden können.

2. Das nested Mapping als Voraussetzung

Damit eine Nested Aggregation überhaupt funktioniert, muss das betroffene Feld im Mapping explizit als Typ nested deklariert werden, nicht als einfaches object. Der Unterschied ist fundamental: Ein object-Feld wird intern zu einem flachen Dokument abgeflacht, ein nested-Feld erzeugt für jedes Array-Element ein separates, verstecktes Lucene-Dokument, das über einen internen Join-Mechanismus mit dem Parent-Dokument verbunden bleibt. Das kostet beim Indexieren mehr Speicher und Zeit, ist aber die einzig korrekte Lösung, sobald Beziehungen zwischen Feldern innerhalb eines Array-Elements erhalten bleiben müssen.

Ein häufiger Fehler ist, ein bereits bestehendes object-Mapping nachträglich zu nested ändern zu wollen: Das ist ohne Reindexierung nicht möglich, weil sich die interne Lucene-Dokumentstruktur ändert. Wer facettierte Suche auf Varianten plant, sollte das nested-Mapping deshalb von Beginn an im Index-Template festlegen, bevor produktive Daten eingespielt werden.


PUT /products
{
  "mappings": {
    "properties": {
      "name": { "type": "text" },
      "variants": {
        "type": "nested",
        "properties": {
          "sku": { "type": "keyword" },
          "size": { "type": "keyword" },
          "color": { "type": "keyword" },
          "price": { "type": "float" },
          "in_stock": { "type": "boolean" }
        }
      }
    }
  }
}
// "nested" preserves the relation between size, color and
// price within a single variant, unlike a plain "object" field

3. Die nested Aggregation im Detail

Die Nested Aggregation selbst erzeugt keinen eigenen Bucket im klassischen Sinn, sondern öffnet einen neuen Auswertungskontext: Alle Sub-Aggregationen innerhalb einer nested-Aggregation operieren nicht mehr auf den Parent-Dokumenten, sondern auf den versteckten Kind-Dokumenten des angegebenen Pfads. Erst innerhalb dieses Kontexts liefert eine Terms Aggregation auf variants.color korrekte, isolierte Zählwerte, weil sie ausschließlich die tatsächlichen Varianten-Dokumente sieht, ohne Vermischung durch die Parent-Struktur.

Wichtig ist, dass der path-Parameter exakt dem im Mapping definierten Pfad des nested-Feldes entsprechen muss. Bei mehrstufig verschachtelten Strukturen, etwa Varianten mit wiederum verschachtelten Lagerbeständen pro Standort, lassen sich Nested Aggregationen ineinander schachteln, wobei jede Ebene ihren eigenen path deklariert. Diese Verschachtelung folgt derselben Logik wie normale Bucket-Aggregationen, arbeitet aber auf einem jeweils tieferen Lucene-Dokument-Kontext.


POST /products/_search
{
  "size": 0,
  "aggs": {
    "variant_colors": {
      "nested": { "path": "variants" },
      "aggs": {
        "colors": {
          "terms": { "field": "variants.color", "size": 20 }
        },
        "avg_variant_price": {
          "avg": { "field": "variants.price" }
        }
      }
    }
  }
}
// All sub-aggregations here operate on variant documents,
// not on the parent product document

4. reverse_nested: zurück zum Parent-Kontext

Innerhalb einer Nested Aggregation hat man keinen direkten Zugriff mehr auf Felder des Parent-Dokuments, etwa die Produktkategorie oder die Marke. Genau hier setzt reverse_nested an: Diese Aggregation springt aus dem nested-Kontext zurück in den Kontext des Parent-Dokuments, ohne den umgebenden Bucket zu verlassen. Damit lässt sich beantworten, wie viele eindeutige Produkte (nicht Varianten) eine bestimmte Farbe überhaupt anbieten, ein Unterschied, der bei Produkten mit mehreren Varianten derselben Farbe entscheidend ist.

Ohne reverse_nested würde eine Zählung innerhalb des nested-Kontexts immer die Anzahl der Varianten liefern, nicht die Anzahl der Produkte. Bei einem Produkt mit drei blauen Varianten in unterschiedlichen Größen zählt eine einfache Terms Aggregation auf variants.color drei Treffer für Blau, obwohl es sich um ein einziges Produkt handelt. Mit reverse_nested und einer verschachtelten cardinality-Aggregation auf die Parent-ID lässt sich diese Verzerrung korrigieren und die tatsächliche Produktanzahl pro Farbe ermitteln.


POST /products/_search
{
  "size": 0,
  "aggs": {
    "variant_colors": {
      "nested": { "path": "variants" },
      "aggs": {
        "colors": {
          "terms": { "field": "variants.color", "size": 20 },
          "aggs": {
            "distinct_products": {
              "reverse_nested": {},
              "aggs": {
                "product_count": {
                  "cardinality": { "field": "_id" }
                }
              }
            }
          }
        }
      }
    }
  }
}
// reverse_nested jumps back to the parent document context
// distinct_products counts actual products, not variants

5. Facettierte Navigation mit Produktvarianten

In der Praxis kombiniert eine facettierte Kategorieseite mehrere Nested Aggregationen gleichzeitig: eine für Farben, eine für Größen, oft eine dritte für Verfügbarkeit. Jede dieser Facetten muss unabhängig von der Auswahl der anderen berechnet werden, damit ein Nutzer, der bereits "Größe M" ausgewählt hat, in der Farbfacette weiterhin alle Farben sieht, die für Größe M verfügbar sind, nicht nur die für die aktuell gefilterte Kombination. Dieses Muster nennt sich in der Elasticsearch-Community oft "sticky facets" und wird durch eine Kombination aus post_filter und mehreren parallelen nested-Aggregationszweigen realisiert, von denen jeder einen eigenen, gezielt reduzierten Filter im nested-Kontext trägt.

Ein praktisches Beispiel: Die Größenfacette soll die Farbauswahl berücksichtigen, aber nicht die eigene Größenauswahl. Dazu wird innerhalb der nested-Aggregation ein filter-Aggregations-Block eingefügt, der nur nach Farbe filtert, während die äußere Query alle anderen Filter unabhängig von der Facette anwendet. Diese Technik erlaubt es, in einer einzigen Anfrage mehrere unabhängig gefilterte Facetten-Zweige zu berechnen, ohne mehrere Requests an den Cluster senden zu müssen.

6. Filter innerhalb nested Aggregationen

Um Attributkombinationen innerhalb einer einzelnen Variante korrekt zu filtern, etwa "Größe M UND Farbe Blau gleichzeitig in derselben Variante", muss der Filter selbst als nested-Query formuliert werden, nicht als einfacher bool.filter auf Top-Level. Eine nested-Query mit bool.must auf variants.size und variants.color garantiert, dass beide Bedingungen auf demselben Kind-Dokument erfüllt sein müssen, während zwei separate Top-Level-Filter auch dann matchen würden, wenn Größe und Farbe aus unterschiedlichen Varianten desselben Produkts stammen.


POST /products/_search
{
  "query": {
    "nested": {
      "path": "variants",
      "query": {
        "bool": {
          "must": [
            { "term": { "variants.size": "M" } },
            { "term": { "variants.color": "blue" } },
            { "term": { "variants.in_stock": true } }
          ]
        }
      }
    }
  },
  "aggs": {
    "variant_colors": {
      "nested": { "path": "variants" },
      "aggs": {
        "colors": { "terms": { "field": "variants.color" } }
      }
    }
  }
}
// The nested query guarantees size, color and stock status
// match within the SAME variant document

7. Performance-Fallstricke bei nested Feldern

Jedes nested-Objekt erzeugt ein zusätzliches verstecktes Lucene-Dokument, das im selben Segment wie das Parent-Dokument gespeichert wird. Ein Produkt mit fünfzig Varianten belegt damit einundfünfzig Lucene-Dokumente statt einem. Bei großen Produktkatalogen mit vielen Varianten pro Produkt kann das die Gesamtzahl der Lucene-Dokumente im Index um ein Vielfaches erhöhen, was Indexgröße, Merge-Aufwand und Speicherverbrauch entsprechend steigert. Elasticsearch begrenzt deshalb standardmäßig die Anzahl nested-Dokumente pro Parent auf 10000 über index.mapping.nested_objects.limit, eine Grenze, die bei sehr variantenreichen Katalogen bewusst geprüft werden sollte.

Ein zweiter Performance-Aspekt betrifft die Join-Berechnung selbst: Jede Nested Aggregation und jede nested Query muss zur Laufzeit die Beziehung zwischen Parent- und Kind-Dokumenten auflösen, was zusätzliche CPU-Zyklen im Vergleich zu einer flachen Aggregation kostet. Bei Kategorieseiten mit hoher Trefferzahl und mehreren parallelen Facetten-Zweigen sollte deshalb gemessen werden, ob die Antwortzeit noch akzeptabel bleibt, und gegebenenfalls die Anzahl der gleichzeitig berechneten Facetten reduziert werden.

8. nested vs. flattened: wann welches Mapping

Nicht jede Array-Struktur benötigt zwingend nested. Der Feldtyp flattened speichert ein komplettes JSON-Objekt als eine einzige, unstrukturierte Einheit und eignet sich für Daten mit unbekanntem oder stark variierendem Schema, bei denen keine Beziehungen zwischen einzelnen Unterfeldern über eine Aggregation ausgewertet werden müssen. Für Produktvarianten mit festem Schema und dem klaren Bedarf, Facetten korrekt auf Kombinationen von Größe, Farbe und Preis zu berechnen, ist nested jedoch praktisch alternativlos.

Eine dritte Option ist die Denormalisierung: Statt Varianten als Array im Produkt-Dokument zu halten, wird jede Variante als eigenständiges Dokument in einem separaten Index gespeichert, mit einer Referenz auf das Parent-Produkt. Diese Strategie vermeidet die nested-Join-Kosten vollständig, erfordert aber eigene Aggregations-Queries auf dem Varianten-Index und eine zusätzliche Verknüpfungslogik auf Anwendungsebene, wenn Produkt- und Variantenebene gemeinsam dargestellt werden müssen.


// flattened: entire object as one opaque unit, no relations kept
PUT /reviews
{
  "mappings": {
    "properties": {
      "raw_attributes": { "type": "flattened" }
    }
  }
}
// Good for unknown/variable schema, no relation between sub-fields

// nested: preserves relations, required for correct facets
PUT /products
{
  "mappings": {
    "properties": {
      "variants": {
        "type": "nested",
        "properties": {
          "size": { "type": "keyword" },
          "color": { "type": "keyword" }
        }
      }
    }
  }
}
// Choose nested when sub-field combinations must be queryable

9. Nested vs. Non-Nested Aggregation im Vergleich

Die folgende Tabelle stellt gegenüber, wie sich Aggregationen auf object-Feldern und nested-Feldern unterscheiden.

Aspekt object-Aggregation Nested Aggregation
Beziehung zwischen Feldern Geht beim Indexieren verloren Bleibt pro Array-Element erhalten
Lucene-Dokumente Ein Dokument pro Parent Ein Dokument pro Array-Element plus Parent
Aggregations-Syntax Direkte terms/range-Aggregation Erfordert nested-Wrapper mit path
Zugriff auf Parent-Felder Direkt verfügbar Nur über reverse_nested
Index-Größe Kleiner Größer durch zusätzliche Kind-Dokumente
Korrektheit bei Facetten Fehlerhaft bei kombinierten Attributen Korrekt, Attribut-Kombination bleibt erhalten

Die Entscheidung für nested ist damit ein bewusster Trade-off zwischen Indexgröße und Query-Komplexität auf der einen Seite und Korrektheit der Facettenzählung auf der anderen. Für Produktvarianten mit mehreren gemeinsam relevanten Attributen gibt es praktisch keine Alternative, die dieselbe Genauigkeit liefert.

Mironsoft

Facettierte Suche, Produktvarianten und Elasticsearch-Mapping-Design

Facetten, die auf Produktvarianten korrekt zählen?

Wir entwerfen nested Mappings und Aggregations-Queries für variantenreiche Kataloge, korrigieren fehlerhafte Facettenzählungen und optimieren die Performance komplexer facettierter Suchseiten.

Mapping-Design

nested vs. flattened vs. Denormalisierung fachlich bewerten

Facetten-Fixes

reverse_nested und sticky facets für korrekte Zählungen einbauen

Performance-Tuning

nested-Dokumentanzahl und Query-Zeiten bei großen Katalogen optimieren

10. Zusammenfassung

Die Nested Aggregation ist die einzige korrekte Lösung, um Facetten auf Feldern zu berechnen, die als Array von Objekten mit mehreren zusammengehörigen Attributen modelliert sind, etwa Produktvarianten mit Größe, Farbe und Preis. Voraussetzung ist ein nested-Mapping statt eines einfachen object-Feldes, das für jedes Array-Element ein eigenes, verstecktes Lucene-Dokument anlegt und dadurch die Beziehung zwischen den Attributen einer einzelnen Variante erhält. Innerhalb der nested-Aggregation liefern Terms-, Range- oder Metric-Aggregationen korrekte, isolierte Werte auf Varianten-Ebene.

Mit reverse_nested lässt sich zurück in den Parent-Kontext springen, etwa um die tatsächliche Anzahl eindeutiger Produkte statt der Anzahl von Varianten zu zählen. Für Filter-Queries auf Kombinationen mehrerer Variantenattribute muss zusätzlich eine nested-Query verwendet werden, damit die Bedingungen garantiert auf demselben Kind-Dokument geprüft werden. Der Preis dieser Korrektheit ist ein größerer Index und zusätzlicher Join-Aufwand zur Laufzeit, der bei sehr variantenreichen Katalogen gemessen und im Zweifel gegen Alternativen wie Denormalisierung abgewogen werden sollte.

Nested Aggregation für facettierte Suche, das Wichtigste auf einen Blick

nested Mapping

Pflicht für korrekte Facetten auf Array-Feldern mit zusammengehörigen Attributen wie Größe und Farbe.

nested Aggregation

Öffnet den Kind-Dokument-Kontext, Sub-Aggregationen zählen dann korrekt auf Varianten-Ebene.

reverse_nested

Springt zurück in den Parent-Kontext, wichtig für eindeutige Produktzählungen statt Variantenzählungen.

Kosten

Größerer Index durch zusätzliche Lucene-Dokumente, mehr CPU für Join-Auflösung zur Laufzeit.

11. FAQ: Nested Aggregationen für facettierte Suche

1Warum liefert Terms Aggregation auf Varianten falsche Facetten?
object-Felder verschmelzen Werte aller Varianten zu einer flachen Liste. Die Beziehung zwischen Größe und Farbe geht verloren, Facetten kombinieren falsch.
2Muss nested vorher geplant werden?
Ja, object lässt sich nicht ohne Reindexierung zu nested wandeln. Mapping vor dem Import produktiver Daten festlegen.
3Was macht reverse_nested?
Springt zurück in den Parent-Kontext innerhalb der nested Aggregation, ohne den Bucket zu verlassen. So werden Parent-Felder wieder auswertbar.
4Eindeutige Produkte statt Varianten zählen?
reverse_nested plus cardinality auf die Parent-ID. Jedes Produkt wird nur einmal gezählt, egal wie viele passende Varianten es hat.
5Kombination mehrerer Variantenattribute filtern?
Mit einer nested Query. Nur so gelten alle Bedingungen garantiert für dasselbe Kind-Dokument, nicht für unterschiedliche Varianten.
6Maximale Anzahl nested Objekte pro Dokument?
Standardmäßig 10000, über index.mapping.nested_objects.limit steuerbar. Bei sehr variantenreichen Produkten prüfen und anpassen.
7Ist nested immer richtig für Array-Felder?
Nein. flattened für unbekanntes Schema, Denormalisierung bei extrem vielen Varianten. nested lohnt sich bei Beziehungen zwischen Unterfeldern.
8Warum sind sticky facets bei nested komplexer?
Jede Facette braucht einen eigenen nested-Zweig mit gezielt reduziertem Filter, damit die eigene Auswahl die Facette selbst nicht einschränkt.
9Wirkt sich nested auf die Indexgröße aus?
Ja, jedes Array-Element erzeugt ein zusätzliches Lucene-Dokument. Fünfzig Varianten bedeuten einundfünfzig Dokumente statt einem.
10Mehrere nested Ebenen verschachteln?
Möglich, etwa Varianten mit Lagerbeständen pro Standort. Jede Ebene braucht einen eigenen path-Parameter und erhöht die Query-Kosten.