Skip to content

Debugging surrogate keys

Invalidation only reaches what the keys name, so “which keys did this response get” is the question behind most invalidation problems. You can read the answer off any response.

With response.surrogate_key_header = true (it is off by default), every response that carries keys lists them, space separated. The header grows with the response, one key per object with an entity key, so switch it on where you want to inspect keys, for example in a staging service:

Terminal window
# GRAPHPILOT_HOST is your service hostname, <subdomain>.<label>.graphpilot.cloud,
# or a custom domain you attached. The portal lists both.
curl -sS -D- -o/dev/null "https://$GRAPHPILOT_HOST/products/42" | grep -i '^gp-surrogate-key'
gp-surrogate-key: ck:GET:/products/42 product-42 catalog

For a GraphQL request, the keys GraphPilot derived from the response are listed too:

Terminal window
curl -sS -D- -o/dev/null "https://$GRAPHPILOT_HOST/graphql" \
-H 'content-type: application/json' \
-d '{"query":"{ products { id name } }"}' | grep -i '^gp-surrogate-key'
gp-surrogate-key: ck:9f86d0 ck:9f86d0:4e0740 q:products Product Product:id:1 Product:id:2

This is the set the entry was tagged with, which is exactly the set a purge can name to remove it. The header stops at 8,192 bytes by default (response.surrogate_key_header_max_bytes). If a gp-surrogate-key-truncated header is also present, the list is incomplete and that header says how many keys were left out. The request detail in the portal has the full set. See size limit. Before raising the limit, check what every client in the path accepts, including proxies and load balancers in front of your clients: a header they refuse fails the whole request, not just the header.

The prefix tells you what kind of key it is, and the kind decides what invalidation can do with it: ck: is the cache key, q: and m: name an operation, ![…] an empty list, and Type:field:value one object. The full table, with what each kind lets a purge do, is on surrogate keys.

Your origin’s keys are there. A key your origin sent in a Surrogate-Key header appears in its folded form: a character outside printable ASCII is folded (Müller as Muller), so look for that spelling. See the header’s format.

The deployment accepted your directives. A @surrogateKey the deployment build refused contributes no key, and the deployment still succeeds. Read its diagnostics after every schema change.

Your entity keys are there at all. If a response holding products carries only Product and q:products and no Product:id:…, GraphPilot found no key field. Either the type has no inferable key or the field is not annotated. Fix it in the schema, not in the query: you do not need to select the key fields yourself, selection set expansion injects them and strips them again.

They name the type you expect. For an interface or union, the key uses the concrete type that was actually returned. A key reading Product:id:1 where you expected Item:id:1 means the invalidation you write against Item will miss it.

They are stable across requests. The same object in two different queries must produce the same key, or a purge that clears one will not clear the other. If they differ, the key fields being read differ, which usually means one query reaches the object through a different parent field.

No key is ever silently dropped. If a response’s keys do not fit on the cache entry, the response is not cached at all, reported as CACHE_SKIPPED_SURROGATE_KEYS_TOO_LARGE, so a purge can never miss an entry because a key went missing from it. A response that generates that many keys is usually a very large list; paginating it fixes both problems at once. See how many keys fit on one response.

When a request is stored in Insights (part of every plan, within its logged-request allowance), the same keys are recorded with it. The request detail in the portal shows them on every plan, and from Airliner (Business) you can filter the request list by a key. The list has no surrogate key column. From the CLI, gpilot requests --wide shows them (from Airliner (Business)), and --surrogate-key finds the requests that carried one.

The service’s Purges page lists every purge with the keys it addressed. Together, the two answer “what did that purge actually remove” after the fact, without reproducing the request. See verifying a purge.