der entscheidende Unterschied bei Arrays von Objekten
Ein Array von Objekten sieht in JSON harmlos aus, wird aber je nach gewaehltem Feldtyp intern vollkommen unterschiedlich gespeichert. Der Standardtyp object macht die Struktur flach und verliert dabei die Zuordnung zwischen Feldern verschiedener Array-Eintraege, was zu Treffern fuehrt, die eigentlich keine sein duerften. Nested bewahrt genau diese Beziehung, kostet dafuer aber mehr Ressourcen.
Inhaltsverzeichnis
- 1. Warum Arrays von Objekten in Elasticsearch besonders sind
- 2. Wie der Typ object Arrays intern flach macht
- 3. Cross-Object Matching: der klassische Bug in der Praxis
- 4. Wie der Typ nested Beziehungen bewahrt
- 5. Die nested-Query: Objekte gezielt einzeln abfragen
- 6. Aggregation ueber nested-Felder
- 7. Performance- und Speicherkosten von nested
- 8. Entscheidungskriterien: wann object, wann nested
- 9. Praxisbeispiel: Produktvarianten korrekt modellieren
- 10. Zusammenfassung
- 11. FAQ
1. Warum Arrays von Objekten in Elasticsearch besonders sind
Elasticsearch basiert intern auf Lucene, und Lucene kennt in seiner Grundstruktur keine echten verschachtelten Dokumente, sondern nur flache Wertelisten pro Feld. Ein einzelnes Objekt in einem Dokument laesst sich problemlos abbilden, aber ein Array von Objekten, etwa mehrere Varianten eines Produkts mit jeweils eigener Farbe und Groesse, stellt Elasticsearch vor eine grundlegende Designentscheidung: Wie soll die Beziehung zwischen den Feldern innerhalb jedes einzelnen Array-Elements gespeichert werden?
Genau hier unterscheiden sich die beiden Feldtypen object und nested fundamental, obwohl beide auf den ersten Blick dieselbe JSON-Struktur akzeptieren. Wer diesen Unterschied nicht kennt, baut haeufig ein Mapping, das beim ersten Testdokument problemlos funktioniert und erst bei mehreren Array-Eintraegen mit unterschiedlichen Werten unerwartete Suchtreffer produziert. Dieser Artikel erklaert, was intern passiert, wann der Unterschied tatsaechlich relevant wird und wie man die richtige Wahl trifft.
2. Wie der Typ object Arrays intern flach macht
Der Standardtyp object speichert ein Array von Objekten, indem er fuer jedes Feld innerhalb der Objekte eine eigene, flache Werteliste anlegt. Ein Dokument mit einem Array aus zwei Varianten, eine mit Farbe "rot" und Groesse "S", eine mit Farbe "blau" und Groesse "L", wird intern in etwa so gespeichert: Das Feld farbe enthaelt die Werte ["rot", "blau"] und das Feld groesse enthaelt ["S", "L"], aber ohne jede Information, welcher Farbwert zu welchem Groessenwert gehoert.
Diese Flachheit ist keine willkuerliche Einschraenkung, sondern eine direkte Folge davon, wie Lucene Dokumente indexiert: Ein Lucene-Dokument besteht aus benannten Feldern mit jeweils einer Liste von Werten, es gibt keine native Unterstuetzung fuer verschachtelte Strukturen mit erhaltener Zuordnung. Elasticsearch simuliert verschachtelte JSON-Strukturen fuer den Typ object, indem es die Feldpfade einfach zusammenfuehrt, ohne die urspruengliche Gruppierung zu bewahren. Fuer viele Anwendungsfaelle ist das unproblematisch, aber sobald eine Query mehrere Felder innerhalb desselben Array-Elements gleichzeitig einschraenken soll, wird die verlorene Zuordnung zum Problem.
PUT /products
{
"mappings": {
"properties": {
"name": { "type": "text" },
"variants": { "type": "object" }
}
}
}
POST /products/_doc
{
"name": "T-Shirt",
"variants": [
{ "color": "red", "size": "S" },
{ "color": "blue", "size": "L" }
]
}
// Internally flattened, roughly equivalent to:
// variants.color -> ["red", "blue"]
// variants.size -> ["S", "L"]
// The link between red+S and blue+L is lost
3. Cross-Object Matching: der klassische Bug in der Praxis
Das direkte Ergebnis der Flachheit von object ist ein Phaenomen, das in der Elasticsearch-Community als Cross-Object Matching bekannt ist. Eine Query, die nach "Farbe rot UND Groesse L" filtert, sollte im Beispiel aus dem vorherigen Abschnitt kein Ergebnis liefern, weil keine der beiden Varianten diese Kombination hat. Mit dem Typ object liefert dieselbe Query aber sehr wohl einen Treffer, weil "rot" und "L" beide irgendwo in den jeweiligen flachen Wertelisten vorkommen, auch wenn sie zu unterschiedlichen Array-Elementen gehoeren.
Dieser Bug ist besonders tueckisch, weil er sich erst bei realistischen Datenmengen mit mehreren Array-Eintraegen pro Dokument zeigt. Ein Testdokument mit nur einer Variante funktioniert immer korrekt, weil es keine zweite Kombination gibt, mit der sich die Werte vermischen koennten. Erst in Produktion, wenn Produkte mit drei, fuenf oder zehn Varianten indexiert werden, tauchen ploetzlich Suchtreffer auf, die inhaltlich keinen Sinn ergeben, und das Team steht vor einem schwer nachvollziehbaren Bug, der tief im Feldtyp verwurzelt ist, nicht in der Query-Logik.
// This query with type "object" WRONGLY matches the T-Shirt document
// because "red" and "L" both exist somewhere in the flattened arrays,
// even though they belong to different variants
GET /products/_search
{
"query": {
"bool": {
"must": [
{ "match": { "variants.color": "red" } },
{ "match": { "variants.size": "L" } }
]
}
}
}
4. Wie der Typ nested Beziehungen bewahrt
Der Feldtyp nested loest das Problem, indem er jedes Element eines Arrays als eigenes, verstecktes Lucene-Dokument innerhalb desselben Segments speichert, statt die Felder flach zusammenzufuehren. Intern entsteht so ein Hauptdokument fuer das Produkt und ein separates verstecktes Dokument pro Variante, jeweils mit vollstaendig erhaltener Zuordnung zwischen Farbe und Groesse innerhalb dieses einen Elements. Diese versteckten Dokumente sind ueber einen internen Block-Join-Mechanismus mit dem Hauptdokument verknuepft und werden bei normalen Queries nicht direkt sichtbar.
Diese Architektur ist der Grund, warum nested Cross-Object Matching zuverlaessig verhindert: Eine Abfrage gegen ein nested-Feld wird immer gegen genau eines dieser versteckten Dokumente ausgewertet, niemals gegen eine flache, vermischte Werteliste. Der Preis dafuer ist struktureller Natur: Jedes zusaetzliche Array-Element erzeugt ein zusaetzliches Lucene-Dokument, was sich direkt auf Speicherverbrauch und Indexierungsaufwand auswirkt, besonders bei Arrays mit vielen Eintraegen.
PUT /products
{
"mappings": {
"properties": {
"name": { "type": "text" },
"variants": {
"type": "nested",
"properties": {
"color": { "type": "keyword" },
"size": { "type": "keyword" }
}
}
}
}
}
// Same source document, now correctly kept as separate hidden documents
POST /products/_doc
{
"name": "T-Shirt",
"variants": [
{ "color": "red", "size": "S" },
{ "color": "blue", "size": "L" }
]
}
5. Die nested-Query: Objekte gezielt einzeln abfragen
Ein nested-Feld kann nicht mit den ueblichen Query-Klauseln wie match oder term direkt abgefragt werden, weil diese fuer flache Felder konzipiert sind. Stattdessen ist die spezielle nested-Query erforderlich, die einen path-Parameter mit dem Namen des nested-Feldes und eine innere query erwartet, die ausschliesslich innerhalb eines einzelnen versteckten Dokuments ausgewertet wird. Genau diese Kapselung ist der Mechanismus, der Cross-Object Matching zuverlaessig verhindert.
Mehrere Bedingungen innerhalb derselben nested-Query, etwa ueber eine bool-Klausel mit mehreren must-Bedingungen, muessen alle innerhalb desselben Array-Elements erfuellt sein, um einen Treffer zu erzeugen. Werden dagegen zwei separate nested-Queries mit demselben path in einem uebergeordneten bool kombiniert, koennen sie wieder gegen unterschiedliche Array-Elemente matchen, weil jede nested-Query fuer sich genommen ausgewertet wird. Dieses Detail ist eine haeufige Fehlerquelle, selbst wenn der Feldtyp korrekt auf nested gesetzt wurde.
// Correct: both conditions must match within the SAME variant
GET /products/_search
{
"query": {
"nested": {
"path": "variants",
"query": {
"bool": {
"must": [
{ "term": { "variants.color": "red" } },
{ "term": { "variants.size": "L" } }
]
}
}
}
}
}
// Correctly returns zero hits for the T-Shirt document
6. Aggregation ueber nested-Felder
Auch Aggregationen ueber nested-Felder benoetigen eine eigene Syntax, weil die versteckten Dokumente ausserhalb des normalen Aggregationskontexts liegen. Die nested-Aggregation ist der Einstiegspunkt, der den Kontext auf den angegebenen Pfad umschaltet, innerhalb dessen dann normale Sub-Aggregationen wie terms oder avg auf den Feldern der versteckten Dokumente arbeiten koennen. Ohne diese Umschaltung waeren die Felder innerhalb eines nested-Objekts fuer Aggregationen gar nicht erreichbar.
Ein haeufiger Anwendungsfall ist das Zaehlen, wie oft jede Farbe ueber alle Varianten aller Produkte vorkommt, waehrend gleichzeitig eine Filterbedingung auf Dokumentebene aktiv bleibt, etwa nur fuer Produkte einer bestimmten Kategorie. Die reverse_nested-Aggregation erlaubt zusaetzlich, aus dem nested-Kontext wieder zurueck auf das Hauptdokument zu wechseln, was fuer verschachtelte Auswertungen ueber mehrere Ebenen wichtig wird, etwa wenn ein Produkt sowohl Varianten als auch Kategorien als separate nested-Felder besitzt.
7. Performance- und Speicherkosten von nested
Nested loest das Cross-Object-Matching-Problem strukturell korrekt, ist dafuer aber nicht kostenlos. Jedes Array-Element erzeugt ein zusaetzliches verstecktes Lucene-Dokument, was bei Arrays mit hunderten Eintraegen pro Hauptdokument den Speicherverbrauch und die Indexierungszeit spuerbar erhoehen kann. Elasticsearch begrenzt deshalb standardmaessig ueber index.mapping.nested_objects.limit die maximale Anzahl an nested-Objekten pro Dokument, um extreme Faelle abzufangen.
Auch Queries gegen nested-Felder sind rechenintensiver als gegen flache Felder, weil der Block-Join-Mechanismus zur Laufzeit zwischen Hauptdokument und versteckten Dokumenten navigieren muss. Fuer Arrays mit wenigen Eintraegen, etwa bis zu einer niedrigen zweistelligen Zahl, ist dieser Mehraufwand in der Praxis meist vernachlaessigbar. Bei sehr grossen Arrays lohnt sich dagegen eine genaue Pruefung, ob die Korrektheit von nested tatsaechlich gebraucht wird oder ob eine alternative Modellierung, etwa ein separater verknuepfter Index mit join-Feldtyp oder Denormalisierung, die bessere Wahl ist.
8. Entscheidungskriterien: wann object, wann nested
Die Entscheidung zwischen object und nested haengt in erster Linie davon ab, ob Queries jemals mehrere Felder innerhalb desselben Array-Elements gleichzeitig einschraenken muessen. Wenn ein Array nur ein einziges Feld enthaelt, etwa eine einfache Liste von Tags, entsteht das Cross-Object-Matching-Problem gar nicht, weil es keine zweite Dimension gibt, mit der sich Werte vermischen koennten. In diesem Fall ist object oder sogar ein einfaches keyword-Array ausreichend und deutlich guenstiger.
Sobald ein Array aus Objekten mit zwei oder mehr Feldern besteht und eine realistische Query beide Felder gleichzeitig gegeneinander pruefen koennte, etwa Farbe und Groesse einer Variante oder Preis und Gueltigkeitsdatum eines Rabatts, ist nested fast immer die richtige Wahl, sobald mehr als ein Array-Element pro Dokument vorkommen kann. Die einzige Ausnahme sind Faelle, in denen die Datenmenge pro Array so gross ist, dass die Performance-Kosten von nested den Korrektheitsgewinn uebersteigen, was eine bewusste Kompromissentscheidung erfordert, keine automatische.
Fuer Magento-Betreiber mit dem Elasticsearch-Katalogindex ist relevant, dass konfigurierbare Produkte mit mehreren Optionen intern aehnliche Strukturen verwenden koennen. Wer eigene Erweiterungen baut, die Attributkombinationen ueber mehrere Dimensionen filtern muessen, etwa Farbe und Groesse gemeinsam, sollte pruefen, ob die zugrunde liegende Struktur tatsaechlich als nested modelliert ist, bevor unerwartete Cross-Object-Treffer als Bug in der eigenen Suchlogik missverstanden werden.
9. Praxisbeispiel: Produktvarianten korrekt modellieren
Ein vollstaendiges Beispiel zeigt die praktische Umsetzung. Ein Produktkatalog mit Varianten, die jeweils Farbe, Groesse und einen variantenspezifischen Preis besitzen, muss Anfragen wie "verfuegbar in rot und Groesse M unter 30 Euro" korrekt beantworten koennen. Mit object wuerde eine solche Anfrage regelmaessig falsche Treffer liefern, sobald ein Produkt mehrere Varianten mit unterschiedlichen Farb-Groesse-Preis-Kombinationen hat. Mit nested und der passenden nested-Query bleibt die Zuordnung korrekt, unabhaengig davon, wie viele Varianten ein Produkt hat.
Der komplette Umbau von object zu nested erfordert, wie jede Aenderung eines Feldtyps, einen Reindex, weil Feldtypen im Mapping unveraenderlich sind. Wer bereits weiss, dass ein Array mehrere zusammengehoerige Felder enthaelt und mehr als ein Element pro Dokument realistisch ist, sollte deshalb von Anfang an nested waehlen, statt spaeter unter Produktionsdruck einen aufwendigen Reindex durchfuehren zu muessen, nachdem fehlerhafte Suchtreffer erstmals aufgefallen sind.
| Kriterium | object |
nested |
|---|---|---|
| Interne Speicherung | Flache Werteliste pro Feld | Separates verstecktes Dokument je Element |
| Cross-Object Matching | Tritt auf, kann falsche Treffer erzeugen | Wird strukturell verhindert |
| Query-Syntax | Normale Query-Klauseln | Erfordert nested-Query mit path |
| Speicher- und Query-Kosten | Geringer | Hoeher, waechst mit Array-Groesse |
| Geeignet fuer | Einfache Arrays, einzelnes Feld pro Element | Mehrere zusammengehoerige Felder pro Element |
Diese Gegenueberstellung macht die Kernentscheidung greifbar: Sobald Felder innerhalb eines Array-Elements gemeinsam abgefragt werden muessen, ist nested die einzige strukturell korrekte Wahl, waehrend object fuer einfachere Faelle ressourcenschonender bleibt.
10. Zusammenfassung
Der Unterschied zwischen object und nested ist keine stilistische Detailfrage, sondern entscheidet darueber, ob Abfragen ueber Arrays von Objekten korrekte Ergebnisse liefern. Der Typ object macht Arrays intern flach und verliert dabei die Zuordnung zwischen Feldern verschiedener Array-Elemente, was zu Cross-Object Matching und damit zu inhaltlich falschen Suchtreffern fuehrt, sobald ein Dokument mehr als ein Array-Element enthaelt.
Nested speichert jedes Array-Element als eigenes, verstecktes Lucene-Dokument und bewahrt so die Beziehung zwischen den Feldern, erfordert dafuer aber die spezielle nested-Query-Syntax und verursacht mehr Speicher- und Indexierungsaufwand. Die richtige Wahl haengt davon ab, ob Queries jemals mehrere Felder innerhalb desselben Elements gleichzeitig pruefen muessen, eine Entscheidung, die am besten vor dem ersten produktiven Import getroffen wird, weil ein spaeterer Wechsel einen vollstaendigen Reindex erfordert.
Nested vs. Object, das Wichtigste auf einen Blick
object macht flach
Felder aus verschiedenen Array-Elementen werden intern zu getrennten flachen Wertelisten, Zuordnung geht verloren.
Cross-Object Matching
Der klassische Bug: Werte aus unterschiedlichen Array-Elementen matchen faelschlich gemeinsam.
nested bewahrt Beziehungen
Jedes Array-Element wird als eigenes verstecktes Dokument gespeichert, Query mit path erforderlich.
Vor dem Import entscheiden
Ein Wechsel des Feldtyps erfordert Reindex, deshalb die Wahl vor dem ersten produktiven Import treffen.