Skip to Content

Complexity-based Rate Limiting

⚠️

This feature is still in alpha, because finding out if you set the right complexity score still takes a while.

If you want to define rate limits based on the complexity of the queries users send in, you will need to configure a QueryComplexity limit.

Same as with the RequestCount limit, Stellate will check how much of a consumer’s complexity budget was already used up in the time window defined by you. Based on that value and the estimated complexity of the current query, the request is either allowed or denied.

stellate.ts
import { Config } from 'stellate'
 
const config: Config = {
  config: {
    getConsumerIdentifiers: (req) => {
      return {
        ip: req.ip,
      }
    },
    rateLimits: [
      {
        name: 'IP Limit',
        consumerIdentifier: 'ip',
        // Allow 1000 complexity points per 60s sliding window
        limit: {
          type: 'QueryComplexity',
          window: '60s',
          budget: 1000,
        },
      },
    ],
  },
}
export default config

Limit maximum query complexity

Any QueryComplexity budget configured via rate limiting, implicitly sets an upper bound for the overal complexity as well: If you allow 1000 complexity points per 60s, operations that have more than 1000 complexity points will be always be blocked.

Additionally, you can also limit the maximum complexity any single operation passing through Stellate can have:

stellate.ts
import { Config } from 'stellate'
 
const config: Config = {
  config: {
    complexity: {
      maxComplexity: 1000,
    },
  },
}
export default config

How to determine the query complexity budget

You can see the complexity of one of your GraphQL queries by opening GraphiQL on your Stellate dashboard. This is currently only visible if the complexity configuration option is defined in your Stellate configuration.

Update your configuration with complexity: {} to trigger this UI to be visible:

stellate.ts
import { Config } from 'stellate'
 
const config: Config = {
  config: {
    complexity: {},
  },
}
export default config

We recommend pasting in your largest query (with the highest pagination arguments you regularly use) and using its complexity as the baseline:

Query Complexity Points

Query complexity takes the number of fields as well as the depth and any pagination arguments into account. Every scalar field adds 1 point, every nested field adds 2 points, and every pagination argument multiplies the nested objects score by the number of records fetched.

Here is an example:

query {
  # Total: 18
  todos(limit: 2) {
    # (Nested: 2 + 1 + 1 + 1 + (author: 2 + 1 + 1)) * limit: 2 = 18
    id # Scalar: 1
    text # Scalar: 1
    completed # Scalar: 1
    author {
      ## Nested: 4 (2 + 1 + 1)
      id ## Scalar: 1
      name ## Scalar: 1
    }
  }
}

By default, it takes any argument named ['first', 'last', 'limit', 'pageSize', 'take'] into account. You can change this default vai the complexity.listSizeArguments configuration option.

To check out how the complexity is calculated for a specific query, you can try any query out both in the hosted and the dashboard GraphiQL:

GraphiQL Rate Limiting Debugging

Last updated on