{ }
type
GraphQL · Caching · Persisted Queries · CDN · Magento Performance
Caching in GraphQL
HTTP, Persisted Queries, Response Cache, Edge Caching

GraphQL schickt Queries als POST-Request an einen einzigen Endpunkt – das macht Standard-HTTP-Caching schwieriger als in REST. Wer GraphQL-Performance ernst nimmt, muss alle Caching-Schichten kennen: Persisted Queries für HTTP-Caching, Resolver-Level Response Cache und CDN-Edge-Caching für nicht-personalisierte Inhalte.

16 Min. Lesezeit Persisted Queries · Response Cache · Redis · Varnish · CDN-Edge GraphQL · Magento · Headless Commerce

1. Warum Caching in GraphQL komplexer ist als in REST

In REST-APIs ist HTTP-Caching straightforward: GET-Requests für /products/42 können von CDN, Browser und Proxies gecacht werden. Der URL ist der Cache-Key. In GraphQL hingegen sind alle Anfragen POST-Requests an denselben Endpunkt (/graphql) – POST-Requests werden per Definition nicht gecacht. Das bedeutet, dass jede Query, egal ob für Produktlisten, Kategorien oder CMS-Seiten, ungecacht durch alle Caching-Schichten hindurch auf den Applikationsserver trifft. Bei hohem Traffic ist das ein erheblicher Performance-Nachteil.

Hinzu kommt die Granularität: In GraphQL kann eine einzige Query Daten aus verschiedenen Backend-Systemen mit unterschiedlichen Cache-Lebenszeiten zusammenführen. Eine Query, die gleichzeitig statische CMS-Inhalte (Cache-TTL: Stunden) und dynamische Lagerstandsdaten (Cache-TTL: Minuten) abfragt, lässt sich nicht mit einem einzigen HTTP-Cache-Header abbilden. Effektives Caching in GraphQL erfordert deshalb ein mehrschichtiges Konzept, das auf die Charakteristik jedes einzelnen Felds und Resolvers abgestimmt ist.

2. HTTP-Caching und das POST-Problem

Das POST-Problem ist der zentrale Ausgangspunkt für jede GraphQL-Caching-Strategie. Während GET /products?search=bag von CDNs und Browser-Caches problemlos gecacht wird, landet POST /graphql mit dem Query-Body immer beim Applikationsserver. Es gibt zwei Wege, dieses Problem zu umgehen. Der erste Weg: Queries können auch als GET-Request mit dem Query-String URL-kodiert gesendet werden (GET /graphql?query={products{items{sku}}}). Das funktioniert für kurze Queries, stößt aber bei komplexen Queries an URL-Längenbeschränkungen.

Der zweite und robustere Weg sind Persisted Queries: die Query wird vorab serverseitig unter einem Hash-ID registriert. Der Client sendet dann nur GET /graphql?hash=abc123 – ein kurzer, cachebarer GET-Request. Beide Methoden ermöglichen prinzipiell HTTP-Caching, aber nur Persisted Queries skalieren zuverlässig für produktive GraphQL-APIs. Der Schlüssel liegt darin, dass der Cache-Key stabil und vorhersagbar ist, was bei dynamisch generierten Query-Strings schwieriger zu garantieren ist.

3. Persisted Queries: HTTP-Caching für GraphQL aktivieren

Persisted Queries sind ein zweistufiger Mechanismus. In der ersten Phase registriert das Frontend-Build-System oder ein Bootstrap-Script alle bekannten Queries beim Server. Jede Query erhält einen deterministischen Hash (meist SHA256 des Query-Strings). In der zweiten Phase sendet der Client nur noch den Hash via GET-Request – der Server sucht die Query anhand des Hashes in seiner Registry und führt sie aus. Da der Request jetzt ein GET mit einem stabilen Hash als Parameter ist, kann CDN, Varnish oder Nginx ihn normal cachen.

Der praktische Vorteil geht über Caching hinaus: da der Server nur noch registrierte Queries akzeptiert, können beliebige oder manipulierte Query-Bodies vom Client in der Produktion abgelehnt werden. Das ist ein erheblicher Sicherheitsgewinn. Apollo Client, Relay und andere GraphQL-Clients unterstützen Persisted Queries nativ. Für Magento-Headless-Frontends mit Next.js oder Nuxt kann die Query-Registrierung als Build-Step in die CI-Pipeline integriert werden, sodass neue Queries automatisch beim Server registriert werden, bevor das Frontend deployed wird.


# Persisted Query workflow

# Step 1 (build time): Register query with hash
# POST /graphql
# { "query": "query ProductList { products(search: \"bag\") { items { sku name } } }",
#   "extensions": { "persistedQuery": { "version": 1, "sha256Hash": "abc123..." } } }

# Step 2 (runtime): Client sends only the hash via GET
# GET /graphql?extensions={"persistedQuery":{"version":1,"sha256Hash":"abc123..."}}
# → Server looks up query by hash, executes, returns result
# → CDN/Varnish can cache GET responses by hash + query variables

# Step 3 (CDN caching): Only for public, non-personalized data
# Cache-Control: public, max-age=300 (5 minutes for product data)
# Vary: Accept-Language, Store-Code (vary by store view)

# For authenticated/personalized queries: use response cache at resolver level
# Never edge-cache responses that contain customer-specific data

4. Response Cache auf Resolver-Ebene

Der Response Cache auf Resolver-Ebene ist die zweite Caching-Schicht und funktioniert unabhängig vom HTTP-Protokoll. Er ist besonders nützlich für teure Resolver-Operationen, die denselben Output für gleiche Inputs produzieren – unabhängig davon, ob der Request als GET oder POST kommt. Das Pattern: der Resolver prüft einen In-Memory-Cache (Redis, Memcached) auf einen Cache-Key, der aus den Resolver-Argumenten und optionalen Kontext-Daten berechnet wird. Bei Cache-Hit wird das gespeicherte Ergebnis sofort zurückgegeben. Bei Cache-Miss wird die eigentliche Operation ausgeführt und das Ergebnis mit einem TTL in den Cache geschrieben.

Der Cache-Key-Design ist dabei kritisch. Für öffentliche Daten wie Produktlisten oder Kategorien reicht ein Key aus Query-Argumenten (Suchbegriff, Filter, Sortierung). Für Store-View-abhängige Daten muss der Store-Code in den Key. Für personalisierte Daten sollte kein Response Cache verwendet werden – hier ist der Performance-Gewinn durch Caching selten den Komplexitätsaufwand wert, und Datenschutzprobleme entstehen, wenn personalisierte Daten versehentlich geteilt werden.

5. Redis als Caching-Backend: Patterns und Konfiguration

Redis ist das Standard-Caching-Backend für produktive GraphQL-APIs und Magento. Für GraphQL-Response-Caching eignen sich vor allem String-Keys mit JSON-serialisierten Werten und TTL-Ablauf. Das Pattern: der Cache-Key enthält einen Präfix (z.B. gql:product:), die Hash-ID der Query und einen Hash der Argumente. Das verhindert Key-Kollisionen zwischen verschiedenen Query-Typen und ermöglicht gezielte Invalidierung über Key-Präfixe.

Für Tag-basierte Invalidierung – das wichtigste Pattern für präzisen Cache-Clear – speichert man zusätzlich zu jedem Cache-Eintrag die zugehörigen Cache-Tags in einem Set. Wenn ein Produkt aktualisiert wird, iteriert man über alle Sets, die den Produkt-Tag enthalten, und löscht die zugehörigen Cache-Keys. Dieses Pattern ist in Magento über das Cache-Tag-System bereits für Varnish etabliert und lässt sich analog für Redis-basierte GraphQL-Response-Caches implementieren.

6. Edge Caching mit CDN und Varnish

Edge Caching über CDN oder Varnish ist die leistungsstärkste Caching-Schicht, weil sie Requests abfängt, bevor sie den Applikationsserver überhaupt erreichen. Für GraphQL funktioniert das ausschließlich mit Persisted Queries via GET-Request. Die Konfiguration am CDN ist dann analog zu REST: Cache-Control-Header steuern TTL und Vary-Parameter. Wichtig: ausschließlich nicht-personalisierte, öffentliche Daten gehören in den Edge-Cache. Produktlisten, Kategorieseiten, CMS-Blöcke und statische Konfigurationsdaten sind gute Kandidaten.

Der kritische Fallstrick beim Edge Caching: wenn ein eingeloggter Kunde eine Query sendet, die dieselbe Hash-ID wie eine öffentliche Query hat, aber im Authorization-Header einen Token trägt, darf der Edge-Cache die öffentliche Version nicht zurückgeben. Das Vary: Authorization-Header-Pattern stellt sicher, dass Requests mit und ohne Token als verschiedene Cache-Keys behandelt werden. Varnish-Konfigurationen für Magento nutzen dieses Prinzip bereits für traditionelle HTTP-Requests und müssen für GraphQL-GET-Requests entsprechend erweitert werden.

7. Cache-Invalidierung: Tag-basiert und präzise

Das bekannte Sprichwort gilt auch für GraphQL: Cache-Invalidierung ist eines der schwierigsten Probleme in der Informatik. Zu aggressive Invalidierung (bei jeder Änderung alles leeren) eliminiert den Caching-Vorteil. Zu konservative Invalidierung (Inhalte zu lange cachen) führt zu veralteten Daten im Frontend. Das Tag-basierte Invalidierungsmuster ist der beste Kompromiss: jeder Cache-Eintrag wird mit Tags versehen, die beschreiben, welche Daten er enthält.

In Magento sind Cache-Tags bereits ein etabliertes Konzept: Produkte tragen Tags wie cat_p_42, Kategorien cat_c_7. Diese Tags werden bei Aktualisierungen über Observer-Events gesammelt und gezielt invalidiert. Für GraphQL-Response-Cache-Einträge sollte dasselbe Tag-System genutzt werden: ein Resolver, der Produktdaten zurückgibt, annotiert den Cache-Eintrag mit den Produkt-IDs aller zurückgegebenen Produkte. Wenn Produkt 42 aktualisiert wird, werden nur Cache-Einträge mit dem Tag cat_p_42 invalidiert – nicht der gesamte Cache.


# Cache-tag-based invalidation concept for GraphQL resolvers

# Query that fetches products — resolver annotates cache entry with product IDs
query CategoryProducts {
  categoryList(filters: { ids: { in: ["3"] } }) {
    name
    products(pageSize: 24, currentPage: 1) {
      items {
        id
        sku
        name
        price_range {
          minimum_price {
            final_price { value currency }
          }
        }
      }
    }
  }
}

# Resolver pseudo-code for cache with tags:
# cacheKey = "gql:cat:3:page:1:sort:default"
# cacheTags = ["cat_c_3", "cat_p_101", "cat_p_102", "cat_p_103", ...]
# store in Redis: SET gqlcache:{cacheKey} {json} EX 300
# store tag index: SADD gqltag:cat_p_101 gqlcache:{cacheKey}
#
# On product 101 update event:
# SMEMBERS gqltag:cat_p_101 → [gqlcache:{cacheKey}, ...]
# DEL gqlcache:{cacheKey} (targeted invalidation, not full flush)

8. Caching in Magento GraphQL: Praxis und Fallstricke

Magento hat ein eigenes Response-Caching-System für GraphQL, das über den X-Magento-Cache-Id-Header gesteuert wird. Nicht-personalisierte Anfragen erhalten einen deterministischen Cache-Key aus Store-View, Währung und Kundengruppe (Gast). Personalisierte Anfragen – mit Authorization-Token – werden nicht gecacht. Dieses System funktioniert out-of-the-box für Standard-Queries, kann aber bei Custom-Resolvern, die externe Daten einbinden, zu veralteten Ergebnissen führen.

Der häufigste Fallstrick in Magento: ein Custom-Resolver lädt Daten aus einem externen CRM oder ERP. Der Response Cache von Magento weiß nichts von diesen externen Daten und kann sie nicht gezielt invalidieren. Die Lösung: für externe Daten entweder den Cache-Key erweitern (TTL-basiert auf die Aktualisierungsfrequenz der externen Quelle abstimmen) oder den Response Cache für diese spezifischen Queries deaktivieren. Es ist wichtig, diese Entscheidungen explizit zu dokumentieren – sonst entstehen schwer reproduzierbare Cache-Bugs, bei denen Daten im Frontend korrekt erscheinen, aber Stunden veraltet sind.

9. Caching-Schichten im Vergleich

Jede Caching-Schicht hat ihre eigene Charakteristik, Einsatzbereich und Einschränkungen. Eine durchdachte GraphQL-Caching-Strategie kombiniert typischerweise mehrere Schichten – nicht alle auf einmal, sondern gezielt je nach Anforderung der jeweiligen Query.

Caching-Schicht Mechanismus Geeignet für Einschränkung
HTTP / Browser Cache GET + Cache-Control Headers Statische, öffentliche Inhalte Nur mit Persisted Queries via GET
CDN / Edge Cache Varnish, Cloudflare, Fastly Produktlisten, Kategorien, CMS Nie für personalisierte Daten
Magento GraphQL Cache X-Magento-Cache-Id Header Standard-Queries ohne Custom-Resolver Keine Kontrolle über externe Daten
Redis Response Cache Resolver-Level, Tag-basiert Teure Resolver, Custom-Daten Implementierungsaufwand
In-Memory (PHP Request) Array-Cache innerhalb eines Requests N+1-Vermeidung im selben Request Nur für die Dauer eines Requests

Die optimale Strategie für eine Magento-Headless-Applikation: Persisted Queries für alle öffentlichen, nicht-personalisierten Queries – diese können dann über CDN gecacht werden. Magento's eingebauter GraphQL Response Cache für Standard-Queries. Redis-basierter Custom-Cache für eigene Resolver mit externen Datenquellen. In-Memory-Collector für N+1-Batching innerhalb eines einzelnen Requests. Personalisierte Queries werden nicht gecacht – hier zählen Resolver-Optimierung und N+1-Vermeidung.

Mironsoft

GraphQL-Performance, Caching-Architektur und Magento-Optimierung

GraphQL-APIs, die auch unter Peak-Traffic performen?

Wir analysieren bestehende GraphQL-Setups auf Caching-Lücken, implementieren Persisted Queries, konfigurieren Redis-Response-Caches und richten Tag-basierte Invalidierung für Magento ein.

Persisted Queries

Implementierung und Build-Step-Integration für HTTP-Caching via CDN

Redis Response Cache

Tag-basierter Cache für Custom-Resolver mit präziser Invalidierung

CDN-Konfiguration

Varnish und CDN für GraphQL-GET-Requests konfigurieren und testen

10. Zusammenfassung

Caching in GraphQL erfordert ein mehrschichtiges Konzept, weil POST-Requests standardmäßig nicht gecacht werden und verschiedene Felder unterschiedliche Cache-Charakteristiken haben. Persisted Queries lösen das POST-Problem, indem sie HTTP-Caching via GET-Request ermöglichen. Response Cache auf Resolver-Ebene mit Redis beschleunigt teure Operationen unabhängig vom Protokoll. Edge Caching mit CDN oder Varnish eliminiert Backend-Requests für öffentliche, nicht-personalisierte Inhalte vollständig. Tag-basierte Invalidierung stellt sicher, dass gecachte Daten präzise und ohne globalen Cache-Clear aktualisiert werden können.

In Magento-Projekten bedeutet das konkret: Persisted Queries im Frontend-Build-Step integrieren, Magento's eingebauten GraphQL-Cache für Standard-Queries nutzen und Custom-Resolver mit Redis-Cache und Tag-basierter Invalidierung ausstatten. Der wichtigste Grundsatz: personalisierte Daten nie in den Edge-Cache. Bei allen anderen Daten gilt: je weiter vorn im Stack gecacht wird, desto besser die Performance für alle Nutzer.

Caching in GraphQL — Das Wichtigste auf einen Blick

Persisted Queries

Hash-registrierte Queries ermöglichen GET-Requests. CDN und Browser können GraphQL-Responses für öffentliche Inhalte cachen. Sicherheitsgewinn als Nebeneffekt.

Response Cache

Resolver-Level-Cache mit Redis. Cache-Key aus Query-Argumenten + Store-Code. TTL je nach Aktualisierungsfrequenz der Daten kalibrieren.

Tag-basierte Invalidierung

Cache-Einträge mit Produkt-/Kategorie-Tags versehen. Bei Aktualisierung gezielt nur betroffene Einträge löschen. Kein globaler Cache-Clear.

Personalisierung

Personalisierte Queries nie in Edge-Cache. Hier zählen N+1-Vermeidung und Resolver-Optimierung. Vary: Authorization schützt Edge-Cache vor Datenlecks.

11. FAQ: Caching in GraphQL

1Warum ist Caching in GraphQL schwieriger?
Alle Queries gehen als POST-Requests an denselben Endpunkt. POST wird nicht gecacht. Persisted Queries lösen das via GET mit stabiler Hash-ID.
2Was sind Persisted Queries?
Serverseitig registrierte Queries mit Hash-ID. Client sendet nur den Hash via GET. Ermöglicht HTTP-Caching und verhindert beliebige Client-Queries in Produktion.
3Response Cache auf Resolver-Ebene?
Redis-Cache den der Resolver selbst verwaltet. Bei Cache-Hit: sofortige Rückgabe. Bei Cache-Miss: Operation ausführen, Ergebnis mit TTL cachen.
4Personalisierte Daten im Edge-Cache?
Nein. Vary: Authorization sorgt für separate Einträge. Bei Unsicherheit lieber nicht cachen als Datenleck riskieren.
5Was ist Tag-basierte Invalidierung?
Cache-Einträge erhalten Tags (Produkt-IDs). Bei Aktualisierung werden gezielt nur betroffene Einträge gelöscht. Kein globaler Cache-Clear nötig.
6Magento's eingebautes GraphQL-Caching?
X-Magento-Cache-Id-Header. Deterministischer Key für Gast-Anfragen. Anfragen mit Token werden nicht gecacht. Custom-Resolver mit externen Daten können Probleme verursachen.
7Wann keinen Response Cache verwenden?
Bei Echtzeit-Daten (Lagerstand), personalisierten Daten und Resolvern mit externen Quellen ohne Invalidierungsevents.
8Gute Kandidaten für Edge-Caching?
Produktlisten, Kategorieseiten, CMS-Blöcke, statische Konfiguration – alles was für alle Gast-Nutzer identisch ist. Persisted Queries via GET sind Voraussetzung.
9Persisted Queries in Next.js + Magento?
Build-Step in CI: Queries extrahieren, hashen, beim Server registrieren. Zur Laufzeit sendet Apollo-Client nur den Hash via GET. Next.js nutzt ISR oder SWR-Caching.
10Wichtigste Caching-Praxisregel?
Nicht alle Queries gleich behandeln. Öffentlich: Edge Cache. Teure Custom-Resolver: Redis. Personalisiert: kein Cache, stattdessen Resolver-Optimierung.

Effektives GraphQL-Caching erfordert das Zusammenspiel mehrerer Schichten — von HTTP über Redis bis zum CDN-Edge — und muss stets zusammen mit der Invalidierungsstrategie geplant werden, um Stale-Data-Probleme und unerklärliche Cache-Misses zu vermeiden.

Persisted Queries sind dabei der entscheidende Enabler: Sie verwandeln POST-Only-Endpoints in cacheable GET-Requests und geben Varnish, Fastly oder Cloudflare die Möglichkeit, GraphQL-Responses auf Edge-Ebene zu beschleunigen.