Proč optimalizovat výkon GraphQL
GraphQL poskytuje klientům flexibilitu v podobě deklarativních dotazů, ale tato svoboda přináší riziko přetížení backendu, N+1 problémů, nadměrné latence a neefektivního využití databází a sítí. Optimalizace výkonu proto zahrnuje návrh schématu, implementaci resolverů, strategie cachování, řízení složitosti dotazů, observabilitu a governance. Cílem je doručit přesná data s co nejnižší latencí a stabilním SLA při udržitelné spotřebě zdrojů.
Typické bottlenecky GraphQL
- N+1 dotazy v resolverechech při načítání navázaných entit.
- Over-fetching na serveru (zbytečné JOINy/kolony) i klientu (dotaz na více polí než je potřeba).
- Drahé výpočty v polích s agregacemi či dynamickou autorizací bez cachování.
- Nekontrolovaná složitost (hloubka, šířka, aliasy) vedoucí k explozivnímu počtu resolver volání.
- Nesprávná paginace způsobující velké stránky a dlouhé transakce.
Návrh schématu: granularita, hranice a kontrakty
- Modelujte vztahy explicitně a zvažte, zda konkrétní pole má být connection s paginací místo neomezeného listu.
- Oddělte drahá pole do samostatných typů/fieldů nebo použijte feature flags a field-level auth s cachováním.
- Preferujte kurzorovou paginaci (Relay
connections) před offsetem kvůli stabilitě a indexům. - Scalar vs. komplexní typ: nadměrně „bohaté“ pole často indikuje potřebu samostatného resolveru s vlastním SLA.
Řešení N+1: batching a caching na úrovni resolverů
Základním nástrojem je DataLoader-like vrstva (batching + memoizace v rámci requestu). Agreguje klíče z více resolverů do jednoho dotazu na datastore.
// pseudokód const userByIdLoader = new DataLoader(async (ids) => db.selectUsersByIds(ids)); const Post = { author: (post, _, ctx) => ctx.loaders.userById.load(post.authorId) };
- Batchovací okno krátké (mikrotask/next-tick) pro sloučení požadavků bez zbytečného čekání.
- Memoizace per-request: zabraňuje duplicitním dotazům na stejná data v rámci jednoho dotazu GraphQL.
- Per-field caching pro deterministické výpočty (např. ACL derivace) s TTL.
Projekce a selektivní načítání (field selection)
Resolver by měl načítat pouze požadované kolony/atributy podle field selection stromu (tzv. lookahead). Tím snížíte IO i CPU.
// pseudokód: převod selectionSet na DB projekci const projection = selectionToColumns(info, { User: { id: 1, name: 1, email: 1 }}); return db('users').select(projection).whereIn('id', ids);
- ORM „eager loading“ s podmíněnou projekcí, nikoli slepé
select *. - Indexy a plány: slaďte schema GraphQL s DB indexy (pole používaná pro filtrování/sort).
Efektivní paginace a třídění
- Cursor-based: stabilní kurzory (např. base64 id + sort key), podpora
first/after,last/before. - Deterministické řazení: unikátní sekundární klíč pro stabilitu (např.
createdAt, id). - Limity: horní mez
first/lastzabraňuje příliš velkým stránkám (např. max 100).
Kontrola složitosti dotazů (complexity & depth limiting)
- Depth limit: zabrání patologickým stromům (např. max hloubka 8).
- Complexity score: přiřaďte polím váhy (např. list pole = 10 ×
args.limit), odmítejte dotazy s vysokým skóre. - Cost-based throttling: rate-limit na základě skóre místo počtu requestů.
- Whitelist/operation registry: povolte jen schválené operace v produkci (persisted queries).
Persisted Queries a edge cachování
Persisted Queries (APQ) mapují hash → dokument dotazu. Klient posílá jen hash a proměnné; server ověří a provede. Výhody:
- Menší payloady, lepší TTFB a vyšší cache hit-rate na CDN/edge (GET + cache headers).
- Eliminuje riziko ad hoc dotazů mimo schválený registr.
Pro cache na CDN používejte GET dotazy s deterministickým URL (hash + serializované proměnné) a Cache-Control s stale-while-revalidate. Na serveru nastavte response caching pro idempotentní query (per-user/per-scope klíče).
Server-side caching: vrstvení a invalidace
- Result cache na úrovni resolveru (např. per entity + args) s TTL a cache key navázaným na verzi dat.
- Entity cache (read-through před DB), invalidace eventy z CDC/streamu (např. Kafka).
- Memoizace v requestu + krátká shared cache pro „hot keys“.
Klientská optimalizace: fragmenty, normalizovaný cache a politiky
- Normalized cache (Apollo/URQL/Relay): minimalizuje síťové dotazy, deduplikuje entity podle
__typename:id. - Fragments: sdílení výběrů napříč obrazovkami, konzistentní projekce dat, méně „overfetch“.
- Policy řízení: cache-first, network-only, cache-and-network dle UX a SLA svěžesti.
- Field policies: merge strategie pro paginované connectiony, deduplikace edge.
Direktivy pro streaming a latenci prvního byte
@defer: odložení nenutných fragmentů pro rychlejší „shell“ UI (progressive rendering).@stream: postupné doručování položek z dlouhých seznamů.- Partial data tolerantní UI: komponenty, které zvládnou inkrementální doručení.
Autorizace a výkon
- Autorizace v datové vrstvě (row/column-level) s pushdown do DB, místo per-řádkové kontroly v aplikaci.
- Cachování rozhodnutí (ABAC/RBAC) na krátké TTL; invalidace při změně rolí.
- Field-level rules aplikujte co nejdříve (lookahead), ať neprovádíte zbytečné IO.
Optimalizace přístupu k databázi
- Jeden komplexní dotaz > mnoho malých: preferujte JOIN/CTE nad iterativními dotazy.
- Materializované pohledy nebo precomputované agregace pro drahá pole.
- Read replicas pro škálování; konsistence řídit per-field SLA.
- Limity transakcí: krátké transakce, izolace dle potřeby (většinou read committed), vyhnout se full-scanům.
Transportní optimalizace
- HTTP keep-alive a HTTP/2/3 pro multiplexing; snižují latenci handshaků.
- Komprese (gzip/br): pozor na CPU – komprimujte až u payloadů > ~1–2 kB a nastavte dictionary pro typické odpovědi.
- Persisted GET pro CDN cache, POST pro necacheovatelné mutace.
Observabilita: tracing, metriky a logy
- Distributed tracing (OpenTelemetry): sledujte čas ve vrstvě GraphQL, DB, cache, externí API; přenášejte
trace_id. - Metriky: latence p50/p95/p99 per-field, počet resolver volání, hit-rate cache, hloubka a komplexita dotazů.
- Logging: strukturované logy s anonymizací proměnných (PII), sampling u velkých odpovědí.
SLA, governance a bezpečnostní „zábradlí“
- Timeouty a rozpočet: celkový timeout requestu + per-field „budget“; zamezí dominovému efektu.
- Rate limiting podle identity a cost score; ochrana proti abusu.
- Operation registry: whitelisting schválených dotazů, zákaz dynamické introspekce v produkci.
- Static validation v CI: odmítejte breaking changes schématu a dotazů s překročením složitosti.
Mutace a konzistence
- Optimistické UI s následnou revalidací; šetří round-trip a subjektivní latenci.
- Invalidace cache podle změněných entit/edge; používejte události pro reaktivní re-fetch.
- Idempotence mutací (klientské idempotentní klíče) pro spolehlivost.
Streaming, Subscriptions a Live Queries
- Subscriptions jen pro skutečně real-time potřeby; jinak preferujte polling/SSR + revalidaci.
- Backpressure a sampling událostí pro omezení zatížení při špičkách.
- Cache-aware stream: spojení s edge cache pro snížení egress nákladů.
Testování výkonu a škálování
- Contract testy mezi klienty a schématem; stabilizují selectionSety a zmenšují variabilitu.
- Load testy: generujte mix operací, proměnných a hloubek; sledujte p95/p99 dosažitelnost po nasazení.
- Chaos testy: degradace DB, vypnutí cache, latence sítí – ověřte graceful chování.
Praktický checklist
- Zapněte DataLoader (batch & memoize) pro všechny entitní přístupy.
- Implementujte projekci podle field selection a vyhněte se
select *. - Standardizujte cursor paginaci a omezte
first/last. - Zaveďte complexity + depth limit a operation registry.
- Podporujte Persisted Queries a GET caching na CDN.
- Měřte p95/p99 per-field, hit-rate cache a počet resolver volání.
- Oddělte drahá pole, přidejte TTL/invalidaci a zvažte materializace.
- V CI validujte breaking changes a limity složitosti.
Závěr
Optimalizace GraphQL není jednorázový trik, ale soubor disciplín: od návrhu schématu, přes batchování a projekci, až po cachování, řízení složitosti a observabilitu. Týmy, které zavedou DataLoader, cursor paginaci, persisted queries, complexity limity a měření per-field metrik, získají spolehlivou, rychlou a nákladově efektivní GraphQL platformu, která škáluje s rostoucími požadavky byznysu i počtem klientů.