graphql-limit-count
The graphql-limit-count
plugin uses a fixed window algorithm to limit the rate of GraphQL requests based on the depth of the GraphQL queries or mutations.
In GraphQL, the depth refers to the number of nesting levels in a query or mutation. The following is an example query with a depth of 3:
{
a {
b {
c
}
}
}
The graphql-limit-count
plugin rate limits by a quota of depth within a given time interval. For example, if the quota of count is set to 4 within a 30-second interval, requests with a depth of 3 will be allowed. The remaining quota within the same 30-second is 1. If a request of depth 2 is sent within the same 30-second interval, it will be rejected.
Examples
The examples below use GitHub GraphQL API endpoint as an upstream and demonstrate how you can configure graphql-limit-count
for different scenarios.
To follow along, create a GitHub personal access token with the appropriate scopes for the resources you want to interact with.
Apply Rate Limiting by Remote Address
The following example demonstrates the rate limiting of GraphQL requests by a single variable, remote_addr
.
Create a route with graphql-limit-count
plugin that allows for a quota of depth 2 within a 30-second window per remote address:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "graphql-limit-count-route",
"uri": "/graphql",
"plugins": {
"graphql-limit-count": {
"count": 2,
"time_window": 30,
"rejected_code": 429,
"key_type": "var",
"key": "remote_addr"
}
},
"upstream": {
"type": "roundrobin",
"pass_host": "node",
"scheme": "https",
"nodes": {
"api.github.com:443": 1
}
}
}'
Verify with GraphQL Query
Send a request with a GraphQL query of depth 2 to verify:
curl -i "http://127.0.0.1:9080/graphql" -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${GH_ACCESS_TOKEN}" \
-d '{"query": "query {viewer{login}}"}'
You should see an HTTP/1.1 200 OK
response with the corresponding response body.
The request has consumed all the quota allowed for the time window. If you send the request again within the same 30-second time interval, you should receive an HTTP/1.1 429 Too Many Requests
response, indicating the request surpasses the quota threshold.
Verify with GraphQL Mutation
You can also send a request with a GraphQL mutation of depth 3 to verify:
curl -i "http://127.0.0.1:9080/graphql" -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${GH_ACCESS_TOKEN}" \
-d '{"query": "mutation AddReactionToIssue {addReaction(input:{subjectId:\"MDU6SXNzdWUyMzEzOTE1NTE=\",content:HOORAY}) {reaction {content} subject {id}}}"}'
You should see an HTTP/1.1 429 Too Many Requests
response at any time, as depth 3 always surpasses the quota of depth 2.
Apply Rate Limiting by Remote Address and Consumer Name
The following example demonstrates the rate limiting of GraphQL requests by a combination of variables, remote_addr
and consumer_name
. It allows for a quota of depth 2 within a 30-second window per remote address and for each consumer.
Create a consumer john
:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "john"
}'
Create key-auth
credential for the consumer:
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'
Create a second consumer jane
:
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "jane"
}'
Create key-auth
credential for the consumer:
curl "http://127.0.0.1:9180/apisix/admin/consumers/jane/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-jane-key-auth",
"plugins": {
"key-auth": {
"key": "jane-key"
}
}
}'
Create a route with key-auth
and graphql-limit-count
plugins:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "graphql-limit-count-route",
"uri": "/graphql",
"plugins": {
"key-auth": {},
"graphql-limit-count": {
"count": 2,
"time_window": 30,
"rejected_code": 429,
"key_type": "var_combination",
"key": "$remote_addr $consumer_name"
}
},
"upstream": {
"type": "roundrobin",
"pass_host": "node",
"scheme": "https",
"nodes": {
"api.github.com:443": 1
}
}
}'
❶ key-auth
: enable key authentication on the route.
❷ key_type
: set to var_combination
to interpret the key
is as a combination of variables.
❸ key
: set to $remote_addr $consumer_name
to apply rate limiting quota by remote address and consumer.
Send a request with a GraphQL query of depth 2 as the consumer jane
:
curl -i "http://127.0.0.1:9080/graphql" -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${GH_ACCESS_TOKEN}" \
-H 'apikey: jane-key' \
-d '{"query": "query {viewer{login}}"}'
You should see an HTTP/1.1 200 OK
response with the corresponding response body.
This request has consumed all the quota set for the time window. If you send the same request as the consumer jane
within the same 30-second time interval, you should receive an HTTP/1.1 429 Too Many Requests
response, indicating the request surpasses the quota threshold.
Send the same request as the consumer john
within the same 30-second time interval:
curl -i "http://127.0.0.1:9080/graphql" -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${GH_ACCESS_TOKEN}" \
-H 'apikey: john-key' \
-d '{"query": "query {viewer{login}}"}'
You should see an HTTP/1.1 200 OK
response with the corresponding response body, indicating the request is not rate limited.
Send the same request as the consumer john
again within the same 30-second time interval, you should receive an HTTP/1.1 429 Too Many Requests
response.
This verifies the plugin rate limits by the combination of variables, remote_addr
and consumer_name
.
Share Quota among Routes
The following example demonstrates the sharing of GraphQL rate limiting quota among multiple routes by configuring the group
of the graphql-limit-count
plugin.
Note that the configurations of the graphql-limit-count
plugin of the same group
should be identical. To avoid update anomalies and repetitive configurations, you can create a service with graphql-limit-count
plugin and upstream for routes to connect to.
Create a service:
curl "http://127.0.0.1:9180/apisix/admin/services" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "graphql-limit-count-service",
"plugins": {
"graphql-limit-count": {
"count": 2,
"time_window": 30,
"rejected_code": 429,
"group": "srv1"
}
},
"upstream": {
"type": "roundrobin",
"pass_host": "node",
"scheme": "https",
"nodes": {
"api.github.com:443": 1
}
}
}'
Create two routes and configure their service_id
to be 1
, so that they share the same configurations for the plugin and upstream:
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "graphql-limit-count-route-1",
"service_id": "graphql-limit-count-service",
"uri": "/graphql1",
"plugins": {
"proxy-rewrite": {
"uri": "/graphql"
}
}
}'
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "graphql-limit-count-route-2",
"service_id": "graphql-limit-count-service",
"uri": "/graphql2",
"plugins": {
"proxy-rewrite": {
"uri": "/graphql"
}
}
}'
The proxy-rewrite
plugin is used to rewrite the URI to /graphql
so that requests are forwarded to the correct endpoint.
Send a request with a GraphQL query of depth 2 to route /graphql1
:
curl -i "http://127.0.0.1:9080/graphql1" -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${GH_ACCESS_TOKEN}" \
-d '{"query": "query {viewer{login}}"}'
You should see an HTTP/1.1 200 OK
response with the corresponding response body.
Send the same query of depth 2 to route /graphql2
within the same 30-second time interval:
curl -i "http://127.0.0.1:9080/graphql2" -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${GH_ACCESS_TOKEN}" \
-d '{"query": "query {viewer{login}}"}'
You should receive an HTTP/1.1 429 Too Many Requests
response, which verifies the two routes share the same rate limiting quota.
Share Quota Among APISIX Nodes with a Redis Server
The following example demonstrates the rate limiting of GraphQL requests across multiple APISIX nodes with a Redis server, such that different APISIX nodes share the same rate limiting quota.
On each APISIX instance, create a route with the following configurations. Adjust the address of the Admin API accordingly.
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "graphql-limit-count-route",
"uri": "/graphql",
"plugins": {
"graphql-limit-count": {
"count": 2,
"time_window": 30,
"rejected_code": 429,
"key": "remote_addr",
"policy": "redis",
"redis_host": "192.168.xxx.xxx",
"redis_port": 6379,
"redis_password": "p@ssw0rd",
"redis_database": 1
}
},
"upstream": {
"type": "roundrobin",
"pass_host": "node",
"scheme": "https",
"nodes": {
"api.github.com:443": 1
}
}
}'
❶ policy
: set to redis
to use a Redis instance for rate limiting.
❷ redis_host
: set to Redis instance IP address.
❸ redis_port
: set to Redis instance listening port.
❹ redis_password
: set to the password of the Redis instance, if any.
❺ redis_database
: set to the database number in the Redis instance.
Send a request with a GraphQL query of depth 2 to an APISIX instance:
curl -i "http://127.0.0.1:9080/graphql" -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${GH_ACCESS_TOKEN}" \
-d '{"query": "query {viewer{login}}"}'
You should see an HTTP/1.1 200 OK
response with the corresponding response body.
Send the same request to a different APISIX instance within the same 30-second time interval, you should receive an HTTP/1.1 429 Too Many Requests
response, verifying routes configured in different APISIX nodes share the same quota.
Share Quota Among APISIX Nodes with a Redis Cluster
You can also use a Redis cluster to apply the same quota across multiple APISIX nodes, such that different APISIX nodes share the same rate limiting quota.
Ensure that your Redis instances are running in cluster mode. A minimum of two nodes are required for the graphql-limit-count
plugin configurations.
On each APISIX instance, create a route with the following configurations. Adjust the address of the Admin API accordingly.
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "graphql-limit-count-route",
"uri": "/graphql",
"plugins": {
"graphql-limit-count": {
"count": 2,
"time_window": 30,
"rejected_code": 429,
"key": "remote_addr",
"policy": "redis-cluster",
"redis_cluster_nodes": [
"192.168.xxx.xxx:6379",
"192.168.xxx.xxx:16379"
],
"redis_password": "p@ssw0rd",
"redis_cluster_name": "redis-cluster-1",
"redis_cluster_ssl": true
}
},
"upstream": {
"type": "roundrobin",
"pass_host": "node",
"scheme": "https",
"nodes": {
"api.github.com:443": 1
}
}
}'
❶ policy
: set to redis-cluster
to use a Redis cluster for rate limiting.
❷ redis_cluster_nodes
: set to Redis node addresses in the Redis cluster.
❸ redis_password
: set to the password of the Redis cluster, if any.
❹ redis_cluster_name
: set to the Redis cluster name.
➎ redis_cluster_ssl
: enable SSL/TLS communication with Redis cluster.
Send a request with a GraphQL query of depth 2 to an APISIX instance:
curl -i "http://127.0.0.1:9080/graphql" -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${GH_ACCESS_TOKEN}" \
-d '{"query": "query {viewer{login}}"}'
You should see an HTTP/1.1 200 OK
response with the corresponding response body.
Send the same request to a different APISIX instance within the same 30-second time interval, you should receive an HTTP/1.1 429 Too Many Requests
response, verifying routes configured in different APISIX nodes share the same quota.