From 2bb16a3b181dfdd50d45b18a74b43ef06a875552 Mon Sep 17 00:00:00 2001 From: Gigi Sayfan Date: Wed, 7 Jan 2026 22:40:37 -0800 Subject: [PATCH 1/6] updated response caching docs and addressed discrepancies --- .../routing/performance/caching/response-caching/faq.mdx | 7 +++++-- .../performance/caching/response-caching/quickstart.mdx | 8 ++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/source/routing/performance/caching/response-caching/faq.mdx b/docs/source/routing/performance/caching/response-caching/faq.mdx index 1e34a97448..938129a7c5 100644 --- a/docs/source/routing/performance/caching/response-caching/faq.mdx +++ b/docs/source/routing/performance/caching/response-caching/faq.mdx @@ -25,7 +25,7 @@ When you use the router's [authorization directives](/router/configuration/autho ### How do schema updates affect my cache? -Cached data remains valid across schema updates. When a new schema is deployed, the router immediately invalidates cache entries that depend on changed portions of the schema, ensuring responses always match the current schema. Cache entries for unchanged schema portions remain available. +The router does not automatically invalidate cache entries when a schema is updated. Cache entries expire based on their TTL or through explicit invalidation via the invalidation API. If a schema change affects the structure of cached data, you should either wait for TTL expiration or use the invalidation API to clear affected entries. ### Does the router cache error responses? @@ -63,7 +63,10 @@ Operations without tags eventually expire based on their TTL. You can invalidate ### How does caching work for operations with multiple root fields? -The router caches the entire operation response as a single unit. This means operations with overlapping root fields don't currently share cache entries—each unique operation gets its own entry. +The router caches at the subgraph fetch level, not the entire operation. Each root field query and entity fetch is cached separately using a key that includes the subgraph name, type, query hash, and authorization context. This means: + +- **Root fields**: Each root field query to a subgraph gets its own cache entry. Different operations that result in the same subgraph query can share cache entries. +- **Entities**: Entity fetches are cached per entity representation. When different operations request the same entity with the same fields, they can share the cached entity data. ### When should I use invalidation vs. TTL-based caching? diff --git a/docs/source/routing/performance/caching/response-caching/quickstart.mdx b/docs/source/routing/performance/caching/response-caching/quickstart.mdx index 3ac109ca2c..6a7a6a53b3 100644 --- a/docs/source/routing/performance/caching/response-caching/quickstart.mdx +++ b/docs/source/routing/performance/caching/response-caching/quickstart.mdx @@ -31,8 +31,15 @@ To use response caching in GraphOS Router, you must set up: In `router.yaml`, configure `response_cache`: - Enable response caching globally - Configure Redis using the same conventions described in [distributed caching](/router/configuration/distributed-caching#redis-url-configuration) +- **Configure a TTL** either globally under `subgraph.all.ttl` or for each individual subgraph—the router requires a TTL to be configured and will fail to start without one - Configure response caching per subgraph, with overrides per subgraph for disabling response caching and TTL + + +A TTL must be configured globally or for every subgraph. The router uses this as the fallback cache duration when a subgraph response doesn't include a `Cache-Control` header with a `max-age` directive. When a response does include `Cache-Control: max-age`, the router uses that value instead of the configured TTL. + + + For example: ```yaml title="router.yaml" @@ -46,6 +53,7 @@ response_cache: subgraph: all: enabled: true + ttl: 300s # Required: fallback TTL when responses don't include Cache-Control headers # Configure Redis for all subgraphs redis: urls: ["redis://localhost:6379"] From c7ecc7f9da28f1a4976702e432a507ad11226d38 Mon Sep 17 00:00:00 2001 From: Gigi Sayfan Date: Thu, 15 Jan 2026 01:01:41 -0800 Subject: [PATCH 2/6] addressed review comments --- .../routing/performance/caching/response-caching/faq.mdx | 7 ++----- .../performance/caching/response-caching/quickstart.mdx | 4 ++-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/docs/source/routing/performance/caching/response-caching/faq.mdx b/docs/source/routing/performance/caching/response-caching/faq.mdx index 938129a7c5..070c6017c2 100644 --- a/docs/source/routing/performance/caching/response-caching/faq.mdx +++ b/docs/source/routing/performance/caching/response-caching/faq.mdx @@ -25,7 +25,7 @@ When you use the router's [authorization directives](/router/configuration/autho ### How do schema updates affect my cache? -The router does not automatically invalidate cache entries when a schema is updated. Cache entries expire based on their TTL or through explicit invalidation via the invalidation API. If a schema change affects the structure of cached data, you should either wait for TTL expiration or use the invalidation API to clear affected entries. +The router does not explicitly remove cache entries when a schema is updated. However, if a schema change affects the queries sent to subgraphs, the router generates new cache keys for those queries. This means old cache entries won't receive cache hits anymore—from your perspective, they're effectively expired even though the data may still exist in storage until its TTL expires. Cache entries for unchanged query patterns remain available and continue to serve cache hits normally. ### Does the router cache error responses? @@ -63,10 +63,7 @@ Operations without tags eventually expire based on their TTL. You can invalidate ### How does caching work for operations with multiple root fields? -The router caches at the subgraph fetch level, not the entire operation. Each root field query and entity fetch is cached separately using a key that includes the subgraph name, type, query hash, and authorization context. This means: - -- **Root fields**: Each root field query to a subgraph gets its own cache entry. Different operations that result in the same subgraph query can share cache entries. -- **Entities**: Entity fetches are cached per entity representation. When different operations request the same entity with the same fields, they can share the cached entity data. +The router caches the entire response from each subgraph fetch as a single unit. When an operation queries multiple root fields from the same subgraph, the complete response (containing all those root fields) is cached together under a single cache entry. This means operations with overlapping root fields don't currently share cache entries—each unique subgraph request gets its own entry based on the full query sent to that subgraph. ### When should I use invalidation vs. TTL-based caching? diff --git a/docs/source/routing/performance/caching/response-caching/quickstart.mdx b/docs/source/routing/performance/caching/response-caching/quickstart.mdx index 6a7a6a53b3..e23c8fd227 100644 --- a/docs/source/routing/performance/caching/response-caching/quickstart.mdx +++ b/docs/source/routing/performance/caching/response-caching/quickstart.mdx @@ -31,7 +31,7 @@ To use response caching in GraphOS Router, you must set up: In `router.yaml`, configure `response_cache`: - Enable response caching globally - Configure Redis using the same conventions described in [distributed caching](/router/configuration/distributed-caching#redis-url-configuration) -- **Configure a TTL** either globally under `subgraph.all.ttl` or for each individual subgraph—the router requires a TTL to be configured and will fail to start without one +- **Configure a TTL** either globally under `subgraph.all.ttl` or for each individual subgraph—the router requires a TTL to be configured and will fail to start without one. This TTL acts as a fallback; when a subgraph response includes a `Cache-Control` header with `max-age`, that value takes precedence. - Configure response caching per subgraph, with overrides per subgraph for disabling response caching and TTL @@ -53,7 +53,7 @@ response_cache: subgraph: all: enabled: true - ttl: 300s # Required: fallback TTL when responses don't include Cache-Control headers + ttl: 5m # Required: fallback TTL when responses don't include Cache-Control headers # Configure Redis for all subgraphs redis: urls: ["redis://localhost:6379"] From 317aa2ec568b695eecc51868290085e6ad7a6dd5 Mon Sep 17 00:00:00 2001 From: Gigi Sayfan Date: Thu, 15 Jan 2026 01:01:41 -0800 Subject: [PATCH 3/6] addressed review comments --- .changesets/docs_response_caching_faq_corrections.md | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changesets/docs_response_caching_faq_corrections.md diff --git a/.changesets/docs_response_caching_faq_corrections.md b/.changesets/docs_response_caching_faq_corrections.md new file mode 100644 index 0000000000..484df1874e --- /dev/null +++ b/.changesets/docs_response_caching_faq_corrections.md @@ -0,0 +1,10 @@ +### Correct response caching documentation for schema updates and multi-root-field caching + +Updated the response caching FAQ to accurately describe caching behavior: + +- Clarified that schema updates generate new cache keys, so old entries won't receive cache hits (effectively expired from the user's perspective) rather than implying stale data might be served. +- Fixed the explanation of multi-root-field caching to correctly state that the router caches the entire subgraph response as a single unit, not separately per root field. +- Added clarification that the configured TTL is a fallback when subgraph responses don't include `Cache-Control: max-age` headers. +- Changed example TTL from `300s` to `5m` for better readability. + +By [@the-gigi-apollo](https://github.com/the-gigi-apollo) in https://github.com/apollographql/router/pull/8794 From f600ffe9ce053d8a3f3ad7520cab54735543be2b Mon Sep 17 00:00:00 2001 From: Gigi Sayfan Date: Thu, 15 Jan 2026 01:09:23 -0800 Subject: [PATCH 4/6] addressed AI style suggestions --- .../routing/performance/caching/response-caching/faq.mdx | 4 ++-- .../performance/caching/response-caching/quickstart.mdx | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/source/routing/performance/caching/response-caching/faq.mdx b/docs/source/routing/performance/caching/response-caching/faq.mdx index 070c6017c2..c8ac8f9e38 100644 --- a/docs/source/routing/performance/caching/response-caching/faq.mdx +++ b/docs/source/routing/performance/caching/response-caching/faq.mdx @@ -25,7 +25,7 @@ When you use the router's [authorization directives](/router/configuration/autho ### How do schema updates affect my cache? -The router does not explicitly remove cache entries when a schema is updated. However, if a schema change affects the queries sent to subgraphs, the router generates new cache keys for those queries. This means old cache entries won't receive cache hits anymore—from your perspective, they're effectively expired even though the data may still exist in storage until its TTL expires. Cache entries for unchanged query patterns remain available and continue to serve cache hits normally. +The router doesn't remove cache entries when you update a schema. However, if a schema change affects the queries sent to subgraphs, the router generates new cache keys for those queries. As a result, old cache entries stop receiving cache hits. They're effectively expired, although the data might still exist in storage until its TTL expires. Cache entries for unchanged query patterns remain available and continue to serve cache hits normally. ### Does the router cache error responses? @@ -63,7 +63,7 @@ Operations without tags eventually expire based on their TTL. You can invalidate ### How does caching work for operations with multiple root fields? -The router caches the entire response from each subgraph fetch as a single unit. When an operation queries multiple root fields from the same subgraph, the complete response (containing all those root fields) is cached together under a single cache entry. This means operations with overlapping root fields don't currently share cache entries—each unique subgraph request gets its own entry based on the full query sent to that subgraph. +The router caches the entire response from each subgraph fetch as a single unit. When an operation queries multiple root fields from the same subgraph, the router caches the complete response (containing all root fields) under a single cache entry. Operations with overlapping root fields don't share cache entries. The router creates a unique entry for each subgraph request based on the full query sent to the subgraph. ### When should I use invalidation vs. TTL-based caching? diff --git a/docs/source/routing/performance/caching/response-caching/quickstart.mdx b/docs/source/routing/performance/caching/response-caching/quickstart.mdx index e23c8fd227..a16bc7d1a2 100644 --- a/docs/source/routing/performance/caching/response-caching/quickstart.mdx +++ b/docs/source/routing/performance/caching/response-caching/quickstart.mdx @@ -31,15 +31,9 @@ To use response caching in GraphOS Router, you must set up: In `router.yaml`, configure `response_cache`: - Enable response caching globally - Configure Redis using the same conventions described in [distributed caching](/router/configuration/distributed-caching#redis-url-configuration) -- **Configure a TTL** either globally under `subgraph.all.ttl` or for each individual subgraph—the router requires a TTL to be configured and will fail to start without one. This TTL acts as a fallback; when a subgraph response includes a `Cache-Control` header with `max-age`, that value takes precedence. +- Configure a TTL either globally under `subgraph.all.ttl` or for each individual subgraph. The router requires a configured TTL and fails to start without one. This TTL acts as a fallback. When a subgraph response includes a `Cache-Control` header with `max-age`, that value takes precedence. - Configure response caching per subgraph, with overrides per subgraph for disabling response caching and TTL - - -A TTL must be configured globally or for every subgraph. The router uses this as the fallback cache duration when a subgraph response doesn't include a `Cache-Control` header with a `max-age` directive. When a response does include `Cache-Control: max-age`, the router uses that value instead of the configured TTL. - - - For example: ```yaml title="router.yaml" From 0a6bdbd9ffd556f219b92bb5f9068e4ef307ba03 Mon Sep 17 00:00:00 2001 From: the-gigi-apollo Date: Sat, 17 Jan 2026 16:51:14 -0800 Subject: [PATCH 5/6] Update docs/source/routing/performance/caching/response-caching/faq.mdx Co-authored-by: Coenen Benjamin --- .../source/routing/performance/caching/response-caching/faq.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/routing/performance/caching/response-caching/faq.mdx b/docs/source/routing/performance/caching/response-caching/faq.mdx index c8ac8f9e38..81894f7c26 100644 --- a/docs/source/routing/performance/caching/response-caching/faq.mdx +++ b/docs/source/routing/performance/caching/response-caching/faq.mdx @@ -63,7 +63,7 @@ Operations without tags eventually expire based on their TTL. You can invalidate ### How does caching work for operations with multiple root fields? -The router caches the entire response from each subgraph fetch as a single unit. When an operation queries multiple root fields from the same subgraph, the router caches the complete response (containing all root fields) under a single cache entry. Operations with overlapping root fields don't share cache entries. The router creates a unique entry for each subgraph request based on the full query sent to the subgraph. +The router caches the entire response from each subgraph fetch as a single unit. When an operation queries multiple root fields from the same subgraph, the router caches the complete response (containing all root fields) under a single cache entry. Operations with overlapping root fields don't share cache entries. The router creates a unique entry for each subgraph request based on the full query sent to the subgraph except for entities. ### When should I use invalidation vs. TTL-based caching? From 0261e7bdaa80b88b22045473084df969bb66e7a8 Mon Sep 17 00:00:00 2001 From: Gigi Sayfan Date: Sat, 17 Jan 2026 17:07:49 -0800 Subject: [PATCH 6/6] address AI style --- .../routing/performance/caching/response-caching/faq.mdx | 4 ++-- .../performance/caching/response-caching/quickstart.mdx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/routing/performance/caching/response-caching/faq.mdx b/docs/source/routing/performance/caching/response-caching/faq.mdx index 81894f7c26..fb504e3e6b 100644 --- a/docs/source/routing/performance/caching/response-caching/faq.mdx +++ b/docs/source/routing/performance/caching/response-caching/faq.mdx @@ -25,7 +25,7 @@ When you use the router's [authorization directives](/router/configuration/autho ### How do schema updates affect my cache? -The router doesn't remove cache entries when you update a schema. However, if a schema change affects the queries sent to subgraphs, the router generates new cache keys for those queries. As a result, old cache entries stop receiving cache hits. They're effectively expired, although the data might still exist in storage until its TTL expires. Cache entries for unchanged query patterns remain available and continue to serve cache hits normally. +The router doesn't remove cache entries when you update a schema. However, if a schema change affects the queries sent to subgraphs, the router generates new cache keys for those queries. As a result, old cache entries stop serving cache hits. They're effectively expired, although the data might still exist in storage until its TTL expires. Cache entries for unchanged query patterns remain available and continue to serve cache hits normally. ### Does the router cache error responses? @@ -63,7 +63,7 @@ Operations without tags eventually expire based on their TTL. You can invalidate ### How does caching work for operations with multiple root fields? -The router caches the entire response from each subgraph fetch as a single unit. When an operation queries multiple root fields from the same subgraph, the router caches the complete response (containing all root fields) under a single cache entry. Operations with overlapping root fields don't share cache entries. The router creates a unique entry for each subgraph request based on the full query sent to the subgraph except for entities. +The router caches the entire response from each subgraph request as a single unit. When an operation queries multiple root fields from the same subgraph, the router caches the complete response (containing all root fields) under a single cache entry. Operations with overlapping root fields don't share cache entries. The router creates a unique entry for each subgraph request based on the full query sent to the subgraph. This behavior does not apply to entity requests. ### When should I use invalidation vs. TTL-based caching? diff --git a/docs/source/routing/performance/caching/response-caching/quickstart.mdx b/docs/source/routing/performance/caching/response-caching/quickstart.mdx index a16bc7d1a2..420fb0be5a 100644 --- a/docs/source/routing/performance/caching/response-caching/quickstart.mdx +++ b/docs/source/routing/performance/caching/response-caching/quickstart.mdx @@ -31,7 +31,7 @@ To use response caching in GraphOS Router, you must set up: In `router.yaml`, configure `response_cache`: - Enable response caching globally - Configure Redis using the same conventions described in [distributed caching](/router/configuration/distributed-caching#redis-url-configuration) -- Configure a TTL either globally under `subgraph.all.ttl` or for each individual subgraph. The router requires a configured TTL and fails to start without one. This TTL acts as a fallback. When a subgraph response includes a `Cache-Control` header with `max-age`, that value takes precedence. +- Configure a TTL either globally in `subgraph.all.ttl` or for each subgraph. The router requires a configured TTL to start. This TTL serves as a fallback. When a subgraph response includes a `Cache-Control` header with `max-age`, that value takes precedence. - Configure response caching per subgraph, with overrides per subgraph for disabling response caching and TTL For example: