Introducing Complexity Control

Graeme CouparGraeme Coupar
Introducing Complexity Control

One of the advantages GraphQL offers over traditional HTTP APIs is the flexilbity to build new queries without involving an API developer in the change. But this flexibility comes with a cost: if clients can build any query then they can unwittingly build a query that would overload the backend at scale.

Grafbase already offers Operation Limits to help avoid these scenarios, but we've just shipped some major improvements in a feature we're calling Complexity Control.

This feature works a lot like our old complexity operation limit, except subgraph developers can now control how complex the gateway considers a particular field. If a field is particularly expensive, it can be marked as such. We now also take lists into account during complexity analysis - the complexity of a list is the expected length of that list multiplied by the complexity of the fields contained within.

This feature can be enabled in the Grafbase Gateway configuration:

[complexity_control] mode = "enforce" limit = 10000 list_size = 100

Subgraph developers can control the complexity of their fields using two new directives that we support:

directive @cost( weight: Int! ) on ARGUMENT_DEFINITION | ENUM | FIELD_DEFINITION | INPUT_FIELD_DEFINITION | OBJECT | SCALAR directive @listSize( assumedSize: Int slicingArguments: [String!] sizedFields: [String!] requireOneSlicingArgument: Boolean = true ) on FIELD_DEFINITION

These directives work as outlined in the Cost Directive Specification.

For example, a subgraph developer using the cursor specification could annotate their schema like this:

type Query { products(first: Int, after: String): ProductConnection @listSize(slicingArguments: ["first"], sizedFields: ["edges"]) } type ProductConnection { edges: [ProductEdge] totalSize: Int! @cost(weight: 50) } type ProductEdge { node: Product cursor: String } type Product { id: ID! }

Here we've used @listSize to tell complexity control that our first argument controls the size of the list, and the edges field on our connection is the field that this length applies to.

We've also set the weight of our totalSize field to 50 to account for some extra database work our backend has to do to calculate this total size.

Now lets break down how this affects our complexity calculation on an example query:

query { # cost: 71 products(first: 10) { # cost: 71 totalSize # cost: 50 edges { # cost: 10 * 2 node { # cost: 1 id # cost: 0 } } } }

For more details on this issue you can read the documentation.

Your input helps us to improve, refine and evolve Grafbase. Join the conversation on Discord, where you can share your ideas and feedback directly with the Grafbase team and get involved with our Community.