diff --git a/.changesets/exp_geal_entity_cache_documentation.md b/.changesets/exp_geal_entity_cache_documentation.md new file mode 100644 index 0000000000..546cc6cd40 --- /dev/null +++ b/.changesets/exp_geal_entity_cache_documentation.md @@ -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: + +```yaml title="router.yaml" +# Enable entity caching globally +preview_entity_cache: + enabled: true + subgraph: + all: + enabled: true + accounts: + private_id: "user_id" +``` + + +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 \ No newline at end of file diff --git a/docs/source/configuration/entity-caching.mdx b/docs/source/configuration/entity-caching.mdx index 6d53a2ba7b..53aeb930f5 100644 --- a/docs/source/configuration/entity-caching.mdx +++ b/docs/source/configuration/entity-caching.mdx @@ -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 @@ -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 +``` + +```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. + +### 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