Card 07/ 09

GotchaDifficulty: Advanced2 min

GraphQL Queries With No Limit on Their Cost

A query nested five levels deep can ask the server for millions of objects in one request.

In GraphQL the client decides the shape of the response, including how deep it nests and how many items each list returns. A server that resolves any valid query lets one request ask for millions of objects, and the database pays for every one. Without limits, a single query copied from a forum can take a public endpoint down.

The symptom. A public GraphQL endpoint slows to a halt. The logs show one client and one request, a query that follows a social graph three levels deep.

The objects resolved at each level of friends(first: 100) nested three deep

Input{ user(id: 1) { friends(first: 100) { friends(first: 100) { friends(first: 100) { name } } } } }

  1. Level 1

    1. 100 friends100

    One list of up to 100.

  2. Level 2

    1. 100 × 100 friends10,000

    Each of the 100 has its own list of up to 100.

  3. Level 3

    1. 100 × 100 × 100 friends1,000,000

    Each of those 10,000 has up to 100 more, each with a name to load.

ResultOne request of under a hundred characters asks for up to a million objects.

Why it happens. The flexibility that lets a screen ask for exactly what it needs also lets any client ask for everything. Fetching only the fields asked for does not help when what was asked for is enormous.

What it costs. Database load, memory and time multiplied by every level, and an outage for every other client.

What to do.

  • Limit query depth, for example to five levels.
  • Calculate a cost for each query before running it, from the list sizes it asks for, and refuse any over a budget.
  • Require a first or last argument with a maximum on every list, so no list is unbounded.
  • For a known set of clients, accept only persisted queries, registered in advance, and refuse anything else.
  • Rate limit by query cost as well as by request count.