Skip to content

[AS-240] Tech note: Entity caching best practices - #41

Open
ilan-bel wants to merge 3 commits into
mainfrom
docs/as-240-entity-caching
Open

ilan-bel wants to merge 3 commits into
mainfrom
docs/as-240-entity-caching

Conversation

@ilan-bel

@ilan-bel ilan-bel commented Jun 5, 2026

Copy link
Copy Markdown

Summary

Adds docs/entity-caching-best-practices.md. First-draft synthesis of public Apollo guidance covering: when entity caching pays off (and when it doesn't), TTL configuration shape, cache-key composition pitfalls (per-tenant / per-viewer caching — the most common source of cross-user data leaks), invalidation patterns, operational pitfalls (memory, thundering herd, in-process vs distributed cache), and verification via debug logs.

Important

The original AS-240 ticket cites the Wayfair engagement as the primary source. This PR is a first-draft synthesis of public Apollo guidance and general patterns; a reviewer with access to the Wayfair notes should fold in customer-specific learnings before this is migrated to the public docs site at Summit GA.

Tracks AS-240.

Note

Placement: apollographql/docs archived; apollographql/platform-docs requires SAML grant.

Test plan

  • Renders cleanly
  • All Apollo docs links resolve
  • SA/SE review folds in Wayfair-specific learnings before upstreaming to public docs

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@apollo-solutions-reviewer apollo-solutions-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The structure and conceptual coverage are solid (when-to-use, cache-key composition, invalidation, operational pitfalls). The blocker is the configuration examples: they use a YAML schema that does not match the current Router and will not validate.

Specific accuracy issues:

  1. Top-level key. The doc uses preview_entity_cache. The current feature is configured under response_cache (see the sibling docs/response-caching-guide.md in this same repo, and the Apollo docs at graphos/routing/performance/caching/response-caching). Using a stale/preview key will mislead readers and fail config validation.

  2. Per-tenant / per-locale block is invented. invalidation: { private: true } and cache_key_modifiers with context:/header: entries are not real Router config. Private/user-specific caching is configured with private_id (a context field pointing at e.g. the JWT sub claim), and custom cache keys are set at runtime via the apollo::response_cache::key context entry (Rhai or coprocessor), not declarative YAML modifiers. As written, this section would steer SAs to a config that does not exist; given the section is specifically about preventing cross-user data leaks, the inaccuracy is high-risk.

  3. Debug logging target is unverified. RUST_LOG="apollo_router::services::entity_cache=debug" references a module path that should be confirmed against the actual crate before publishing; recommend pointing at the cache debugger in Sandbox (as the sibling guide does) plus a verified log target.

  4. "See also" links point at apollographql.com/docs/router/configuration/entity-caching/ and a #cachecontrol-directive anchor. The feature now lives under graphos/routing/performance/caching/response-caching; verify all four links resolve before publishing.

The self-flagged Wayfair gap is acknowledged and acceptable for a first draft. The above are correctness issues that must be fixed before this is usable as SA/SE reference material, let alone upstreamed for Summit GA. Recommend reconciling every config snippet against the current response_cache reference and the sibling guide in this repo.

CI: only "Secrets in Source" runs and it passed; no automated link or schema checks exist here, so accuracy is on review.


## Configuration shape

```yaml title="router.yaml — entity caching at the subgraph level"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preview_entity_cache is not the current config key. This feature is configured under response_cache (matching the sibling docs/response-caching-guide.md and the current Apollo docs). Update the key and reconcile the block against the response_cache reference; as written it will not validate.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified at b8bf3d7: the stale preview_entity_cache key is gone. The configuration shape block now uses response_cache with subgraph.all (enabled, ttl, redis) and per-subgraph overrides under subgraph.subgraphs, matching the sibling docs/response-caching-guide.md and the current Apollo response-caching reference. This thread can be resolved.

Comment thread docs/entity-caching-best-practices.md Outdated
products:
invalidation:
private: true
# The cache key is composed of the JWT 'tenant' claim and an

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

invalidation: { private: true } and cache_key_modifiers are not real Router config. Use private_id (context field, e.g. JWT sub) for per-user caching, and set custom cache keys at runtime via the apollo::response_cache::key context entry (Rhai/coprocessor). This section is about preventing cross-user leaks, so the wrong syntax here is high-risk.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified at b8bf3d7: the invented invalidation: { private: true } and cache_key_modifiers syntax is gone. Per-user caching now uses private_id pointing at a context field, and per-tenant/per-locale dimensions are set at runtime via the apollo::response_cache::key context entry from Rhai, which are the correct mechanisms. This thread can be resolved. One residual concern about the exact shape of that runtime cache-key value is raised separately in the review body so it stays tracked.

…hema (AS-240)

Address docs-reviewer feedback. The PR used `preview_entity_cache`, an
out-of-date top-level key; current Router (v2.6+) configures this under
`response_cache` (matching the sibling response-caching-guide and the
current Apollo docs). Also:

- Redis settings live under `subgraph.all.redis`, not at the top level.
- TTL placement: required under `subgraph.all.ttl`.
- Redis timeout bumped from 5ms (unrealistic — most lookups would time
  out) to 200ms.
- Per-user/tenant caching uses the documented `private_id` field
  pointing at a context entry (e.g. JWT `sub`), not the fictitious
  `invalidation: { private: true }`.
- Custom multi-dimensional cache keys use the `apollo::response_cache::key`
  context entry from Rhai/coprocessor, replacing the fictitious
  `cache_key_modifiers` block.
- Repoint legacy /docs/router/... See-also links.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@apollo-solutions-reviewer apollo-solutions-reviewer Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed at b8bf3d7. The two original blockers are fixed. The configuration block now uses the response_cache schema with subgraph.all and per-subgraph overrides, matching the sibling response-caching-guide.md and the current Apollo reference; and the per-tenant section now uses private_id plus the apollo::response_cache::key runtime context entry instead of the invented invalidation/cache_key_modifiers YAML. Both bot threads are addressed.

The rewrite introduces one new accuracy issue in the same cross-user-leak section, plus two smaller items, so this is not yet ready to approve.

  1. Runtime cache-key shape (high-risk, inline). The Rhai snippet assigns a bare string to apollo::response_cache::key. Per the Apollo customization reference, that context entry takes a JSON object keyed by scope (all, subgraphs., or operation name); for example request.context[Router.APOLLO_RESPONSE_CACHE_KEY]["all"] = ... As written the flat string does not match the documented structure and would not segment the cache as intended, which is the exact failure mode this section is meant to prevent.

  2. Rhai accessor shape (verify). The documented examples use supergraph_service with request.headers; this snippet uses subgraph_service with request.subgraph.headers and a nested request.context["apollo::authentication::jwt_claims"]?.tenant access. Confirm these accessors are valid in the current Rhai API before publishing.

  3. private_id value (verify). The docs point private_id at a flat context key that you populate from the JWT sub claim via Rhai, not directly at a nested apollo::authentication::jwt_claims.sub path. Confirm the nested-path form resolves, or switch to the documented intermediate-key pattern.

Non-blocking: the two See also links use /caching/entity; the canonical feature now lives under /caching/response-caching, so verify those resolve. The self-flagged Wayfair gap remains acceptable for a first draft. Test plan checkboxes are still unchecked.

Comment thread docs/entity-caching-best-practices.md Outdated
service.map_request(|request| {
let tenant = request.context["apollo::authentication::jwt_claims"]?.tenant ?? "anon";
let locale = request.subgraph.headers["accept-language"] ?? "*";
request.context["apollo::response_cache::key"] = `tenant=${tenant};lang=${locale}`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apollo::response_cache::key takes a JSON object keyed by scope (all, subgraphs., or operation name), not a flat string. Per the Apollo customization reference the pattern is request.context[Router.APOLLO_RESPONSE_CACHE_KEY]["all"] = . Assigning a bare tenant=...;lang=... string at the top level does not match the documented structure and would not segment cache entries as intended, which is the leak this section is meant to prevent. Set the value under all (or the relevant subgraph/operation field) instead.

- apollo::response_cache::key must be a JSON object keyed by scope (e.g. {"all": "..."}) not a bare string
- private_id: use an intermediate flat context key populated from JWT claims via Rhai, not a nested dotted path which is unreliably resolved

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants