| title | Request Limits | |
|---|---|---|
| subtitle | Protect your router from requests exceeding network, parser, operation-based, and subgraph response limits | |
| redirectFrom |
|
For enhanced security, the GraphOS Router can reject requests that violate any of the following kinds of limits:
- Operation-based semantic limits
- Network-based limits
- Parser-based lexical limits
- Subgraph response size limits
limits:
router:
# Network-based limits
http_max_request_bytes: 2000000 # Default value: 2 MB
http1_max_request_headers: 200 # Default value: 100
http1_max_request_buf_size: 800kb # Default value: 400kib
# Parser-based limits
parser_max_tokens: 15000 # Default value
parser_max_recursion: 500 # Default value
# Operation-based limits (License only)
max_depth: 100
max_height: 200
max_aliases: 30
max_root_fields: 20
# Subgraph response size limits
subgraph:
all:
http_max_response_size: 10mb # 10 MB for all subgraphs
subgraphs:
products:
http_max_response_size: 20mb # per-subgraph override (20 MB)
# Connector response size limits
connector:
all:
http_max_response_size: 5mb # 5 MB for all connector sources
sources:
products.rest:
http_max_response_size: 10mb # per-source override (10 MB)<PlanRequired plans={["Free", "Developer", "Standard", "Enterprise"]}>
Rate limits apply on the Free plan. Developer and Standard plans require Router v2.6.0 or later.
You can define operation limits in your router's configuration to reject potentially malicious requests. An operation that exceeds any specified limit is rejected (unless you run your router in warn_only mode).
To use operation limits, you must run v1.17 or later of the Apollo Router. Download the latest version.
You define operation limits in your router's YAML config file, like so:
limits:
router:
max_depth: 100
max_height: 200
max_aliases: 30
max_root_fields: 20
# Uncomment to enable warn_only mode
# warn_only: trueEach limit takes an integer value. You can define any combination of supported limits.
Limits the deepest nesting of selection sets in an operation, including fields in fragments.
The GetBook operation below has depth three:
query GetBook {
book { # Depth 1 (root field)
...bookDetails
}
}
fragment bookDetails on Book {
details { # Depth 2 (nested under `book`)
... on ProductDetailsBook {
country # Depth 3 (nested under `details`)
}
}
}Limits the number of unique fields included in an operation, including fields of fragments. If a particular field is included multiple times via aliases, it's counted only once.
The GetUser operation below has height three:
query GetUser {
user { # 1
id # 2
name # 3
username: name # Aliased duplicate (not counted)
}
}Each unique field increments an operation's height by one, regardless of that field's return type (scalar, object, or list).
Limits the total number of aliased fields in an operation, including fields of fragments.
The GetUser operation below includes three aliases:
query GetUser {
user {
nickname: name # 1
username: name # 2
handle: name # 3
}
}Each aliased field increments the alias count by one, regardless of that field's return type (scalar, object, or list).
Limits the number of root fields in an operation, including root fields in fragments. If a particular root field is included multiple times via aliases, each usage is counted.
The following operation includes three root fields:
query GetTopProducts {
topBooks { # 1
id
}
topMovies { # 2
id
}
topGames { # 3
id
}
}If you run your router in warn_only mode, operations that exceed defined limits are not rejected. Instead, the router processes these operations as usual and emits a WARN trace that notes all exceeded limits, like so:
2023-03-15T19:08:23.123456Z WARN apollo_router::operation_limits: max_depth exceeded, max_depth: 3, current_op_depth: 5, operation: "query GetOwnerLocation {cat {owner {location {postalCode}}}}"
Running in warn_only mode can be useful while you're testing to determine the most appropriate limits to set for your supergraph.
You can enable or disable warn_only mode in your router's YAML config file, like so:
limits:
router:
warn_only: true # warn_only mode always enabledWhenever your router rejects a request because it exceeds an operation limit, the router responds with a 400 HTTP status code and a standard GraphQL error response body:
# HTTP 400
{
"data": {},
"errors": [
{
"message": "Maximum height (field count) limit exceeded in this operation",
"extensions": {
"code": "MAX_HEIGHT_LIMIT"
}
}
]
}If you run your router in warn_only mode, the router logs the limit violation but executes the operation as normal, returning a 200 status code with the expected response.
Router telemetry can help you set operation limits, especially when you have a large number of existing operations. You can measure incoming operations over a fixed duration, then use the captured data as a baseline configuration.
To log limit information about every operation, you can configure the router with a custom event to log the values of aliases, depth, height, and root_fields for each operation:
telemetry:
instrumentation:
events:
supergraph:
OPERATION_LIMIT_INFO:
message: operation limit info
on: response
level: info
attributes:
graphql.operation.name: true
query.aliases:
query: aliases
query.depth:
query: depth
query.height:
query: height
query.root_fields:
query: root_fieldsFor a large amount of traffic, you may prefer to collect and export metrics to your APM instead.
To capture and view metrics to help set your operation limits, you can configure the router to collect custom metrics on the values of aliases, depth, height, and root_fields for each operation:
telemetry:
exporters:
metrics:
common:
views:
# Define a custom view because operation limits are different than the default latency-oriented view of OpenTelemetry
- name: oplimits.*
aggregation:
histogram:
buckets:
- 0
- 5
- 10
- 25
- 50
- 100
- 500
- 1000
instrumentation:
instruments:
supergraph:
oplimits.aliases:
value:
query: aliases
type: histogram
unit: number
description: "Aliases for an operation"
oplimits.depth:
value:
query: depth
type: histogram
unit: number
description: "Depth for an operation"
oplimits.height:
value:
query: height
type: histogram
unit: number
description: "Height for an operation"
oplimits.root_fields:
value:
query: root_fields
type: histogram
unit: number
description: "Root fields for an operation"You should also configure the router to export metrics to your APM tool.
Limits the amount of data read from the network for the body of HTTP requests, to protect against unbounded memory consumption. This limit is checked before JSON parsing. Both the GraphQL document and associated variables count toward it.
For multipart file upload requests, this limit applies only to the GraphQL operations field (the query and variables), not to the uploaded file data.
Limit file data separately with preview_file_uploads.protocols.multipart.limits.max_file_size.
The default value is 2000000 bytes, 2 MB.
Before increasing this limit significantly consider testing performance in an environment similar to your production, especially if some clients are untrusted. Many concurrent large requests could cause the router to run out of memory.
Limit the maximum number of headers of incoming HTTP1 requests. The default value is 100 headers.
If router receives more headers than the buffer size, it responds to the client with 431 Request Header Fields Too Large.
Limit the maximum buffer size for the HTTP1 connection. Default is ~400kib.
Limits the number of tokens a query document can include. This counts all tokens, including both lexical and ignored tokens.
The default value is 15000.
Limits the deepest level of recursion allowed by the router's GraphQL parser to prevent stack overflows. This corresponds to the deepest nesting level of any single GraphQL operation or fragment defined in a query document.
The default value is 500.
In the example below, the GetProducts operation has a recursion of three, and the ProductVariation fragment has a recursion of two. Therefore, the max recursion of the query document is three.
query GetProducts {
allProducts { #1
...productVariation
delivery { #2
fastestDelivery #3
}
}
}
fragment ProductVariation on Product {
variation { #1
name #2
}
}Note that the router calculates the recursion depth for each operation and fragment separately. Even if a fragment is included in an operation, that fragment's recursion depth does not contribute to the operation's recursion depth.
In versions of the Apollo Router prior to 1.17, this limit was defined via the config option experimental_parser_recursion_limit.
Limits the number of bytes the router reads from a subgraph's HTTP response body, to protect against unbounded memory consumption when a subgraph returns an unexpectedly large payload.
This limit is enforced as the response body streams in — the router stops reading and returns an error as soon as the limit is exceeded, without buffering the entire body first.
When the limit is exceeded, the router returns a GraphQL error to the client with extension code SUBREQUEST_HTTP_ERROR.
There is no default limit; subgraph responses are unrestricted unless you configure this option.
You can set a global default that applies to all subgraphs, and optionally override it per subgraph:
limits:
subgraph:
all:
http_max_response_size: 10mb # 10 MB for all subgraphs
subgraphs:
products:
http_max_response_size: 20mb # 20 MB override for 'products'The per-subgraph entry under subgraphs takes precedence over all. If only all is set, it applies to every subgraph. If only a named entry is set, only that subgraph is limited.
To determine the appropriate limit, measure the actual response sizes your subgraphs return. Use the built-in http.client.response.body.size histogram to collect a distribution of subgraph response sizes:
telemetry:
instrumentation:
instruments:
subgraph:
http.client.response.body.size: trueThis histogram is disabled by default. Once enabled, use it to observe the 95th or 99th percentile of response sizes across your subgraphs, then set http_max_response_size above that value with enough headroom for legitimate variation (for example, large page sizes in paginated queries).
After a subgraph response is aborted because it exceeds the configured limit:
- The router increments the
apollo.router.limits.subgraph_response_size.exceededcounter with asubgraph.nameattribute identifying the affected subgraph. - The subgraph request span gets an
apollo.subgraph.response.abortedattribute set toresponse_size_limit.
Monitor the counter to detect misconfigured limits or misbehaving subgraphs. Use the span attribute to filter limit-triggered aborts in your tracing backend.
Limits the number of bytes the router reads from a connector's HTTP response body, to protect against unbounded memory consumption when a connector source returns an unexpectedly large payload.
This limit is enforced as the response body streams in — the router stops reading and returns a GraphQL error as soon as the limit is exceeded.
There is no default limit; connector responses are unrestricted unless you configure this option.
Identify sources by subgraph_name.source_name (matching the key format used in connector configuration). You can set a global default that applies to all sources, and optionally override it per source:
limits:
connector:
all:
http_max_response_size: 5mb # 5 MB for all connector sources
sources:
products.rest:
http_max_response_size: 10mb # 10 MB override for 'products.rest'The per-source entry under sources takes precedence over all.
The built-in http.client.response.body.size histogram measures actual connector response sizes:
telemetry:
instrumentation:
instruments:
connector:
http.client.response.body.size: trueWhen a connector response is aborted because it exceeds the configured limit:
- The router increments the
apollo.router.limits.connector_response_size.exceededcounter with aconnector.sourceattribute identifying the affected source. - The connector request span gets an
apollo.connector.response.abortedattribute set toresponse_size_limit.