{ }
type
GraphQL · Complexity · Security · DoS-Schutz · Magento
Query Complexity praktisch
messen und begrenzen

Eine GraphQL-API ohne Complexity-Limit ist ein offenes Einfallstor für Overloading-Angriffe. Depth-Limits, feldspezifische Kosten und Complexity-Budgets sind die drei Schichten, die verhindern, dass eine einzige Query den Server für Sekunden beschäftigt — oder für Minuten, wenn das Schema genug teure Felder hat.

11 Min. Lesezeit Complexity-Analyse · Depth-Limit · Cost-Direktiven · Magento GraphQL · API-Sicherheit · Produktionsbetrieb

1. Warum Query Complexity ein Sicherheitsthema ist, kein Performance-Thema

Query Complexity wird häufig als reines Performance-Optimierungsthema diskutiert, aber der eigentliche Ausgangspunkt ist Sicherheit. GraphQL-APIs sind von Natur aus flexibel: Clients können beliebige Queries formulieren und beliebig viele Felder in beliebiger Tiefe abfragen. Das ist der Kern des GraphQL-Versprechens. Aber diese Flexibilität hat eine Kehrseite: Ein Client — oder ein Angreifer — kann eine Query formulieren, die den Server für Sekunden oder Minuten beschäftigt, ohne selbst viel Aufwand zu betreiben.

Ein Depth-Limit alleine reicht dabei nicht aus. Eine flache Query mit hundert verschachtelten Listen-Feldern kann genauso teuer sein wie eine tiefe Query. Das Stichwort ist Query Complexity: ein numerischer Wert, der die geschätzten Kosten einer Query vor ihrer Ausführung berechnet. Liegt dieser Wert über einem konfigurierten Budget, wird die Query abgelehnt — bevor ein einziger Resolver aufgerufen wird. Das ist der entscheidende Unterschied zu Rate-Limiting: Complexity-Limits prüfen nicht, wie viele Requests ein Client schickt, sondern wie teuer jede einzelne Query ist.

2. Wie Complexity berechnet wird

Die Grundidee hinter Complexity-Berechnung ist simpel: Jedes Feld im Schema hat eine zugeordnete Kosten. Einfache Skalarfelder kosten 1. Felder, die eine Liste zurückgeben, kosten mehr — typischerweise werden sie mit dem Multiplikator der enthaltenen Elemente gewichtet. Felder, die teure Datenbankoperationen auslösen, bekommen einen höheren Basiswert. Die Gesamtkomplexität einer Query ist die Summe aller Feldkosten, wobei verschachtelte Felder mit den Kosten ihres Elternfeldes multipliziert werden.

Es gibt unterschiedliche Berechnungsmodelle: Das einfachste zählt die Anzahl der abgefragten Felder. Das sinnvollste gewichtet jedes Feld individuell und berücksichtigt den Multiplikator durch List-Felder. Das aufwändigste berücksichtigt auch Argumente: pageSize: 100 kostet mehr als pageSize: 5, weil das Ergebnis proportional größer ist. Für die meisten produktiven APIs ist das zweite Modell ausreichend — es erkennt teure Queries zuverlässig ohne, dass man für jedes Argument eine separate Kostenformel hinterlegen muss.


# Complexity analysis example — costs annotated as comments
query ExpensiveQuery {
  products(search: "jacket", pageSize: 50) {   # cost: 10 (list field, multiplier x50)
    items {                                       # cost: 1 per item = 50
      sku                                         # cost: 1 per item = 50
      name                                        # cost: 1 per item = 50
      related_products {                          # cost: 5 per item (nested list) = 250
        sku                                       # cost: 1 per related product = 250
        name                                      # cost: 1 per related product = 250
        media_gallery {                           # cost: 3 per item = 750
          url                                     # cost: 1 = 750
        }
      }
    }
  }
}
# Total estimated complexity: ~2410 — exceeds limit of 200, query rejected

# Lean query — low complexity
query LeanQuery {
  products(search: "jacket", pageSize: 10) {   # cost: 10
    items {                                      # cost: 10
      sku                                        # cost: 10
      name                                       # cost: 10
      url_key                                    # cost: 10
    }
  }
}
# Total estimated complexity: ~50 — well within limit

3. Depth-Limits: einfach, aber begrenzt wirksam

Das Depth-Limit ist die einfachste Form der Query-Einschränkung und gleichzeitig die am wenigsten wirksame, wenn sie allein eingesetzt wird. Es begrenzt, wie tief eine Query verschachtelt sein darf. Ein Limit von 10 bedeutet: maximal 10 Ebenen von der Wurzel der Query bis zum tiefsten Feld. Das verhindert die klassische zirkuläre Query (Produkt → related_products → related_products → ...), die ohne Limit unendlich tief rekursiv wird und den Server blockiert.

Das Problem mit Depth-Limits: Sie erkennen breite Queries nicht. Eine Query mit nur drei Verschachtelungsebenen, die aber auf jeder Ebene 50 Felder abfragt und jedes Feld ein teures Datenbankjoin erfordert, ist deutlich teurer als eine tiefe, aber schmale Query. Depth-Limits sollten daher immer zusammen mit Complexity-Limits konfiguriert werden — das Depth-Limit als einfache erste Barriere, das Complexity-Limit als intelligentere zweite Prüfschicht, die tatsächliche Kosten abschätzt.

4. Feldspezifische Kosten: teure Felder korrekt gewichten

Der wertvollste Schritt bei der Complexity-Konfiguration ist die Zuweisung feldspezifischer Kosten. Nicht jedes Feld kostet dasselbe: Ein einfaches name-Feld, das direkt aus dem Speicher gelesen wird, ist trivial. Ein related_products-Feld, das einen separaten Datenbankjoin auslöst und möglicherweise N weitere Resolver triggert, ist um Größenordnungen teurer. Wenn alle Felder denselben Basiswert haben, überschätzt das Complexity-System simple Queries und unterschätzt teure Queries gleichzeitig — ein ungenaues Modell, das zu falschen Limitierungen oder zu wenig Schutz führt.

Feldkosten werden in der Schema-Definition über Direktiven (@complexity) oder über externe Konfigurationsdateien zugewiesen, je nach verwendetem Framework. In Magento ist die Complexity-Logik über Validierungsregeln in der GraphQL-Infrastruktur implementiert. Für eigene Felder, die über @resolver eingebunden werden, kann man die Complexity-Kosten über die Resolver-Implementierung beeinflussen — allerdings müssen diese Kosten auch von der Complexity-Validierungslogik berücksichtigt werden.


# Schema with complexity costs assigned per field
# Using @complexity directive (supported by graphql-php and similar libraries)
type Query {
  # Simple list — moderate cost
  products(search: String, pageSize: Int): ProductOutput
    @complexity(value: 10, multipliers: ["pageSize"])

  # Expensive join — higher base cost
  categoryList(filters: CategoryFilterInput): [CategoryTree]
    @complexity(value: 20)
}

type ProductInterface {
  sku: String             # cost: 1 (scalar, cheap)
  name: String            # cost: 1 (scalar, cheap)

  # Nested list — expensive, triggers separate DB query per product
  related_products: [ProductInterface]
    @complexity(value: 5, multipliers: ["pageSize"])

  # External data source — highest cost
  stock_status: ProductStockStatus
    @complexity(value: 3)
}

5. Implementierung: Complexity vor der Ausführung berechnen

Complexity-Prüfung muss zwingend vor der Query-Ausführung stattfinden. Eine Complexity-Prüfung, die erst während der Ausführung abbricht, hat das Problem bereits teilweise verursacht — Resolver wurden aufgerufen, Datenbankverbindungen geöffnet, Speicher reserviert. Die korrekte Position ist die Validierungsphase: Nach dem Parsen der Query, aber vor dem ersten Resolver-Aufruf, wird die Complexity berechnet und mit dem konfigurierten Budget verglichen. Liegt die Complexity über dem Budget, wird eine Fehlermeldung zurückgegeben und die Ausführung gar nicht erst begonnen.

Die Fehlerantwort bei überschrittener Complexity sollte einheitlich und nicht zu informativ sein: Der exakte Complexity-Wert sollte nicht an den Client zurückgegeben werden, weil das Angreifern hilft, die Query schrittweise bis an das Limit zu optimieren. Eine generische Meldung wie "Query exceeds maximum allowed complexity" ist ausreichend. Im Server-Log hingegen sollte der tatsächliche Complexity-Wert für Debugging und Monitoring erfasst werden, damit legitime Clients, die das Limit unbeabsichtigt überschreiten, ihren Fehler nachvollziehen können.

6. Typische Fehler beim Einsatz von Complexity-Limits

Der häufigste Fehler ist ein Complexity-Budget, das zu niedrig konfiguriert ist und legitime Frontend-Queries ablehnt. Das führt dazu, dass Teams das Limit schnell erhöhen — manchmal bis auf einen Wert, der keinen sinnvollen Schutz mehr bietet. Die richtige Vorgehensweise ist, erst die tatsächlichen Complexity-Werte der produktiven Queries zu messen und dann das Budget mit einem angemessenen Puffer darüber zu setzen. Tools wie GraphQL Hive oder Apollo Studio können die Complexity-Verteilung über alle produktiven Queries aggregieren.

Ein zweiter häufiger Fehler ist die fehlende Unterscheidung zwischen authentifizierten und anonymen Clients. Ein eingeloggter Admin-Nutzer hat andere Anforderungen als ein anonymer Shopbesucher. Es macht Sinn, unterschiedliche Complexity-Budgets für unterschiedliche Rollen zu konfigurieren: Anonyme Clients bekommen ein strenges Limit, authentifizierte Clients ein höheres, interne Service-Accounts eventuell kein Limit. Das erfordert eine Middlewareschicht, die das Budget dynamisch aus dem Auth-Kontext ableitet.

7. Complexity-Limits in Magento GraphQL

Magento GraphQL hat eine eigene Complexity-Validierung, die aus der Konfiguration gelesen wird. Die Standardwerte sind in der Magento-Konfiguration unter graphql/validation/complexity_limit und graphql/validation/depth_limit konfigurierbar. Im Production Mode sind Standardlimits aktiv; im Developer Mode sind sie häufig deaktiviert oder sehr hoch, um die Entwicklung nicht zu behindern. Ein häufiger Fehler: Developer-Mode-Konfiguration wird versehentlich auf die Staging-Umgebung übertragen, und niemand bemerkt, dass die Limits fehlen.

Magento berechnet die Query-Complexity vor der Ausführung anhand eines eingebauten Algorithmus. Für eigene Felder und Resolver, die ins Schema eingebunden werden, wird deren Complexity standardmäßig mit einem Basiswert berechnet. Teure Custom-Resolver — etwa solche, die auf externe APIs zugreifen — sollten einen erhöhten Complexity-Wert bekommen, damit das Gesamtbudget korrekt modelliert, was diese Query wirklich kostet. Das erfordert einen Plugin auf die Complexity-Validierungslogik oder eine direkte Konfiguration über den Resolver-Mechanismus.

8. Niedrige und hohe Complexity im Vergleich

Die folgende Tabelle illustriert, warum dasselbe Complexity-Limit bei unterschiedlichen Query-Strukturen unterschiedlich wirksam ist und warum feldspezifische Kosten entscheidend sind.

Query-Typ Felder Tiefe Geschätzte Complexity Tatsächliche Last
Schlanke Produktliste sku, name, url_key (10 Produkte) 3 ~40 Niedrig — 1 DB-Query
Produktliste mit Preis sku, name, price_range (20 Produkte) 5 ~120 Mittel — 2 DB-Queries
Produkt mit related_products Produkt + related (je 20 Einträge) 6 ~800 Hoch — N+1 möglich
Tief verschachtelte Query 5 Ebenen, je 10 Felder 10 ~2000+ Sehr hoch — mehrere Joins
Introspection-Query __schema mit allen Typen 4 Sehr hoch (variabel) Hoch — vollständiges Schema laden

9. Zusammenfassung

Query Complexity ist der effektivste Mechanismus, um GraphQL-APIs gegen Overloading-Angriffe abzusichern und gleichzeitig die Last kontrollierbar zu halten. Depth-Limits sind einfach zu konfigurieren, decken aber nur eine Teilmenge der problematischen Query-Strukturen ab. Complexity-Limits mit feldspezifischen Kosten modellieren tatsächliche Serverkosten wesentlich genauer und schützen auch gegen breite, flache Queries. Die Kombination aus beidem — Depth als erste schnelle Barriere, Complexity als inhaltlichere Prüfschicht — ist der empfohlene Ansatz für produktive APIs.

In Magento sind Complexity-Limits konfigurierbar und in der Validierungsphase aktiv. Für eigene Resolver-Erweiterungen sollte die Complexity-Konfiguration explizit berücksichtigt werden. Wer das Complexity-Budget basierend auf echten produktiven Queries kalibriert (statt mit Schätzwerten), erzielt die beste Balance zwischen Schutz und Flexibilität. Monitoring der Complexity-Werte pro Operation ist die Grundlage für diese Kalibrierung.

Query Complexity messen und begrenzen — Das Wichtigste auf einen Blick

Depth-Limit

Einfachste Barriere — begrenzt Verschachtelungstiefe. Verhindert zirkuläre Queries, erkennt aber breite Queries nicht. Immer zusammen mit Complexity-Limit einsetzen.

Complexity-Budget

Numerischer Gesamtwert pro Query. Berechnung vor Ausführung — kein Resolver wird aufgerufen bei Überschreitung. Basierend auf produktiven Queries kalibrieren.

Feldspezifische Kosten

Teure Felder (Joins, externe APIs, Listen-Multiplikatoren) höher gewichten. Einheitliche Basiskosten für alle Felder geben ungenaues Modell.

Magento-Konfiguration

graphql/validation/complexity_limit und depth_limit konfigurieren. Developer Mode nie in Produktion — Limits sind dort oft deaktiviert. Eigene Resolver-Kosten explizit definieren.

11. FAQ: Query Complexity in GraphQL

1Was ist Query Complexity?
Numerischer Wert, der geschätzte Kosten einer GraphQL-Query vor der Ausführung beschreibt. Queries über dem Budget werden abgelehnt, bevor ein Resolver aufgerufen wird.
2Warum reicht Depth-Limit alleine nicht?
Depth-Limits erkennen breite, flache Queries nicht. Eine Query mit 3 Ebenen aber 100 teuren Feldern pro Ebene übersteht das Depth-Limit, ist aber sehr teuer. Complexity ergänzt das.
3Wie werden Feldkosten zugewiesen?
@complexity-Direktiven im Schema oder externe Konfiguration. Skalare = Basiswert 1, Listen mit Multiplikator, teure Felder (Joins, externe APIs) erhöhter Basiswert.
4Warum Complexity vor der Ausführung prüfen?
Prüfung während der Ausführung hat das Problem bereits teilweise verursacht. Validierungsphase — nach Parsen, vor erstem Resolver — ist der korrekte Zeitpunkt.
5Magento Complexity-Konfiguration?
graphql/validation/complexity_limit und depth_limit. Production Mode aktiviert diese Limits. Developer Mode hat sie oft deaktiviert — nicht in Produktion übernehmen.
6Wie legitime Queries nicht blockieren?
Budget basierend auf tatsächlichen produktiven Queries kalibrieren. Complexity aller produktiven Queries messen, Maximum bestimmen, Budget mit 20–50% Puffer darüber setzen.
7Exakte Complexity in der Fehlerantwort?
Nein — hilft Angreifern bei der Optimierung. Generische Meldung "Query exceeds maximum allowed complexity" reicht. Im Server-Log den Wert für Debugging erfassen.
8Unterschiedliche Budgets für unterschiedliche Nutzer?
Empfohlen: Anonyme = strenges Limit, authentifizierte Nutzer = höheres Limit, Service-Accounts = kein Limit. Middlewareschicht leitet Budget dynamisch aus Auth-Kontext ab.
9Sinnvoller Startwert für das Budget?
Kein universeller Wert — hängt von Schema und Feldkosten ab. Typisch 100–500. Besser: produktive Queries messen und Budget mit 20–50% Puffer über dem Maximum setzen.
10Complexity vs. Rate-Limiting?
Rate-Limiting: wie viele Requests pro Zeit. Complexity: wie teuer ein einzelner Request. Ergänzen sich — schützen gegen unterschiedliche Angriffsmuster. Beide einsetzen.