What must an edge cache key contain for a GraphQL query sent over GET?
answer
- One endpoint, so the path discriminates nothing
- Identity of the work, not its label
- Same map, two encodings, two entries
- Cacheability follows the selection set
- Too coarse leaks, too fine costs
basics
~20 sAn identity for the operation that will run, the variables in a canonical form, and — whenever a selected field depends on who is asking — a dimension of the caller's identity. Miss the third and one viewer receives another's data.
solid answer
~50 sBecause a single endpoint serves the whole schema, the path contributes nothing to the key: every read shares one URL prefix. The identity of the work therefore has to come from the query string — an operation identifier (a document hash or a manifest entry) rather than the operation's name, plus the variables serialized the same way by every client, since two byte-different encodings of the same variable map become two entries. The third component is the one teams forget. In a resource API a per-user thing lives at a per-user path, so the key separates viewers for free. In GraphQL one document can select a public catalogue field and a viewer-scoped field side by side, so two different users produce byte-identical URLs and must receive different responses. Either an identity dimension enters the key, which collapses the hit rate toward one entry per viewer, or you split the operation so the shared part carries nothing viewer-specific.
code
graphql · 10 linesquery ReleaseShelf($releaseId: ID!, $market: String!) {
release(id: $releaseId, market: $market) {
title
releasedOn
tracks { id title durationMs }
}
viewer {
library { isSaved(releaseId: $releaseId) }
}
}go deeper
Know that a GraphQL service has one endpoint, so the URL path alone cannot tell two different reads apart, and that anything a response depends on has to appear somewhere in the request for a cache to key on it.
Be able to name the three components and explain why each is needed: operation identity, canonically serialized variables, and an identity dimension when the selection includes viewer-scoped fields. Explain how two encodings of one variable map fragment the cache.
An interviewer expects the structural insight — cacheability follows the selection set, not the endpoint — plus the concrete choice between adding an identity dimension and splitting the operation, with the hit-rate arithmetic behind that choice.
Own the guardrail that stops this class of leak by construction: which operations are permitted at the edge, who reviews a selection set for viewer-scoped fields, and how you keep an edge key rule and a schema change from drifting apart across two teams.
## The path tells a shared cache nothing For a GET, a shared cache stores under the method plus the full URI. That works in a resource API because the path *is* the description of what was asked for. A GraphQL API has one endpoint, so every read in the schema shares the same path and the entire discriminating power of the key has to live in the query string. Getting that string right is the whole job, and it has three parts. ## Part one: identity of the operation The key needs something that changes exactly when the work changes. Two candidates are available and only one is safe. The **operation identifier** — a hash of the document produced by a runtime handshake, or a name-plus-version pulled from a build-time manifest — is derived from, or bound to, the document text. Two clients shipping the same operation derive the same identifier, so they share one entry; a change to the selection changes it, so a miss coincides with a real change. The **operation name** is not an identity. It is a label the client chose, whose only specified job is to pick one operation out of a document that defines several. Keying on it is a real and recurring mistake: two clients that happen to choose the same name are then served each other's responses. Either way, note what an identifier does *not* do: it makes the response addressable, not cacheable. Whether a shared cache may store it, and for how long, is a separate signal the origin still has to send. ## Part two: the variables, canonically Variables belong in the key — two callers asking for different releases must not share an entry. But variables reach the URL as a JSON object serialized to text, and text has freedoms the object does not. `{"releaseId":"rel_4471","market":"SE"}` and `{"market":"SE","releaseId":"rel_4471"}` are one variable map and two cache entries. So are two encoders that disagree about whether a space is `%20` or `+`, and so are a client that omits a variable to take its schema default and a client that sends the default explicitly. The discipline is client-side: fixed key order, no incidental whitespace, one agreed encoding, and one convention about defaults. Teams that skip it get the short URL and keep the fragmentation. (How caches themselves may be configured to normalize a key is HTTP-caching material; what matters here is that GraphQL gives you an unusually large surface on which to be accidentally inconsistent.) ## Part three: the viewer, when the selection needs it This is the component that produces incidents, and the reason is structural rather than careless. In a resource API, cacheability is mostly a property of the endpoint. A public catalogue resource and a personal library resource are different paths, and a shared cache that keys on the path separates them without anyone thinking about it. In GraphQL, cacheability is a property of the **selection set**. One document can ask for a release's title and tracklist — identical for everyone on earth — and, in the same request, whether *this* viewer has saved it. Two users then send byte-identical URLs and must receive different bodies. A shared cache keyed on the URL alone will serve the first user's answer to the second, and the leak is silent: no error, no anomaly in the origin's logs, because the origin was never asked the second time. So one of two things has to be true. Either the key gains an identity dimension — some stable token for the viewer, or for the coarser group the answer actually varies over, such as an entitlement tier or a market — and the hit rate collapses accordingly: on a catalogue with 41,000 daily listeners, keying the shelf read by viewer took the measured edge hit rate from 71% to 3.8%, which is the honest arithmetic and not a bug. Or the operation is **split**: a public document with the shared fields, cached hard at the edge, and a small second document for the viewer-scoped fields that never leaves the origin. The client issues two requests and merges them, paying one extra round trip to keep the expensive part shareable. The intermediate case is worth naming because it is common: the answer varies over something much coarser than a person. A catalogue read whose only viewer-dependence is licensing by country needs `market` in the key, not a user id — 40-odd entries instead of 41,000. Finding the coarsest dimension the response actually varies over is most of the design work. ## Putting it together A well-formed key for a GraphQL read over GET is: the endpoint path, plus an operation identifier, plus canonically serialized variables, plus the coarsest identity dimension the selection genuinely depends on — and nothing else. Anything extra fragments the cache; anything missing merges entries that should never have met. And the failure modes are asymmetric, which is the sentence worth ending on in an interview. A key that is too fine costs money: low hit rate, more origin traffic, a slower service. A key that is too coarse costs correctness: one viewer served another viewer's data, silently, for as long as the entry lives. When in doubt, over-fragment.
- Why is the endpoint path such a poor contributor to the key here?Because a GraphQL service exposes one endpoint for the whole schema, every read shares it. In a resource API the path encodes what was requested and does most of the discriminating work; here it is a constant. All the identity has to be reconstructed from the query string, which is why an operation identifier and canonical variables matter so much more than they would in front of a resource API.
- If the response varies by viewer, why not just key on the caller's token?It is correct but usually wasteful. A token is per-session, so the same person on two devices, or after one re-login, mints separate entries and the hit rate approaches zero. Key on the coarsest dimension the answer actually varies over — a market, an entitlement tier, a stable account identifier — and confirm by asking which distinct responses genuinely exist for a given operation and variable set.
- What does splitting a document into public and viewer halves cost the client?An extra round trip and a merge in the client, plus the possibility of the two halves being momentarily inconsistent. In exchange the expensive, shared half becomes cacheable for everyone rather than for one person, so the trade is usually worth it on high-volume reads and rarely worth it on a rarely-called one.
saying these in an interview costs you the question
- Keys on the operation name as an identity
- Assumes one endpoint means one cache entry
- Leaves variables out of the key entirely
- Thinks per-user data is safe if URLs match
- Treats cacheability as a property of the endpoint
- Keys on a session token instead of a stable dimension