zwei unterschiedliche Caching-Ebenen für Aggregationen und Queries
Elasticsearch bringt zwei unterschiedliche, oft verwechselte Caching-Mechanismen mit, die beide die Antwortzeit wiederkehrender Anfragen verbessern, dabei aber auf völlig unterschiedlichen Ebenen ansetzen: der Shard-Level Request Cache, der vollständige Suchantworten inklusive Aggregationen node-seitig zwischenspeichert, und der Node Query Cache, oft auch Shard Query Cache genannt, der einzelne, wiederkehrende Filterklauseln auf Shard-Ebene cacht. Wer für eine facettenlastige Magento-Shop-Seite mit Layered Navigation die Performance verbessern will, muss verstehen, welcher der beiden Caches in welcher Situation tatsächlich greift, wie beide bei Schreiboperationen invalidiert werden und wie sich die Query-Struktur gezielt so gestalten lässt, dass beide Caching-Ebenen optimal genutzt werden.
Inhaltsverzeichnis
- 1. Zwei verschiedene Caches, die oft verwechselt werden
- 2. Der Shard-Level Request Cache im Detail
- 3. Der Node Query Cache im Detail
- 4. Wann greift welcher Cache: Query-Struktur entscheidet
- 5. Invalidierungsverhalten bei Schreiboperationen
- 6. Praktische Auswirkung auf facettenlastige Shop-Seiten
- 7. Cache-Größe konfigurieren und Hit-Rate messen
- 8. Praktische Optimierung: Query-Struktur für bessere Cache-Nutzung
- 9. Fallstricke: Real-Time-Suche versus Cache-Effektivität
- 10. Zusammenfassung
- 11. FAQ
1. Zwei verschiedene Caches, die oft verwechselt werden
Der Begriff Caching wird in Elasticsearch für zwei strukturell verschiedene Mechanismen verwendet, die sich sowohl in dem, was sie speichern, als auch darin unterscheiden, wann sie überhaupt greifen. Der Shard-Level Request Cache speichert die vollständige, fertig berechnete JSON-Antwort einer Suchanfrage, einschließlich Aggregationsergebnissen, während der Node Query Cache lediglich das Ergebnis einzelner Filterklauseln in Form einer effizient komprimierten Bitmenge auf Segment-Ebene vorhält.
Diese Unterscheidung ist entscheidend, weil beide Caches unterschiedliche Anfragemuster begünstigen: Der Request Cache spielt seine Stärke vor allem bei identischen, wiederholt ausgeführten Aggregations-Anfragen aus, während der Query Cache selbst bei leicht unterschiedlichen Gesamtanfragen greifen kann, solange eine bestimmte, wiederverwendbare Filterklausel Teil der jeweiligen Anfrage ist.
2. Der Shard-Level Request Cache im Detail
Der Request Cache arbeitet auf Node-Ebene pro Shard und speichert das Ergebnis einer Suchanfrage nur dann, wenn size auf 0 gesetzt ist, also keine einzelnen Treffer-Dokumente zurückgegeben werden, sondern ausschließlich Aggregationsergebnisse und die Gesamt-Trefferzahl interessieren. Dieses Verhalten ist bewusst so gewählt, weil Aggregationen häufig über sehr viele Dokumente hinweg berechnet werden und damit vergleichsweise teuer, aber dennoch oft identisch wiederholt abgefragt werden.
Der Cache-Schlüssel besteht aus dem exakten Anfrage-Body kombiniert mit dem Shard-Zustand zum Zeitpunkt der Anfrage. Zwei syntaktisch unterschiedliche, aber semantisch identische Anfragen, etwa mit vertauschter Reihenfolge der JSON-Felder, führen in der Praxis zu unterschiedlichen Cache-Einträgen, weshalb eine konsistente, immer gleich aufgebaute Anfragestruktur für eine gute Trefferquote entscheidend ist.
GET produkte/_search
{
"size": 0,
"query": {
"term": { "kategorie_id": 42 }
},
"aggs": {
"marken": { "terms": { "field": "marke.keyword", "size": 20 } },
"preisspannen": {
"range": {
"field": "preis",
"ranges": [
{ "to": 50 },
{ "from": 50, "to": 150 },
{ "from": 150 }
]
}
}
}
}
3. Der Node Query Cache im Detail
Der Node Query Cache, häufig auch als Shard Query Cache bezeichnet, speichert nicht die gesamte Anfrage, sondern das Ergebnis einzelner, wiederverwendbarer Filterklauseln als Bitmenge pro Lucene-Segment. Nur Klauseln innerhalb eines filter-Kontexts, nicht innerhalb eines wertenden query-Kontexts mit Relevanz-Scoring, kommen für dieses Caching überhaupt infrage, weil nur dort das Ergebnis unabhängig von einer konkreten Score-Berechnung ist.
Elasticsearch cacht dabei nicht automatisch jede Filterklausel, sondern verwendet eine interne Heuristik, die Klauseln bevorzugt, die auf mindestens einem Achtel der Segmente eines Index ausgeführt wurden und damit eine gewisse Wiederverwendungswahrscheinlichkeit erwarten lassen. Selten ausgeführte oder sehr spezifische Filterklauseln landen dadurch gar nicht erst im Cache.
GET produkte/_search
{
"query": {
"bool": {
"must": { "match": { "name": "wanderschuhe" } },
"filter": [
{ "term": { "sichtbar": true } },
{ "term": { "kategorie_id": 42 } }
]
}
}
}
4. Wann greift welcher Cache: Query-Struktur entscheidet
Ob der Request Cache greift, hängt vollständig davon ab, ob die komplette Anfrage inklusive aller Parameter identisch zu einer vorherigen Anfrage ist und size auf 0 gesetzt wurde. Bereits ein einziger unterschiedlicher Parameter, etwa ein anderer Wert im Suchbegriff oder eine andere Sortierrichtung, führt zu einem eigenen, neuen Cache-Eintrag, selbst wenn der überwiegende Teil der Anfrage identisch bleibt.
Der Query Cache dagegen greift auf Ebene einzelner Klauseln und kann deshalb selbst dann Vorteile bringen, wenn sich zwei Gesamtanfragen in anderen Teilen deutlich unterscheiden, solange eine bestimmte Filterklausel, etwa der Ausschluss deaktivierter Produkte, in beiden Anfragen identisch vorkommt. Eine bewusst konsistente, wiederverwendete Formulierung häufiger Filter erhöht die Trefferquote beider Caches gleichermaßen.
5. Invalidierungsverhalten bei Schreiboperationen
Beide Caches sind an den Lucene-Segment-Zustand eines Shards gebunden und werden bei jedem Refresh, der neue Segmente sichtbar macht, implizit invalidiert, da sich der zugrunde liegende Segment-Zustand ändert. Das bedeutet konkret: Auch ohne explizite Löschbefehle verlieren beide Caches bei jedem Refresh-Zyklus zumindest teilweise ihre bisherigen Einträge, weil neue oder veränderte Segmente eine neue Cache-Grundlage erfordern.
Bei einem Standard-Refresh-Intervall von einer Sekunde bedeutet das für stark schreibende Indizes, dass beide Caches praktisch permanent neu aufgebaut werden und selten eine hohe Trefferquote erreichen. Für vorwiegend lesende Workloads mit selteneren Schreiboperationen, wie es bei den meisten Magento-Produktindizes außerhalb der Reindex-Fenster der Fall ist, bleiben die Caches dagegen über deutlich längere Zeiträume gültig.
6. Praktische Auswirkung auf facettenlastige Shop-Seiten
Kategorieseiten mit Layered Navigation führen bei jedem Seitenaufruf typischerweise dieselbe Grundstruktur aus Aggregationen für Attribute, Preisspannen und Marken aus, kombiniert mit denselben, wiederkehrenden Basisfiltern wie der Kategorie-ID und dem Sichtbarkeits-Flag. Genau dieses Muster, viele identische oder stark überlappende Anfragen mit denselben Filterklauseln, begünstigt beide Caching-Ebenen gleichzeitig besonders stark.
Werden dagegen bei jeder Anfrage zusätzlich individuelle, kundenspezifische Faktoren wie personalisierte Preisregeln oder Kundengruppen-Rabatte direkt in dieselbe Aggregations-Anfrage eingebaut, sinkt die Trefferquote des Request Cache erheblich, weil praktisch jede Anfrage dadurch technisch einzigartig wird. Eine sauberere Trennung, bei der personalisierte Aspekte erst nach der gecachten Basisanfrage angewendet werden, erhält die Cache-Effektivität deutlich besser.
7. Cache-Größe konfigurieren und Hit-Rate messen
Die Größe des Request Cache lässt sich über die Node-Einstellung indices.requests.cache.size als Prozentsatz des Heaps konfigurieren, mit einem Standardwert von einem Prozent, der für sehr aggregationslastige Workloads oft zu knapp bemessen ist. Der Node Query Cache wird dagegen über indices.queries.cache.size gesteuert, standardmäßig auf zehn Prozent des Heaps eingestellt.
Die tatsächliche Trefferquote beider Caches lässt sich über die Indices-Stats-API pro Index auslesen, mit getrennten Zählern für Hits, Misses und die aktuell belegte Cache-Größe. Eine dauerhaft niedrige Trefferquote trotz eigentlich wiederkehrender Anfragen deutet meist auf eine zu inkonsistente Query-Struktur oder ein zu kurzes Refresh-Intervall hin, nicht zwingend auf eine zu klein konfigurierte Cache-Größe.
# Trefferquote des Request Cache und des Query Cache pro Index abfragen
curl "localhost:9200/produkte/_stats/request_cache,query_cache?pretty" \
| jq '.indices.produkte.total | {request_cache, query_cache}'
8. Praktische Optimierung: Query-Struktur für bessere Cache-Nutzung
Für eine gute Ausnutzung des Request Cache sollten Aggregations-Anfragen konsistent mit size gleich 0 formuliert werden, sobald tatsächlich nur die Aggregationsergebnisse und nicht die einzelnen Treffer-Dokumente benötigt werden, sowie mit einer stets identischen, nicht dynamisch neu zusammengesetzten JSON-Struktur, etwa durch eine feste Reihenfolge der Felder in der Anwendungsschicht.
Für den Query Cache lohnt es sich, häufig wiederkehrende, statische Filterbedingungen konsequent in einem separaten filter-Block statt im wertenden query-Teil zu platzieren, selbst wenn beide Varianten funktional zum selben Ergebnis führen würden. Nur die Variante im Filter-Kontext eröffnet dem Query Cache überhaupt die Möglichkeit, die betroffene Klausel unabhängig von Relevanz-Scoring wiederzuverwenden.
9. Fallstricke: Real-Time-Suche versus Cache-Effektivität
Ein häufiger Fallstrick besteht darin, ein sehr kurzes Refresh-Intervall für vermeintlich aktuellere Suchergebnisse zu konfigurieren, ohne zu bedenken, dass dadurch beide Caching-Ebenen ihre Grundlage ständig verlieren und effektiv kaum noch greifen. In den meisten Magento-Kontexten ist eine Verzögerung von wenigen Sekunden zwischen einer Datenänderung und ihrer Sichtbarkeit in der Suche völlig unkritisch und rechtfertigt selten den damit verbundenen Cache-Effektivitätsverlust.
Ein zweiter, subtilerer Fallstrick ist das versehentliche Vermischen von personalisierten und generischen Aggregationen in derselben Anfrage, wodurch selbst gut gemeinte Optimierungsversuche wirkungslos bleiben. Wer die Trennung zwischen generischen, gut cachebaren Basisdaten und individuellen, bewusst ungecachten Zusatzinformationen konsequent durchhält, profitiert von beiden Caching-Ebenen deutlich zuverlässiger.
| Aspekt | Shard-Level Request Cache | Node Query Cache | Praxisrelevanz |
|---|---|---|---|
| Speichert | Vollständige Aggregations-Antwort | Bitmenge einzelner Filterklauseln | Unterschiedliche Granularität |
| Voraussetzung | size gleich 0 in der Anfrage | Klausel im filter-Kontext | Beide erfordern bewusste Query-Struktur |
| Invalidierung | Bei jedem Segment-verändernden Refresh | Bei jedem Segment-verändernden Refresh | Beide vom Refresh-Intervall abhängig |
| Konfiguration | indices.requests.cache.size, ein Prozent Standard | indices.queries.cache.size, zehn Prozent Standard | Beide über Node-Settings anpassbar |
| Ideal für | Identische, wiederholte Aggregations-Anfragen | Wiederkehrende, statische Basisfilter | Layered Navigation profitiert von beiden |
Mironsoft
Suchindex-Setup, Relevanz-Tuning und Magento-Suche
Magento-Suche, die die falschen Produkte zuerst zeigt?
Wir richten Elasticsearch oder OpenSearch für Magento sauber ein, tunen Relevanz und Facetten auf das tatsächliche Sortiment und optimieren Indexierungsprozesse für große Kataloge.
Relevanz-Tuning
Suchergebnisse und Facetten auf die tatsächlichen Kundenbedürfnisse abstimmen.
Such-Migration
Umstieg von Solr oder MySQL-Suche auf Elasticsearch/OpenSearch sauber begleiten.
Index-Performance
Indexierungsprozesse für große Kataloge zuverlässig und performant gestalten.
10. Zusammenfassung
Request Cache und Query Cache: Das Wichtigste auf einen Blick
Zwei Ebenen
Der Request Cache speichert vollständige Aggregations-Antworten, der Query Cache einzelne, wiederverwendbare Filterklauseln als Bitmenge.
Voraussetzungen
Request Cache braucht size gleich 0, Query Cache braucht Klauseln im filter- statt im query-Kontext.
Invalidierung
Beide Caches hängen am Lucene-Segment-Zustand und verlieren bei jedem Segment-verändernden Refresh zumindest teilweise ihre Gültigkeit.
Praxisnutzen
Facettenlastige Kategorieseiten mit konsistenten Basisfiltern profitieren stark, personalisierte Aggregationen sollten getrennt gehalten werden.