Response Cache Customization
Cache private data and customize cache keys
Beyond basic response caching, the router supports customization for caching private data, modifying cache keys, and advanced Redis configuration.
When to customize
Consider these advanced customization options when:
You need to cache user-specific data (
PRIVATEcache scope)Cache entries should vary based on request headers (locale, tenant ID, feature flags)
You require multiple Redis instances for different subgraphs
You need fine-tuned Redis performance (connection pools, timeouts, namespacing)
For basic response caching setup, see the Quickstart page.
Private data caching
A subgraph can return a response with the header Cache-Control: private, indicating that it contains user-personalized data. Although this usually forbids intermediate servers from storing data, the router can recognize different users and store their data in different parts of the cache.
Use private data caching when:
Your subgraph returns user-specific data that can be cached (shopping cart, user preferences, personalized recommendations)
You can reliably identify users through authentication tokens or session data
The performance gain from caching user-specific data outweighs the complexity of managing separate cache entries per user
Don't use private data caching if:
Your data contains highly sensitive information that should never be cached
You can't reliably identify users across requests
User-specific data changes too frequently to benefit from caching
Configure private_id
To set up private information caching, configure the private_id option. This option is a string pointing at a field in the request context that contains data used to recognize users (for example, a user ID, or sub claim in JWT).
As an example, if you are using the router's JWT authentication plugin, first configure the private_id option in the accounts subgraph to point to the user_id key in the context. Then, use a Rhai script to set that key from the JWT's sub claim:
1preview_response_cache:
2 enabled: true
3 subgraph:
4 all:
5 enabled: true
6 redis:
7 urls: ["redis://..."]
8 subgraphs:
9 accounts:
10 private_id: "user_id"
11authentication:
12 router:
13 jwt:
14 jwks:
15 - url: https://auth-server/jwks.json1fn supergraph_service(service) {
2 let request_callback = |request| {
3 let claims = request.context[Router.APOLLO_AUTHENTICATION_JWT_CLAIMS];
4
5 if claims != () {
6 let private_id = claims["sub"];
7 request.context["user_id"] = private_id;
8 }
9 };
10
11 service.map_request(request_callback);
12}How private data caching works
The router performs the following sequence to determine whether a particular query returns private data:
Upon seeing a query for the first time, the router requests the cache as if it were a public-only query.
When the subgraph returns the response with private data, the router recognizes it and stores the data in a user-specific part of the cache.
The router stores the query in a list of known queries with private data.
When the router subsequently sees a known query:
If the private ID isn't provided, the router doesn't check the cache and instead transmits the subgraph response directly.
If the private ID is provided, the router queries the part of the cache for the current user and checks the subgraph if nothing is available.
Custom cache keys
To store data for a particular request in different cache entries, configure the cache key through the apollo::response_cache::key context entry.
Use custom cache keys when you need to:
Cache different versions of the same query based on request headers (locale, currency, feature flags)
Segment cache entries by tenant, region, or API version
Include request-specific context that affects the response but isn't part of the GraphQL query
Configure cache keys
You can customize the response cache key with the apollo::response_cache::key context entry. Data in this entry modifies the data used to generate the cache key, and it can be any valid JSON.
You can apply customizations at multiple scales using different fields in apollo::response_cache::key:
An
allfield, which affects all subgraph requestsA
subgraphsfield, which contains an object with per-subgraph customizationA field for each operation name, which affects only a specific operation
Data within these customizations are not merged. The router chooses data in order of precedence: operation name, subgraph, then all. To set common data, you must also add it to the more specific sections.
Example:
1{
2 "all": 1,
3 "subgraph_operation1": "key1",
4 "subgraph_operation2": {
5 "data": "key2"
6 },
7 "subgraphs": {
8 "my_subgraph": {
9 "locale": "be"
10 }
11 }
12}Example in Rhai:
1fn supergraph_service(service) {
2 let request_callback = |request| {
3 // Include the request header value of "x-my-new-header" in the primary cache key hash to create a unique cache entry for every value of this header
4 request.context[Router.APOLLO_RESPONSE_CACHE_KEY]["all"] = request.headers["x-my-new-header"]; // Applied on all subgraphs
5 };
6
7 service.map_request(request_callback);
8}Example: Multi-tenant caching
Cache different responses for each tenant based on a request header:
1fn supergraph_service(service) {
2 let request_callback = |request| {
3 // Create separate cache entries for each tenant
4 let tenant_id = request.headers["x-tenant-id"];
5 if tenant_id != () {
6 request.context[Router.APOLLO_RESPONSE_CACHE_KEY]["all"] = tenant_id;
7 }
8 };
9
10 service.map_request(request_callback);
11}Example: Locale-specific caching
Cache different responses for each locale:
1fn supergraph_service(service) {
2 let request_callback = |request| {
3 // Create separate cache entries for each locale
4 let locale = request.headers["accept-language"];
5 if locale != () {
6 request.context[Router.APOLLO_RESPONSE_CACHE_KEY]["all"] = locale;
7 }
8 };
9
10 service.map_request(request_callback);
11}Advanced Redis configuration
The router provides multiple Redis options to ensure you can scale properly and achieve the best performance.
For basic Redis setup, see the Quickstart page.
Per-subgraph Redis instances
Configure a global Redis instance (used by default) and override it with specific instances and their own configuration for individual subgraphs:
1# Enable response caching globally
2preview_response_cache:
3 enabled: true
4 subgraph:
5 all:
6 enabled: true
7 # Configure Redis globally
8 redis:
9 urls: ["redis://..."]
10 fetch_timeout: 300ms # Optional, by default: 150ms
11 insert_timeout: 750ms # Optional, by default: 500ms
12 invalidate_timeout: 750ms # Optional, by default: 1s
13 # Configure response caching per subgraph, overrides options from the "all" section
14 subgraphs:
15 products:
16 redis:
17 urls: ["redis://..."] # Override global Redis instance for this specific subgraph
18 pool_size: 15 # Optional. Default: 5
19 namespace: products_response_cache # Optional. Prefix all the cached entries in Redis with this prefix in Redis key
20 inventory:
21 enabled: false # Disable for a specific subgraphRedis URL formats
The response caching configuration must contain one or more URLs using different schemes depending on the expected deployment:
redis- TCP connected to a centralized serverrediss- TLS connected to a centralized serverredis-cluster- TCP connected to a clusterrediss-cluster- TLS connected to a cluster
The URLs must have the following format:
One node
1redis|rediss :// [[username:]password@] host [:port][/database]Example: redis://localhost:6379
Clustered
1redis|rediss[-cluster] :// [[username:]password@] host [:port][?[node=host1:port1][&node=host2:port2][&node=hostN:portN]]or, if configured with multiple URLs:
1[
2 "redis|rediss[-cluster] :// [[username:]password@] host [:port]",
3 "redis|rediss[-cluster] :// [[username:]password@] host1 [:port1]",
4 "redis|rediss[-cluster] :// [[username:]password@] host2 [:port2]"
5]TLS and authentication
For Redis TLS connections, you can set up a client certificate or override the root certificate authority by configuring tls in your router's YAML config file. For example:
1# Enable response caching globally
2preview_response_cache:
3 enabled: true
4 subgraph:
5 all:
6 enabled: true
7 # Configure Redis globally
8 redis:
9 urls: [ "rediss://redis.example.com:6379" ]
10 username: root
11 password: ${env.REDIS_PASSWORD}
12 tls:
13 certificate_authorities: ${file./path/to/ca.crt}
14 client_authentication:
15 certificate_chain: ${file./path/to/certificate_chain.pem}
16 key: ${file./path/to/key.pem}Timeout configuration
Redis connections and commands have default timeouts that you can override. The timeouts are tailored to specific caching operations: fetch, insert, invalidate, and maintenance. Set a lower fetch timeout and a higher insert timeout for a good balance of reliability and client responsiveness:
1preview_response_cache:
2 enabled: true
3 subgraph:
4 all:
5 enabled: true
6 redis:
7 urls: ["redis://..."]
8 fetch_timeout: 250ms
9 insert_timeout: 750ms
10 invalidate_timeout: 750msTTL for cache entries
The ttl option defines the default global expiration for cache entries. You must set a TTL for all enabled subgraphs.
To prevent potential cache overflow, consider setting the TTL to 24 hours or twice the median publish interval (whichever is less), and monitor cache utilization in your environment, especially if you cache a lot of different data:
1preview_response_cache:
2 enabled: true
3 subgraph:
4 all:
5 enabled: true
6 ttl: 24h
7 products:
8 enabled: true
9 ttl: 6hNamespace prefix
When using the same Redis instance for multiple purposes, the namespace option defines a prefix for all the keys defined by the router:
1preview_response_cache:
2 enabled: true
3 subgraph:
4 all:
5 enabled: true
6 redis:
7 urls: ["redis://..."]
8 namespace: response_cacheRequired to start
When active, the required_to_start option prevents the router from starting if it can't connect to Redis. By default, the router starts without a connection to Redis, which means it sends requests to subgraphs directly, bypassing the cache:
1preview_response_cache:
2 enabled: true
3 subgraph:
4 all:
5 enabled: true
6 redis:
7 urls: ["redis://..."]
8 required_to_start: trueConnection pool size
The pool_size option defines the number of Redis connections the router opens. By default, the router opens five connections. If you have high traffic between the router and Redis, or if you observe latency in those requests, increase the pool size to reduce that latency. You can measure whether you need to increase this number by monitoring the apollo.router.operations.response_cache.insert and apollo.router.operations.response_cache.fetch response cache metrics. If the time to insert and fetch data from the cache is long, it might be because of delays in acquiring a connection from the pool:
1preview_response_cache:
2 enabled: true
3 subgraph:
4 all:
5 enabled: true
6 redis:
7 urls: ["redis://..."]
8 pool_size: 15