The GraphQL Rate Limiting Advanced plugin provides rate limiting for GraphQL queries. The GraphQL Rate Limiting plugin extends the Rate Limiting Advanced plugin.
Configuration Reference
This plugin is partially compatible with DB-less mode.
In DB-less mode, you configure Kong Gateway declaratively. Therefore, the Admin API is mostly read-only. The only tasks it can perform are all related to handling the declarative config, including:
- Setting a target's health status in the load balancer
- Validating configurations against schemas
- Uploading the declarative configuration using the
/config
endpoint
The cluster strategy is not supported in DB-less and hybrid modes. For Kong
Gateway in DB-less or hybrid mode, use the redis
strategy.
Example plugin configuration
Parameters
Here's a list of all the parameters which can be used in this plugin's configuration:
Form Parameter | Description |
---|---|
name
required Type: string |
The name of the plugin, in this case graphql-rate-limiting-advanced . |
service.name or service.id
Type: string |
The name or ID of the service the plugin targets.
Set one of these parameters if adding the plugin to a service through the top-level /plugins endpoint.
Not required if using /services/SERVICE_NAME|SERVICE_ID/plugins . |
route.name or route.id
Type: string |
The name or ID of the route the plugin targets.
Set one of these parameters if adding the plugin to a route through the top-level /plugins endpoint.
Not required if using /routes/ROUTE_NAME|ROUTE_ID/plugins . |
enabled
Type: boolean Default value: true |
Whether this plugin will be applied. |
config.cost_strategy
required Type: string Default value: default
|
Strategy to use to evaluate query costs. Either |
config.max_cost
optional Type: number Default value: 0
|
A defined maximum cost per query. 0 means unlimited. |
config.score_factor
optional Type: number Default value: 1
|
A scoring factor to multiply (or divide) the cost. The |
config.limit
required Type: array of number elements |
One or more requests-per-window limits to apply. |
config.window_size
required Type: array of number elements |
One or more window sizes to apply a limit to (defined in seconds). |
config.identifier
required Type: string Default value: consumer
|
How to define the rate limit key. Can be |
config.dictionary_name
required Type: string Default value: kong_rate_limiting_counters
|
The shared dictionary where counters will be stored until the next sync cycle. |
config.sync_rate
required Type: number |
How often to sync counter data to the central data store. A value of 0 results in synchronous behavior; a value of -1 ignores sync behavior entirely and only stores counters in node memory. A value greater than 0 syncs the counters in that many number of seconds. |
config.namespace
semi-optional Type: string Default value: random_auto_generated_string
|
The rate limiting library namespace to use for this plugin instance. Counter data and sync configuration is shared in a namespace.
|
config.strategy
Type: string Default value: cluster
|
The rate-limiting strategy to use for retrieving and incrementing the limits. Available values are:
In DB-less and hybrid modes, the In Konnect, the default strategy is
For details on which strategy should be used, refer to the implementation considerations. |
config.redis.host
semi-optional Type: string |
Host to use for Redis connection when the |
config.redis.port
semi-optional Type: integer |
Port to use for Redis connection when the |
config.redis.ssl
optional Type: boolean Default value: false
|
If set to true, then uses SSL to connect to Redis. |
config.redis.ssl_verify
optional Type: boolean Default value: false
|
If set to true, then verifies the validity of the server SSL certificate. Note that you need to configure the lua_ssl_trusted_certificate to specify the CA (or server) certificate used by your redis server. You may also need to configure lua_ssl_verify_depth accordingly. |
config.redis.server_name
optional Type: string |
Specifies the server name for the new TLS extension Server Name Indication (SNI) when connecting over SSL. |
config.redis.timeout
semi-optional Type: number Default value: 2000
|
Connection timeout (in milliseconds) to use for Redis connection when the |
config.redis.username
semi-optional Type: string |
Username to use for Redis connection when the This requires Redis v6.0.0+. The username cannot be set to This field is referenceable, which means it can be securely stored as a secret in a vault. References must follow a specific format. |
config.redis.password
semi-optional Type: string |
Password to use for Redis connection when the This field is referenceable, which means it can be securely stored as a secret in a vault. References must follow a specific format. |
config.redis.database
semi-optional Type: integer Default value: 0
|
Database to use for Redis connection when the |
config.redis.sentinel_master
semi-optional Type: string |
Sentinel master to use for Redis connection when the |
config.redis.sentinel_username
semi-optional Type: string |
Sentinel username to authenticate with a Redis Sentinel instance. If undefined, ACL authentication will not be performed. This requires Redis v6.2.0+. This field is referenceable, which means it can be securely stored as a secret in a vault. References must follow a specific format. |
config.redis.sentinel_password
semi-optional Type: string |
Sentinel password to authenticate with a Redis Sentinel instance. If undefined, no AUTH commands are sent to Redis Sentinels. This field is referenceable, which means it can be securely stored as a secret in a vault. References must follow a specific format. |
config.redis.sentinel_role
semi-optional Type: string |
Sentinel role to use for Redis connection when the |
config.redis.sentinel_addresses
semi-optional Type: array of string elements |
Sentinel addresses to use for Redis connection when the |
config.redis.cluster_addresses
semi-optional Type: array of string elements |
Cluster addresses to use for Redis connection when the |
config.redis.keepalive_backlog
optional Type: integer |
If specified, limits the total number of opened connections for a pool. If the
connection pool is full, all connection queues beyond the maximum limit go into
the backlog queue. Once the backlog queue is full, subsequent connect operations
will fail and return |
config.redis.keepalive_pool
optional Type: string Default value: generated from string template
|
The custom name of the connection pool. If not specified, the connection pool
name is generated from the string template |
config.redis.keepalive_pool_size
optional Type: integer Default value: 30
|
The size limit for every cosocket connection pool associated with every remote
server, per worker process. If no |
config.window_type
required Type: string Default value: sliding
|
Sets the time window to either |
config.hide_client_headers
optional Type: boolean Default value: false
|
Optionally hide informative response headers. Available options: |
Note: Redis configuration values are ignored if the
cluster
strategy is used.
Notes:
-
PostgreSQL 9.5+ is required when using the
cluster
strategy withpostgres
as the backing Kong cluster data store. This requirement varies from the PostgreSQL 9.4+ requirement as described in the Kong Community Edition documentation. -
The
dictionary_name
directive was added to prevent the usage of thekong
shared dictionary, which could lead tono memory
errors.
The GraphQL Rate Limiting Advanced plugin is an extension of the Rate Limiting Advanced plugin and provides rate limiting for GraphQL queries.
Due to the nature of client-specified GraphQL queries, the same HTTP request to the same URL with the same method can vary greatly in cost depending on the semantics of the GraphQL operation in the body.
A common pattern to protect your GraphQL API is then to analyze and assign costs to incoming GraphQL queries and rate limit the consumer’s cost for a given time window.
Costs in GraphQL queries
GraphQL query costs are evaluated by introspecting the endpoint’s GraphQL schema and applying cost decoration to parts of the schema tree.
Initially all nodes start with zero cost, with any operation at cost 1. Add rate-limiting constraints on any subtree. If subtree omitted, then rate-limit window applies on the whole tree (any operation).
Since there are many ways of approximating the cost of a GraphQL query, the
plugin exposes two strategies: default
and node_quantifier
.
The following example queries can be run on this SWAPI playground.
default
The default strategy is meant as a good middle ground for general GraphQL queries, where it’s difficult to assert a clear cost strategy, so every operation has a cost of 1.
query { # + 1
allPeople { # + 1
people { # + 1
name # + 1
}
}
}
# total cost: 4
Default node costs can be defined by decorating the schema:
type_path |
mul_arguments |
mul_constant |
add_arguments |
add_constant |
---|---|---|---|---|
Query.allPeople | [“first”] | 1 | [] | 1 |
Person.vehicleConnection | [“first”] | 1 | [] | 1 |
query { # + 1
allPeople(first:20) { # * 20 + 1
people { # + 1
name # + 1
vehicleConnection(first:10) { # * 10 + 1
vehicles { # + 1
id # + 1
name # + 1
cargoCapacity # + 1
}
}
}
}
}
# total cost: ((((4 * 10 + 1) + 1) + 1) * 20 + 1) + 1 = 862
Generally speaking, vehicleConnection weight (4) is applied 10 times, and the total weight of it (40) 20 times, which gives us a rough 800.
Cost constants can be atomically defined as:
type_path |
mul_arguments |
mul_constant |
add_arguments |
add_constant |
---|---|---|---|---|
Query.allPeople | [“first”] | 2 | [] | 2 |
Person.vehicleConnection | [“first”] | 1 | [] | 5 |
Vehicle.name | [] | 1 | [] | 8 |
On this example, Vehicle.name
and Person.vehicleConnection
have specific
weights of 8 and 5 respectively. allPeople
weights 2, and also has double
its weight when multiplied by arguments.
query { # + 1
allPeople(first:20) { # 2 * 20 + 2
people { # + 1
name # + 1
vehicleConnection(first:10) { # * 10 + 5
vehicles { # + 1
id # + 1
name # + 8
cargoCapacity # + 1
}
}
}
}
}
# total cost: ((((11 * 10 + 5) + 1) + 1) * 2 * 20 + 2) + 1 = 4683
node_quantifier
This strategy is fit for GraphQL schemas that enforce quantifier arguments on any connection, providing a good approximation on the number of nodes visited for satisfying a query. Any query without decorated quantifiers has a cost of 1. It is roughly based on GitHub’s GraphQL resource limits.
query {
allPeople(first:100) { # 1
people {
name
vehicleConnection(first:10) { # 100
vehicles {
name
filmConnection(first:5) { # 10 * 100
films{
title
characterConnection(first:50) { # 5 * 10 * 100
characters {
name
}
}
}
}
}
}
}
}
}
# total cost: 1 + 100 + 10 * 100 + 5 * 10 * 100 = 6101
type_path |
mul_arguments |
mul_constant |
add_arguments |
add_constant |
---|---|---|---|---|
Query.allPeople | [“first”] | 1 | [] | 1 |
Person.vehicleConnection | [“first”] | 1 | [] | 1 |
Vehicle.filmConnection | [“first”] | 1 | [] | 1 |
Film.characterConnection | [“first”] | 1 | [] | 1 |
Roughly speaking:
allPeople
returns 100 nodes, and has been called oncevehicleConnection
returns 10 nodes, and has been called 100 timesfilmConnection
returns 5 nodes, and has been called 10 * 100 timescharacterConnection
returns 50 nodes, and has been called 5 * 10 * 100 times
Specific costs per node can be specified by adding a constant:
type_path |
mul_arguments |
mul_constant |
add_arguments |
add_constant |
---|---|---|---|---|
Query.allPeople | [“first”] | 1 | [] | 1 |
Person.vehicleConnection | [“first”] | 1 | [] | 42 |
Vehicle.filmConnection | [“first”] | 1 | [] | 1 |
Film.characterConnection | [“first”] | 1 | [] | 1 |
query {
allPeople(first:100) { # 1
people {
name
vehicleConnection(first:10) { # 100 * 42
vehicles {
name
filmConnection(first:5) { # 10 * 100
films{
title
characterConnection(first:50) { # 5 * 10 * 100
characters {
name
}
}
}
}
}
}
}
}
}
# total cost: 1 + 100 * 42 + 10 * 100 + 5 * 10 * 100 = 10201
Usage
The following configuration example targets a GraphQL endpoint at our SWAPI playground.
Add a GraphQL service and route
curl -X POST http://kong:8001/services \
--data "name=example" \
--data "host=mockbin.org" \
--data "port=443" \
--data "protocol=https"
curl -X POST http://kong:8001/services/example/routes \
--data "hosts=example.com"
Enable the plugin on the service
Example using a single time window:
curl -i -X POST http://kong:8001/services/example/plugins \
--data name=graphql-rate-limiting-advanced \
--data config.limit=100 \
--data config.window_size=60 \
--data config.sync_rate=10
Example using multiple time windows:
curl -i -X POST --url http://kong:8001/services/example/plugins \
--data 'name=graphql-rate-limiting-advanced' \
--data 'config.window_size[]=60' \
--data 'config.window_size[]=3600' \
--data 'config.limit[]=10' \
--data 'config.limit[]=100' \
--data 'config.sync_rate=10'
Decorate GraphQL schema for costs
Cost decoration schema looks like:
Form Parameter | default | description |
---|---|---|
type_path |
Path to node to decorate | |
add_constant |
1 |
Node weight when added |
add_arguments |
[] |
List of arguments to add to add_constant |
mul_constant |
1 |
Node weight multiplier value |
mul_arguments |
[] |
List of arguments that multiply weight |
Cost decoration is available on the following routes:
/services/{service}/graphql-rate-limiting-advanced/costs
- GET: list of costs associated to a service schema
- PUT, POST: add a cost to a service schema
For example:
curl -X POST http://kong:8001/services/example/graphql-rate-limiting-advanced/costs \
--data type_path="Query.allPeople" \
--data mul_arguments="first"
curl -X POST http://kong:8001/services/example/graphql-rate-limiting-advanced/costs \
--data type_path="Person.vehicleConnection" \
--data mul_arguments="first"
--data add_constant=42
curl -X POST http://kong:8001/services/example/graphql-rate-limiting-advanced/costs \
--data type_path="Vehicle.filmConnection" \
--data mul_arguments="first"
curl -X POST http://kong:8001/services/example/graphql-rate-limiting-advanced/costs \
--data type_path="Film.characterConnection" \
--data mul_arguments="first"
/graphql-rate-limiting-advanced/costs
- GET: list of all costs on any service
- PUT, POST: add a cost to a service schema
/graphql-rate-limiting-advanced/costs/{cost_id}
- GET: get cost associated by id
- PATCH: modify cost associated by id
- DELETE: delete cost associated by id
Changing the default strategy
curl -X POST http://kong:8001/services/example/plugins \
--data name=graphql-rate-limiting-advanced \
--data config.limit=100 \
--data config.limit=10000 \
--data config.window_size=60 \
--data config.window_size=3600 \
--data config.sync_rate=10 \
--data config.cost_strategy=default
curl -i -X PATCH http://kong:8001/plugins/{plugin_id} \
--data config.cost_strategy=node_quantifier
Limit query cost by using config.max_cost
It’s usually a good idea to define a maximum cost applied to any query, regardless if the call is within the rate limits for a consumer.
By defining a max_cost
on our service, we are ensuring no query will run with
a cost higher than our set max_cost
. By default it’s set to 0, which means
no limit.
curl -X POST http://kong:8001/services/example/plugins \
--data name=graphql-rate-limiting-advanced \
--data config.limit=100 \
--data config.limit=10000 \
--data config.window_size=60 \
--data config.window_size=3600 \
--data config.sync_rate=0 \
--data config.max_cost=5000
To update max_cost
:
curl -X PATCH http://kong:8001/plugins/{plugin_id} \
--data config.max_cost=10000
Using config.score_factor
to modify costs
GraphQL query cost depends on multiple factors based on our resolvers
and the implementation of the schema. Cost on queries, depending on the cost
strategy might turn very high when using quantifiers, or very low with no
quantifiers at all. By using config.score_factor
the cost can be divided
or multiplied to a certain order of magnitude.
For example, a score_factor
of 0.01
will divide the costs by 100, meaning
every cost unit represents 100 nodes.
curl -X POST http://kong:8001/services/example/plugins \
--data name=graphql-rate-limiting-advanced \
--data config.limit=100 \
--data config.limit=10000 \
--data config.window_size=60 \
--data config.window_size=3600 \
--data config.sync_rate=0 \
--data config.max_cost=5000 \
--data config.score_factor=0.01
To update score_factor
:
curl -i -X PATCH http://kong:8001/plugins/{plugin_id} \
--data config.score_factor=1
Changelog
Kong Gateway 2.8.x
-
Added the
redis.username
andredis.sentinel_username
configuration parameters. -
The
redis.username
,redis.password
,redis.sentinel_username
, andredis.sentinel_password
configuration fields are now marked as referenceable, which means they can be securely stored as secrets in a vault. References must follow a specific format. -
Fixed plugin versions in the documentation. Previously, the plugin versions were labelled as
1.3-x
and2.3.x
. They are now updated to align with the plugin’s actual versions,0.1.x
and0.2.x
.
Kong Gateway 2.5.x
- Added the
redis.keepalive_pool
,redis.keepalive_pool_size
, andredis.keepalive_backlog
configuration parameters. These options relate to Openresty’s Lua INGINX module’stcp:connect
options.
Kong Gateway 2.2.x
- Added Redis TLS support with the
redis.ssl
,redis.ssl_verify
, andredis.server_name
configuration parameters.