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
20 changes: 19 additions & 1 deletion apollo-router/src/plugins/response_cache/plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1763,7 +1763,21 @@ async fn cache_store_entities_from_response(
None
};

// Support cache tags coming from subgraph extensions
// cache tags coming from the subgraph response extension allow for granular invalidation
// by being very specific about _which_ entities we're invalidating. The nested arrays look
// like this:
//
// "data": {"_entities": [{"id": 1, ...}, {"id": 2, ...}]}
// "extensions": {"apolloEntityCacheTags": [["tag-1"], ["tag-2"]]}
//
// where entity with id=1 corresponds to ["tag-1"] and entity with id=2 to ["tag-2"]
//
// this nested array design was in response to how things were previously: a single flat
// array with all the invalidation cache tags, which would invalidate _all_ entities when
// any tag was matched. With nested arrays, each array positionally corresponds to a
// specific entity and its tags are treated individually to that entity rather than
// applying to all entities

let per_entity_surrogate_keys = response
.response
.body()
Expand Down Expand Up @@ -2453,6 +2467,8 @@ async fn insert_entities_in_result(
let mut to_insert: Vec<_> = Vec::new();
let mut debug_ctx_entries = Vec::new();
let mut entities_it = entities.drain(..).enumerate();
// iterate through per-entity cache tags in parallel with entities; tags are matched
// positionally, meaning the first tag array applies to the first entity, etc
let mut per_entity_surrogate_keys_it = per_entity_surrogate_keys.iter();

// insert requested entities and cached entities in the same order as
Expand Down Expand Up @@ -2509,6 +2525,8 @@ async fn insert_entities_in_result(
has_errors = true;
}

// apply per-entity cache tags from the subgraph's apolloEntityCacheTags extension; these tags
// enable targeted cache invalidation for this specific entity
if let Some(Value::Array(keys)) = specific_surrogate_keys {
invalidation_keys
.extend(keys.iter().filter_map(|v| v.as_str()).map(|s| s.to_owned()));
Expand Down
24 changes: 24 additions & 0 deletions apollo-router/tests/integration/response_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,30 @@ async fn invalidate_with_endpoint_by_type() {
");
}

/// Tests cache invalidation by entity cache tags set via the `apolloEntityCacheTags` extension.
///
/// The `experimental_mock_subgraphs` plugin generates `apolloEntityCacheTags` from `__cacheTags`
/// fields in the mock entity data. The extension is an array of arrays where each inner array
/// corresponds **positionally** to entities in the `_entities` response array.
///
/// For example, with this mock configuration in `base_subgraphs()`:
/// ```json
/// "entities": [
/// {"__cacheTags": ["product-1"], "__typename": "Product", "upc": "1", ...},
/// {"__cacheTags": ["product-2"], "__typename": "Product", "upc": "2", ...},
/// ]
/// ```
///
/// The subgraph response will include:
/// ```json
/// "extensions": {"apolloEntityCacheTags": [["product-1"], ["product-2"]]}
/// ```
///
/// The router associates cache tags positionally: the first entity (upc "1") gets tags
/// `["product-1"]`, the second entity (upc "2") gets tags `["product-2"]`.
///
/// This test verifies that invalidating by cache tag `"product-1"` only invalidates the
/// first entity's cache, requiring a new fetch from the reviews subgraph.
#[tokio::test(flavor = "multi_thread")]
async fn invalidate_with_endpoint_by_entity_cache_tag() {
if !graph_os_enabled() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -423,34 +423,70 @@

If you need to set cache tags programmatically (for example, if the tag depends on neither root field arguments nor entity keys), create the cache tags in your subgraph and set them in the response extensions.

For cache tags on _entities_, set `apolloEntityCacheTags` in `extensions`. The following example shows a response payload that sets cache tags for entities returned by a subgraph:
The router uses two different extensions because entities and root fields are cached differently:

Check warning on line 426 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L426

Do not use the article "The" before standalone product names like Apollo Router. ```suggestion Apollo Router uses two different extensions because entities and root fields are cached differently: ```
- **Entities** are cached individually—each entity in an `_entities` response gets its own cache entry. Use `apolloEntityCacheTags` with an array of arrays to assign different tags to different entities.

Check notice on line 427 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L427

The line uses an em dash (—) without spaces, which is acceptable, but ensure the surrounding context remains reader-centric and uses active voice. The current phrasing is acceptable, but ensure 'apolloEntityCacheTags' is always in code font. ```suggestion - **Entities** are cached individually—each entity in an `_entities` response gets its own cache entry. Use `apolloEntityCacheTags` with an array of arrays to assign different tags to different entities. ```
- **Root fields** are cached as a single unit—the entire subgraph response is one cache entry. Use `apolloCacheTags` with a flat array of tags that apply to the whole response.

Check notice on line 428 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L428

Avoid using 'whole' when 'entire' is more precise and consistent with the preceding clause. ```suggestion - **Root fields** are cached as a single unit—the entire subgraph response is one cache entry. Use `apolloCacheTags` with a flat array of tags that apply to the entire response. ```

#### Entity cache tags

Check notice on line 430 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L430

Use sentence casing for all headings. ```suggestion ### Entity cache tags ```

For cache tags on _entities_, set `apolloEntityCacheTags` in `extensions`. This field must be an array of arrays, where:

Check warning on line 432 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L432

Use the imperative voice and avoid using italics for emphasis on terms that have already been introduced. ```suggestion To set cache tags on entities, set `apolloEntityCacheTags` in `extensions`. This field must be an array of arrays, where: ```
- The outer array corresponds **positionally** to the entities in the `_entities` array

Check notice on line 433 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L433

Include ending punctuation for list items that are complete sentences. ```suggestion - The outer array corresponds positionally to the entities in the `_entities` array. ```
- Each inner array contains string cache tags for that specific entity

Check notice on line 434 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L434

Include ending punctuation for list items that are complete sentences. ```suggestion - Each inner array contains string cache tags for that specific entity. ```

The following example shows a response payload that sets cache tags for entities returned by a subgraph:

Check notice on line 436 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L436

Use the present tense and active voice to improve clarity and brevity. ```suggestion The following example shows a response payload that sets cache tags for entities a subgraph returns: ```

```json
{
"data": {"_entities": [
{"__typename": "User", "id": 42, ...},
{"__typename": "User", "id": 1023, ...},
{"__typename": "User", "id": 7, ...},
{"__typename": "User", "id": 42, "name": "Alice"},
{"__typename": "User", "id": 1023, "name": "Bob"},
{"__typename": "User", "id": 7, "name": "Charlie"}
]},
"extensions": {"apolloEntityCacheTags": [
["products", "product-42"],
["products", "product-1023"],
["products", "product-7"]
["users", "user-42"],
["users", "user-1023"],
["users", "user-7"]
]}
}
```

For cache tags on _root fields_, set `apolloCacheTags` in `extensions`. The following example shows a response payload that sets cache tags for root fields returned by a subgraph:
In this example:

Check notice on line 453 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L453

Introduce lists with a complete sentence or fragment that ends in a colon. Avoid using 'this' as a vague reference. ```suggestion The example demonstrates the following: ```
- The first entity (User with id 42) is tagged with `["users", "user-42"]`

Check warning on line 454 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L454

Use ID (no code font) when referring generically to an identifier, and use numerals with code font when referring to a number used in code. ```suggestion - The first entity (User with ID `42`) is tagged with `["users", "user-42"]` ```
- The second entity (User with id 1023) is tagged with `["users", "user-1023"]`

Check notice on line 455 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L455

Use ID (no codefont) when referring generically to something's identifier. ```suggestion - The second entity (User with ID 1023) is tagged with `["users", "user-1023"]` ```
- The third entity (User with id 7) is tagged with `["users", "user-7"]`

Check warning on line 456 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L456

Use numerals with code font when referring to a number used in code or a CLI command. ```suggestion - The third entity (User with id `7`) is tagged with `["users", "user-7"]` ```

Because each entity is cached separately with its own tags, you can invalidate individual entities. For example, invalidating by tag `user-42` only removes User 42 from the cache, while invalidating by tag `users` removes all three users.

Check notice on line 458 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L458

Avoid using the word 'just' or 'simply' when describing tasks; however, this line is acceptable. Ensure you use code font for all user inputs and symbols like `user-42` and `users`. ```suggestion Because each entity is cached separately with its own tags, you can invalidate individual entities. For example, invalidating by tag `user-42` only removes User 42 from the cache, while invalidating by tag `users` removes all three users. ```

To invalidate using these programmatically-set tags, send a request to the invalidation endpoint:

Check notice on line 460 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L460

Remove the hyphen from 'programmatically set' as it is an adverb modifying a verb and does not require a hyphen. ```suggestion To invalidate using these programmatically set tags, send a request to the invalidation endpoint: ```

```json
[{
"kind": "cache_tag",
"subgraphs": ["your-subgraph-name"],
"cache_tag": "user-42"
}]
```

#### Root field cache tags

For cache tags on _root fields_, set `apolloCacheTags` in `extensions`. This field is a flat array of strings because the entire root field response is cached as a single entry—all tags apply to that one cache entry.

The following example shows a response payload that sets cache tags for a `homepage` query:

Check warning on line 474 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L474

Use 'following' instead of 'below' when referring to content in body text. ```suggestion The following example shows a response payload that sets cache tags for a `homepage` query: ```

```json
{
"data": {
"someField": {...}
"homepage": {
"featuredProducts": [...],
"currentUser": {"id": 9001, "name": "Jane"}
}
},
"extensions": {"apolloCacheTags": ["homepage", "user-9001-homepage"]}
}
```

In this example, both tags (`homepage` and `user-9001-homepage`) are applied to the cached response. Later, you can invalidate this cached response by targeting either tag.

Check notice on line 488 in docs/source/routing/performance/caching/response-caching/invalidation.mdx

View check run for this annotation

Apollo Librarian / AI Style Review

docs/source/routing/performance/caching/response-caching/invalidation.mdx#L488

The term 'homepage' is the preferred spelling over 'home page'. ```suggestion In this example, both tags (`homepage` and `user-9001-homepage`) are applied to the cached response. Later, you can invalidate this cached response by targeting either tag. ```

### Invalidation HTTP endpoint

The invalidation endpoint exposed by the router expects to receive an array of invalidation requests and processes them in sequence. For authorization, you must provide a shared key in the request header. For example, with the previous configuration, send the following request:
Expand Down