Optimizing Query Performance
1. Understanding Query Complexity
| Dimension | Detail |
|---|---|
| Depth | Nested levels of selection |
| Breadth | Number of sibling fields |
| Multipliers | List args (first × childCost) |
| Resolver cost | Per-field weight |
2. Implementing Depth Limiting
Example: graphql-depth-limit
import depthLimit from "graphql-depth-limit";
new ApolloServer({ schema, validationRules: [depthLimit(7)] });
3. Implementing Complexity Analysis
Example: Cost analysis
import { createComplexityRule, simpleEstimator, fieldExtensionsEstimator } from "graphql-query-complexity";
validationRules: [
createComplexityRule({
estimators: [fieldExtensionsEstimator(), simpleEstimator({ defaultComplexity: 1 })],
maximumComplexity: 1000,
onComplete: (cost) => logger.info({ cost })
})
]
4. Setting Query Timeouts
| Layer | Mechanism |
|---|---|
| HTTP server | request/socket timeout (e.g. 30s) |
| DB query | statement_timeout in Postgres |
| Resolver | AbortController + Promise.race |
5. Using Persisted Queries
| Benefit | Detail |
|---|---|
| Smaller payloads | Send hash instead of query string |
| CDN caching | GET + hash key cacheable at edge |
| Allowlisting | Reject unknown queries (safe-listing) |
6. Implementing Query Batching
| Pattern | Detail |
|---|---|
| Apollo BatchHttpLink | Send array of operations |
| Server | Process each, return array of responses |
| Trade-off | Latency drop ↔ harder to cache HTTP |
7. Using APQ
Example: Automatic Persisted Queries (Apollo)
// Client first sends only sha256Hash
{ "extensions": { "persistedQuery": { "version": 1, "sha256Hash": "abc..." } } }
// On miss, server returns PERSISTED_QUERY_NOT_FOUND → client resends with full query
// Server caches: hash → query string
8. Implementing Field-level Caching
Example: Cache hint
type Product @cacheControl(maxAge: 300) {
id: ID!
price: Money! @cacheControl(maxAge: 60)
inventory: Int! @cacheControl(maxAge: 30, scope: PRIVATE)
}
9. Optimizing Database Queries
| Technique | Detail |
|---|---|
| DataLoader | Eliminate N+1 |
| Selective columns | Inspect info to project only requested fields |
| JOIN-aware | Single JOIN when child fields are requested |
| EXPLAIN ANALYZE | Verify index usage |
10. Using Database Indexes
| Index Type | Use |
|---|---|
| B-tree | Equality, range, sort |
| Composite | Cover (filter, sort) columns |
| Partial | WHERE deletedAt IS NULL |
| GIN/GIST | Full-text, JSONB, arrays |
11. Implementing Read Replicas
| Routing | Detail |
|---|---|
| Query → replica | Distribute reads |
| Mutation → primary | Maintain consistency |
| Read-after-write | Pin to primary briefly after write |
| Lag handling | Tolerate seconds of staleness or read primary |