Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
27 changes: 27 additions & 0 deletions .changesets/exp_geal_entity_cache_documentation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
### Entity cache preview ([PR #5574](https://github.com/apollographql/router/pull/5574))

#### Support private information caching

The router supports a new `private_id` option that enables separate, private cache entries to be allocated per user for authenticated requests.

When a subgraph returns a `Cache-Control: private` header, the response data shouldn't be cached and shared among users. However, since the router supports request authentication, it can use it to allocate separate cache entries per users.

To enable this, configure the `private_id` to be the name of a key in the request context that contains the data that's used to differentiate users. This option must be paired with a coprocessor or Rhai script to set the value in context.

Example configuration:
Comment thread
Geal marked this conversation as resolved.

```yaml title="router.yaml"
# Enable entity caching globally
preview_entity_cache:
enabled: true
subgraph:
all:
enabled: true
accounts:
private_id: "user_id"
```


Comment thread
Geal marked this conversation as resolved.
To learn more about configuring and customizing private information caching, go to [Private information caching](https://www.apollographql.com/docs/router/configuration/entity-caching/#private-information-caching) docs.

By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/5574
119 changes: 117 additions & 2 deletions docs/source/configuration/entity-caching.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,15 @@ preview_entity_cache:
ttl: 120s # overrides the global TTL
inventory:
enabled: false # disable for a specific subgraph
accounts:
private_id: "user_id"
```

### Configure time to live (TTL)

Besides configuring a global TTL for all the entries in Redis, the GraphOS Router also honors the [`Cache-Control` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) returned with the subgraph response. It generates a `Cache-Control` header for the client response by aggregating the TTL information from all response parts.
A TTL has to be configured for all subgraphs using entity caching, either defined in the per subgraph configuration or inherited from the global configuration.
To decide whether to cache an entity, the router honors the [`Cache-Control` header](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control) returned with the subgraph response. Because `Cache-Control` might not contain a `max-age` or `s-max-age` option, a default TTL must either be defined per subgraph configuration or inherited from the global configuration.

The router also generates a `Cache-Control` header for the client response by aggregating the TTL information from all response parts. If a subgraph doesn't return the header, its response is assumed to be `no-store`.

### Customize Redis cache key

Expand All @@ -123,6 +126,118 @@ This entry contains an object with the `all` field to affect all subgraph reques

```

### Private information 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 may be able to recognize different users and store their data in different parts of the cache.

To set up private information caching, you can configure the `private_id` option. `private_id` is a string pointing at a field in the request context that contains data used to recognize users (for example, user id, or `sub` claim in JWT).

As an example, if you are using the router's JWT authentication plugin, you can first configure the `private_id` option in the `accounts` subgraph to point to the `user_id` key in context, then use a Rhai script to set that key from the JWT's `sub` claim:

```yaml title="router.yaml"
preview_entity_cache:
enabled: true
subgraph:
all:
enabled: true
redis:
urls: ["redis://..."]
subgraphs:
accounts:
private_id: "user_id"
authentication:
router:
jwt:
jwks:
- url: https://auth-server/jwks.json
Comment thread
Geal marked this conversation as resolved.
Outdated
```

```rhai title="main.rhai"
fn supergraph_service(service) {
let request_callback = |request| {
let claims = request.context[Router.APOLLO_AUTHENTICATION_JWT_CLAIMS];

if claims != () {
let private_id = claims["sub"];
request.context["user_id"] = private_id;
}
};

service.map_request(request_callback);
}
```

The router implements 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 interrogate the cache, but it 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.
Comment thread
Geal marked this conversation as resolved.

### Observability

The router supports a [`cache` selector](./telemetry/instrumentation/selectors#subgraph) in telemetry for the subgraph service. The selector returns the number of cache hits or misses by an entity for a subgraph request.

## Spans

You can add a new attribute on the subgraph span for the number of cache hits. For example:

```yaml title="router.yaml"
telemetry:
instrumentation:
spans:
subgraph:
attributes:
cache.hit:
cache: hit
```

## Metrics

The router provides the `telemetry.instrumentation.instruments.cache` instrument to enable cache metrics:

```yaml title="router.yaml"
telemetry:
instrumentation:
instruments:
cache: # Cache instruments configuration
apollo.router.operations.entity.cache: # A counter which counts the number of cache hit and miss for subgraph requests
attributes:
entity.type: true # Include the entity type name. default: false
subgraph.name: # Custom attributes to include the subgraph name in the metric
subgraph_name: true
supergraph.operation.name: # Add custom attribute to display the supergraph operation name
supergraph_operation_name: string
# You can add more custom attributes using subgraph selectors
```

You can use custom instruments to create metrics for the subgraph service. The following example creates a custom instrument to generate a histogram that measures the subgraph request duration when there's at least one cache hit for the "inventory" subgraph:

```yaml title="router.yaml"
telemetry:
instrumentation:
instruments:
subgraph:
only_cache_hit_on_subgraph_inventory:
type: histogram
value: duration
unit: hit
description: histogram of subgraph request duration when we have cache hit on subgraph inventory
condition:
all:
- eq:
- subgraph_name: true # subgraph selector
- inventory
- gt: # If the number of cache hit is greater than 0
- cache: hit
# entity_type: Product # Here you could also only check for the entity type Product, it's `all` by default if we don't specify this config.
- 0

```


## Implementation notes

### Cache-Control header requirement
Expand Down