Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions .changesets/feat_response_cache_ga.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
### Response caching is now Generally Available 🎉 ([PR #8678](https://github.com/apollographql/router/pull/8678))

We're excited to announce that **response caching is now Generally Available (GA)** and ready for production use!

Response caching is a powerful feature that enables the router to cache subgraph query responses using Redis, dramatically improving query latency and reducing load on your underlying services. Unlike traditional HTTP caching solutions, response caching provides GraphQL-aware caching at the entity and root field level, making cached data reusable across different users and queries.

#### Key benefits

- **Improved performance**: Cache origin responses and reuse them across queries to reduce latency
- **Reduced subgraph load**: Minimize redundant requests to your subgraphs by serving cached data
- **Entity-level caching**: Cache individual entity representations independently, enabling fine-grained control over data freshness
- **Flexible cache control**: Set different TTLs for different types of data based on `@cacheControl` directives in your schema or the `Cache-Control` response header
- **Privacy-aware**: Mix public and private data safely—share cached data across users while maintaining privacy for personalized data
- **GraphQL-native**: Solve unique GraphQL caching challenges that traditional CDNs can't address, such as mixed TTLs and high data duplication
- **Active cache invalidation**: Use the `@cacheTag` directive in your schema to tag cached data, then actively invalidate specific cache entries via an HTTP endpoint when data changes

#### What's cached

The router caches two kinds of data:
- **Root query fields**: Cached as complete units (the entire response for these root fields)
- **Entity representations**: Cached independently—each origin's contribution to an entity is cached separately and can be reused across different queries


#### Configuration

Response caching uses Redis as the cache backend and can be configured per subgraph with invalidation support:

```yaml
response_cache:
enabled: true
# Configure the invalidation endpoint
invalidation:
listen: 127.0.0.1:4000
path: /invalidation
subgraph:
all:
enabled: true
ttl: 30s
redis:
urls:
- redis://127.0.0.1:6379
invalidation:
enabled: true
shared_key: ${env.INVALIDATION_SHARED_KEY}
```

The router determines cache TTLs from `Cache-Control` HTTP headers returned by your origins. You also get comprehensive Redis metrics to monitor cache performance, including connection health, command execution, and operational insights.

#### Cache invalidation with @cacheTag

Tag your cached data using the `@cacheTag` directive in your subgraph schema, then actively invalidate specific cache entries when data changes:

```graphql
type Query {
user(id: ID!): User @cacheTag(format: "user-{$args.id}")
users: [User!]! @cacheTag(format: "users-list")
}

type User @key(fields: "id") @cacheTag(format: "user-{$key.id}") {
id: ID!
name: String!
}
```

Send invalidation requests to remove cached data before TTL expires:

```bash
curl --request POST \
--header "authorization: $INVALIDATION_SHARED_KEY" \
--header 'content-type: application/json' \
--url http://localhost:4000/invalidation \
--data '[{"kind":"cache_tag","subgraphs":["posts"],"cache_tag":"user-42"}]'
```

#### Additional features

- **Cache debugger**: See exactly what's being cached during development with detailed cache key inspection
- **Redis cluster support**: Scale your cache with Redis cluster deployments and benefit from read replicas for improved performance and availability
- **Comprehensive metrics**: Monitor cache performance with detailed Redis-specific metrics including connection health, command execution, and latency

For complete documentation, configuration options, and best practices, see the [response caching documentation](/router/routing/performance/caching/response-caching/overview).

By [@bnjjj](https://github.com/bnjjj) in https://github.com/apollographql/router/pull/8678
6 changes: 3 additions & 3 deletions .changesets/fix_response_cache_invalidation_endpoint.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
### Enable invalidation endpoint for response cache when any subgraph has invalidation enabled ([PR #8680](https://github.com/apollographql/router/pull/8680))

Previously, the response cache invalidation endpoint was only enabled when global invalidation was enabled via `preview_response_cache.subgraph.all.invalidation.enabled`. This meant that if you enabled invalidation for only specific subgraphs without enabling it globally, the invalidation endpoint would not be started, preventing cache invalidation requests from being processed.
Previously, the response cache invalidation endpoint was only enabled when global invalidation was enabled via `response_cache.subgraph.all.invalidation.enabled`. This meant that if you enabled invalidation for only specific subgraphs without enabling it globally, the invalidation endpoint would not be started, preventing cache invalidation requests from being processed.

Now, the invalidation endpoint is enabled if either:
- Global invalidation is enabled (`preview_response_cache.subgraph.all.invalidation.enabled: true`), OR
- Global invalidation is enabled (`response_cache.subgraph.all.invalidation.enabled: true`), OR
- Any individual subgraph has invalidation enabled

This allows for more flexible configuration where you can enable invalidation selectively for specific subgraphs:

```yaml
preview_response_cache:
response_cache:
enabled: true
invalidation:
listen: 127.0.0.1:4000
Expand Down
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1161,4 +1161,4 @@ workflows:
- secops-oidc
- github-orb
git-base-revision: <<#pipeline.git.base_revision>><<pipeline.git.base_revision>><</pipeline.git.base_revision >>
disabled-signatures: "rules.providers.semgrep.security.javascript.lang.security.detect-insecure-websocket"
disabled-signatures: "rules.providers.semgrep.security.javascript.lang.security.detect-insecure-websocket"
2 changes: 1 addition & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,4 @@ For full information on available options, including HTML reports and `lcov.info

## Project maintainers

Apollo Graph, Inc.
Apollo Graph, Inc.
6 changes: 3 additions & 3 deletions apollo-router/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ fred = { version = "10.1.0", features = [
"i-cluster",
"tcp-user-timeouts",
"metrics",
"replicas",
"serde-json"
"serde-json",
"replicas"
] }
futures = { version = "0.3.30", features = ["thread-pool"] }
graphql_client = "0.14.0"
Expand Down Expand Up @@ -310,7 +310,7 @@ fred = { version = "10.1.0", features = [
"mocks",
"i-cluster",
"tcp-user-timeouts",
"replicas"
"replicas",
] }
futures-test = "0.3.30"
insta.workspace = true
Expand Down
2 changes: 1 addition & 1 deletion apollo-router/src/configuration/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,7 +323,7 @@ impl InstrumentData {

populate_config_instrument!(
apollo.router.config.response_cache,
"$.preview_response_cache",
"$.response_cache",
opt.enabled,
"$[?(@.enabled)]",
opt.debug,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
description: Response cache feature is now GA
actions:
- type: move
from: preview_response_cache
to: response_cache
4 changes: 2 additions & 2 deletions apollo-router/src/configuration/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,12 +586,12 @@ impl Configuration {
}

// response & entity caching
if self.apollo_plugin_enabled("preview_response_cache")
if self.apollo_plugin_enabled("response_cache")
&& self.apollo_plugin_enabled("preview_entity_cache")
{
return Err(ConfigurationError::InvalidConfiguration {
message: "entity cache and response cache features are mutually exclusive",
error: "either set preview_response_cache.enabled: false or preview_entity_cache.enabled: false in your router yaml configuration".into(),
error: "either set response_cache.enabled: false or preview_entity_cache.enabled: false in your router yaml configuration".into(),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1929,6 +1929,10 @@ expression: "&schema"
"type": "object"
},
"Config7": {
"description": "Configuration for the progressive override plugin",
"type": "object"
},
"Config8": {
"additionalProperties": false,
"description": "Configuration for response caching",
"properties": {
Expand Down Expand Up @@ -1974,7 +1978,7 @@ expression: "&schema"
],
"type": "object"
},
"Config8": {
"Config9": {
"additionalProperties": false,
"description": "Redis cache configuration",
"properties": {
Expand Down Expand Up @@ -2092,10 +2096,6 @@ expression: "&schema"
],
"type": "object"
},
"Config9": {
"description": "Configuration for the progressive override plugin",
"type": "object"
},
"ConnectorConfiguration": {
"properties": {
"all": {
Expand Down Expand Up @@ -9245,7 +9245,7 @@ expression: "&schema"
"redis": {
"anyOf": [
{
"$ref": "#/definitions/Config8"
"$ref": "#/definitions/Config9"
},
{
"type": "null"
Expand Down Expand Up @@ -11947,8 +11947,8 @@ expression: "&schema"
},
"type": "object"
},
"^preview_response_cache$": {
"$ref": "#/definitions/Config7"
"^response_cache$": {
"$ref": "#/definitions/Config8"
}
},
"properties": {
Expand Down Expand Up @@ -12144,7 +12144,7 @@ expression: "&schema"
"$ref": "#/definitions/FileUploadsConfig"
},
"progressive_override": {
"$ref": "#/definitions/Config9"
"$ref": "#/definitions/Config7"
},
"rhai": {
"$ref": "#/definitions/RhaiConfig"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
source: apollo-router/src/configuration/tests.rs
expression: new_config
---
---
response_cache:
enabled: true
subgraph:
all:
redis:
urls:
- "redis://test"
required_to_start: true
enabled: true
invalidation:
enabled: true
shared_key: invalidate
subgraphs:
accounts:
enabled: false
products:
ttl: 120s
invalidation:
enabled: true
shared_key: invalidate
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
preview_response_cache:
response_cache:
enabled: true
debug: true
invalidation:
Expand All @@ -7,7 +7,7 @@ preview_response_cache:
subgraph:
all:
redis:
urls: [ "redis://test" ]
urls: ["redis://test"]
required_to_start: true
enabled: true
invalidation:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
preview_response_cache:
enabled: true
subgraph:
all:
redis:
urls: ["redis://test"]
required_to_start: true
enabled: true
invalidation:
enabled: true
shared_key: "invalidate"
subgraphs:
accounts:
enabled: false
products:
ttl: 120s
invalidation:
enabled: true
shared_key: "invalidate"
9 changes: 5 additions & 4 deletions apollo-router/src/configuration/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1288,11 +1288,12 @@ fn it_prevents_enablement_of_both_subgraph_caching_plugins() {
let make_config = |response_cache_enabled, entity_cache_enabled| {
let mut config = json!({});
if let Some(enabled) = response_cache_enabled {
config.as_object_mut().unwrap().insert(
"preview_response_cache".to_string(),
json!({"enabled": enabled}),
);
config
.as_object_mut()
.unwrap()
.insert("response_cache".to_string(), json!({"enabled": enabled}));
}

if let Some(enabled) = entity_cache_enabled {
config.as_object_mut().unwrap().insert(
"preview_entity_cache".to_string(),
Expand Down
2 changes: 1 addition & 1 deletion apollo-router/src/plugins/response_cache/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ const DEFAULT_LRU_PRIVATE_QUERIES_SIZE: NonZeroUsize = NonZeroUsize::new(2048).u
const LRU_PRIVATE_QUERIES_INSTRUMENT_NAME: &str =
"apollo.router.response_cache.private_queries.lru.size";

register_private_plugin!("apollo", "preview_response_cache", ResponseCache);
register_private_plugin!("apollo", "response_cache", ResponseCache);

#[derive(Clone)]
pub(crate) struct ResponseCache {
Expand Down
4 changes: 2 additions & 2 deletions apollo-router/src/plugins/response_cache/storage/redis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ impl CacheStorage for Storage {
timeout: Some(self.insert_timeout()),
..Options::default()
};
let pipeline = self.storage.pipeline().with_options(&options);
let pipeline = self.storage.client().pipeline().with_options(&options);
for (cache_tag_key, elements) in cache_tags_to_pcks.into_iter() {
self.send_to_maintenance_queue(cache_tag_key.clone());

Expand Down Expand Up @@ -356,7 +356,7 @@ impl CacheStorage for Storage {
}

// phase 3
let pipeline = self.storage.pipeline().with_options(&options);
let pipeline = self.storage.client().pipeline().with_options(&options);
for (document, cache_tags) in batch_docs.into_iter().zip(original_cache_tags.into_iter()) {
let value = CacheValue {
data: document.data,
Expand Down
2 changes: 1 addition & 1 deletion apollo-router/src/plugins/telemetry/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1741,7 +1741,7 @@ impl Telemetry {
// Response cache's top-level enabled flag defaults to false. If the top-level flag is
// enabled, the feature is considered enabled regardless of the subgraph-level enabled
// settings.
response_cache: full_config["preview_response_cache"]["enabled"]
response_cache: full_config["response_cache"]["enabled"]
.as_bool()
.unwrap_or(false),
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ apq:
enabled: true

# Enable response cache
preview_response_cache:
response_cache:
enabled: true
subgraph:
all:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ preview_entity_cache:
urls: ["redis://..."]

# Disable response cache
preview_response_cache:
response_cache:
enabled: false
subgraph:
all:
Expand Down
Loading