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.
Local vs Redis Rate Limiting
The graphql-limit-count plugin supports two modes of rate limiting:
- Local rate limiting: Limits are enforced independently on each gateway instance. Each instance maintains its own counters, so the effective limit is roughly (limit × number of instances) when traffic is spread across instances. This is the default when no
policyis set or whenpolicyislocal. - Redis-based rate limiting: Limits are shared across all gateway instances through Redis. All instances share the same quota, so the configured limit applies to all gateway instances.
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:
- Admin API
- ADC
- Ingress Controller
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",
"policy": "local"
}
},
"upstream": {
"type": "roundrobin",
"pass_host": "node",
"scheme": "https",
"nodes": {
"api.github.com:443": 1
}
}
}'
services:
- name: graphql-service
routes:
- uris:
- /graphql
name: graphql-limit-count-route
plugins:
graphql-limit-count:
count: 2
time_window: 30
rejected_code: 429
key_type: var
key: remote_addr
policy: local
upstream:
type: roundrobin
scheme: https
nodes:
- host: api.github.com
port: 443
weight: 1
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
Gateway API currently has a bug where the upstream scheme is not correctly configured. As a result, requests are forwarded over HTTP instead of HTTPS, which leads to the error The plain HTTP request was sent to HTTPS port.
This issue is scheduled to be fixed in APISIX Ingress Controller version 2.0.2 and will also be addressed in API7 Ingress Controller in an upcoming release. Until then, this example cannot be completed using Gateway API.
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: github-graphql-external-domain
spec:
ingressClassName: apisix
scheme: https
passHost: node
externalNodes:
- type: Domain
name: api.github.com
port: 443
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: graphql-limit-count-route
spec:
ingressClassName: apisix
http:
- name: graphql-limit-count-route
match:
paths:
- /graphql
upstreams:
- name: github-graphql-external-domain
plugins:
- name: graphql-limit-count
enable: true
config:
count: 2
time_window: 30
rejected_code: 429
key_type: var
key: remote_addr
policy: local
Apply the configuration to your cluster:
kubectl apply -f graphql-limit-count-ic.yaml
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.
- Admin API
- ADC
- Ingress Controller
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,
"policy": "local",
"key_type": "var_combination",
"key": "$remote_addr $consumer_name"
}
},
"upstream": {
"type": "roundrobin",
"pass_host": "node",
"scheme": "https",
"nodes": {
"api.github.com:443": 1
}
}
}'
Create two consumers and a route that enables rate limiting by consumers:
consumers:
- username: john
credentials:
- name: key-auth
type: key-auth
config:
key: john-key
- username: jane
credentials:
- name: key-auth
type: key-auth
config:
key: jane-key
services:
- name: graphql-limit-service
routes:
- name: graphql-limit-count-route
uris:
- /graphql
plugins:
key-auth: {}
graphql-limit-count:
count: 2
time_window: 30
rejected_code: 429
policy: local
key_type: var_combination
key: "$remote_addr $consumer_name"
upstream:
type: roundrobin
scheme: https
nodes:
- host: api.github.com
port: 443
weight: 1
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
Create two consumers and a route that enables rate limiting by consumers:
- Gateway API
- APISIX CRD
Gateway API currently has a bug where the upstream scheme is not correctly configured. As a result, requests are forwarded over HTTP instead of HTTPS, which leads to the error The plain HTTP request was sent to HTTPS port.
This issue is scheduled to be fixed in APISIX Ingress Controller version 2.0.2 and will also be addressed in API7 Ingress Controller in an upcoming release. Until then, this example cannot be completed using Gateway API.
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: john
spec:
ingressClassName: apisix
authParameter:
keyAuth:
value:
key: john-key
---
apiVersion: apisix.apache.org/v2
kind: ApisixConsumer
metadata:
namespace: aic
name: jane
spec:
ingressClassName: apisix
authParameter:
keyAuth:
value:
key: jane-key
---
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: github-graphql-external-domain
spec:
ingressClassName: apisix
scheme: https
passHost: node
externalNodes:
- type: Domain
name: api.github.com
port: 443
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: graphql-limit-count-route
spec:
ingressClassName: apisix
http:
- name: graphql-limit-count-route
match:
paths:
- /graphql
upstreams:
- name: github-graphql-external-domain
plugins:
- name: key-auth
enable: true
- name: graphql-limit-count
enable: true
config:
count: 2
time_window: 30
rejected_code: 429
policy: local
key_type: var_combination
key: "$remote_addr $consumer_name"
Apply the configuration to your cluster:
kubectl apply -f graphql-limit-count-ic.yaml
❶ key-auth: enable key authentication on the route.
❷ key_type: set to var_combination to interpret the key 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.
- Admin API
- ADC
- Ingress Controller
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,
"policy": "local",
"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 graphql-limit-count-service, 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"
}
}
}'
Create a service with two routes that share the same rate limiting quota:
services:
- name: graphql-limit-count-service
plugins:
graphql-limit-count:
count: 2
time_window: 30
rejected_code: 429
policy: local
group: srv1
routes:
- name: graphql-limit-count-route-1
uris:
- /graphql1
plugins:
proxy-rewrite:
uri: /graphql
- name: graphql-limit-count-route-2
uris:
- /graphql2
plugins:
proxy-rewrite:
uri: /graphql
upstream:
type: roundrobin
scheme: https
nodes:
- host: api.github.com
port: 443
weight: 1
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
Gateway API currently has a bug where the upstream scheme is not correctly configured. As a result, requests are forwarded over HTTP instead of HTTPS, which leads to the error The plain HTTP request was sent to HTTPS port.
This issue is scheduled to be fixed in APISIX Ingress Controller version 2.0.2 and will also be addressed in API7 Ingress Controller in an upcoming release. Until then, this example cannot be completed using Gateway API.
Create an ApisixRoute with multiple paths that share the same plugin configuration:
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: github-graphql-external-domain
spec:
ingressClassName: apisix
scheme: https
passHost: node
externalNodes:
- type: Domain
name: api.github.com
port: 443
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: graphql-limit-count-shared-route
spec:
ingressClassName: apisix
http:
- name: graphql-limit-count-shared
match:
paths:
- /graphql1
- /graphql2
upstreams:
- name: github-graphql-external-domain
plugins:
- name: proxy-rewrite
enable: true
config:
uri: /graphql
- name: graphql-limit-count
enable: true
config:
count: 2
time_window: 30
rejected_code: 429
policy: local
group: srv1
Apply the configuration to your cluster:
kubectl apply -f graphql-limit-count-ic.yaml
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 Gateway Nodes with a Redis Server
The following example demonstrates the rate limiting of GraphQL requests across multiple gateway nodes with a Redis server, such that different gateway nodes share the same rate limiting quota.
- Admin API
- ADC
- Ingress Controller
Create a route with the following configurations in the gateway group:
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
}
}
}'
Create a route with Redis-based rate limiting:
services:
- name: graphql-redis-limit-service
routes:
- name: graphql-redis-limit-route
uris:
- /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
scheme: https
nodes:
- host: api.github.com
port: 443
weight: 1
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
Gateway API currently has a bug where the upstream scheme is not correctly configured. As a result, requests are forwarded over HTTP instead of HTTPS, which leads to the error The plain HTTP request was sent to HTTPS port.
This issue is scheduled to be fixed in APISIX Ingress Controller version 2.0.2 and will also be addressed in API7 Ingress Controller in an upcoming release. Until then, this example cannot be completed using Gateway API.
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: github-graphql-external-domain
spec:
ingressClassName: apisix
scheme: https
passHost: node
externalNodes:
- type: Domain
name: api.github.com
port: 443
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: graphql-redis-limit-route
spec:
ingressClassName: apisix
http:
- name: graphql-redis-limit-route
match:
paths:
- /graphql
upstreams:
- name: github-graphql-external-domain
plugins:
- name: graphql-limit-count
enable: true
config:
count: 2
time_window: 30
rejected_code: 429
key: remote_addr
policy: redis
redis_host: "redis-service.aic.svc"
redis_port: 6379
redis_password: "p@ssw0rd"
redis_database: 1
Apply the configuration to your cluster:
kubectl apply -f graphql-limit-count-ic.yaml
❶ 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 a gateway 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 gateway 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 gateway nodes share the same quota.
Share Quota Among Gateway Nodes with a Redis Cluster
You can also use a Redis cluster to apply the same quota across multiple gateway nodes, such that different gateway 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.
- Admin API
- ADC
- Ingress Controller
Create a route with the following configurations in the gateway group:
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
}
}
}'
Create a route with Redis cluster-based rate limiting:
services:
- name: graphql-redis-cluster-limit-service
routes:
- name: graphql-redis-cluster-limit-route
uris:
- /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
scheme: https
nodes:
- host: api.github.com
port: 443
weight: 1
Synchronize the configuration to the gateway:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
Gateway API currently has a bug where the upstream scheme is not correctly configured. As a result, requests are forwarded over HTTP instead of HTTPS, which leads to the error The plain HTTP request was sent to HTTPS port.
This issue is scheduled to be fixed in APISIX Ingress Controller version 2.0.2 and will also be addressed in API7 Ingress Controller in an upcoming release. Until then, this example cannot be completed using Gateway API.
apiVersion: apisix.apache.org/v2
kind: ApisixUpstream
metadata:
namespace: aic
name: github-graphql-external-domain
spec:
ingressClassName: apisix
scheme: https
passHost: node
externalNodes:
- type: Domain
name: api.github.com
port: 443
---
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
namespace: aic
name: graphql-redis-cluster-limit-route
spec:
ingressClassName: apisix
http:
- name: graphql-redis-cluster-limit-route
match:
paths:
- /graphql
upstreams:
- name: github-graphql-external-domain
plugins:
- name: graphql-limit-count
enable: true
config:
count: 2
time_window: 30
rejected_code: 429
key: remote_addr
policy: redis-cluster
redis_cluster_nodes:
- "redis-cluster-0.redis-cluster.aic.svc:6379"
- "redis-cluster-1.redis-cluster.aic.svc:6379"
redis_password: "p@ssw0rd"
redis_cluster_name: redis-cluster-1
redis_cluster_ssl: true
Apply the configuration to your cluster:
kubectl apply -f graphql-limit-count-ic.yaml
❶ 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 a gateway 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 gateway 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 gateway nodes share the same quota.