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.
Inhaltsverzeichnis
- 1. Warum facettierte Suche nested Aggregationen braucht
- 2. Das nested Mapping als Voraussetzung
- 3. Die nested Aggregation im Detail
- 4. reverse_nested: zurück zum Parent-Kontext
- 5. Facettierte Navigation mit Produktvarianten
- 6. Filter innerhalb nested Aggregationen
- 7. Performance-Fallstricke bei nested Feldern
- 8. nested vs. flattened: wann welches Mapping
- 9. Nested vs. Non-Nested Aggregation im Vergleich
- 10. Zusammenfassung
- 11. FAQ
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.