The Surrogate-Key header
Your origin can name surrogate keys of its own in a
Surrogate-Key response header. The same header does both halves of invalidation: on the
response to a read it tags the cached entry, and on the response to a write it purges every
entry carrying those keys. There is no API call to make, no credential to manage, and no second
system that can be down when a write succeeds.
Tag on a read, purge on a write
Section titled “Tag on a read, purge on a write”Which job the header does depends on the request that produced the response:
| The response answers | The header |
|---|---|
A plain HTTP GET or HEAD | Tags the entry. Its keys are added to the ones every entry carries. |
| A GraphQL query | Tags the entry. Its keys are added to the ones GraphPilot derives from your schema. |
A plain HTTP POST, PUT, PATCH or DELETE | Purges every cached entry carrying one of its keys. |
| A GraphQL mutation | Purges every cached entry carrying one of its keys, together with what automatic invalidation purges. |
The two halves fit together: tag a read with a key your origin chooses, then name that same key on the response to the write that changes it.
For a plain HTTP API:
# The response to GET /products/42: tag itHTTP/1.1 200 OKCache-Control: max-age=3600Surrogate-Key: product-42 catalog
# The response to PUT /products/42: clear itHTTP/1.1 200 OKSurrogate-Key: product-42When the response to the PUT comes back through GraphPilot, every cached response tagged
product-42 is purged, and the next GET /products/42 is a MISS.
For a GraphQL API it is the same, with a query and a mutation:
# The query response: tag itHTTP/1.1 200 OKCache-Control: max-age=3600Surrogate-Key: catalog-v3 promo-banner
# The mutation response: clear itHTTP/1.1 200 OKSurrogate-Key: catalog-v3Every cached response tagged catalog-v3 is purged, whatever operation produced it and whichever
fields it selected.
A purge from this header is always hard: the entry is removed, and the next request for it goes to your origin. It is recorded in the service’s purge history like any automatic purge; see verifying a purge.
The same holds for a purge named on the response to a plain HTTP write.
Format
Section titled “Format”-
ASCII spaces, tabs and commas separate keys. A key cannot contain any of them.
-
Keys are folded into printable ASCII, the same way as keys from your schema:
MüllerbecomesMuller, and a non-breaking space inside a key becomes_rather than splitting it. A line that is not valid UTF-8 is read as Latin-1, so both spellings ofMüllerreach the same key. At most 1024 characters per key. See what a key may contain. -
The header name is fixed at
Surrogate-Keyand cannot be changed. An origin that already uses that header for a CDN in front of GraphPilot sends the same keys to both. -
Keys are not namespaced. A key you invent shares a space with the ones GraphPilot adds (
ck:…, and for GraphQLProduct:id:1,q:products,![Product]). Prefix yours if you want to be sure they cannot collide. -
Multiple headers merge. Per HTTP, several headers with the same name form one logical header. These two forms are equivalent, and GraphPilot accepts both:
Surrogate-Key: product-123Surrogate-Key: category-456Surrogate-Key: product-123 category-456
GraphPilot removes your origin’s Surrogate-Key header before the response reaches the client.
To see the keys a response carries, switch on the gp-surrogate-key header; see
debugging.
When this is the right tool
Section titled “When this is the right tool”The keys GraphPilot adds on its own cover what a response contains. This header covers what a write affects.
- Any invalidation on a plain HTTP API. Without a schema, the only keys on an entry are its cache key and the ones your origin names. This header is how a write reaches the reads it changes.
- A create that has to appear in a list. The new object’s key clears nothing, because no cached response contained it. A key naming the list does. See automatic invalidation.
- A write that touches more than it returns. Recalculating an aggregate, expiring a sibling, cascading through a relation your response does not include.
- Keys that only exist at runtime. A customer, a locale, a feature-flag cohort, a content version your CMS mints. Your origin knows them when it answers.
- Keys from somewhere else entirely. A third-party API that returns its own cache tags can have them passed straight through.
What it does not do
Section titled “What it does not do”- It does not reach a response that was never tagged. Invalidation is a lookup by key, so a
key that was never written on an entry matches nothing. If a purge appears to do nothing,
check the entry’s keys first with the
gp-surrogate-keyresponse header. - It only runs on responses that pass through GraphPilot. A write made by a background job, an admin tool or a direct database change never produces a response here. Use the purge API or the CLI for those.
- It does not confirm anything to your origin. The write’s response is the trigger; there is no acknowledgement of how many objects were removed. When request telemetry is stored (on every plan, within its logged-request allowance, and not in lean mode), the purge is recorded in the service’s purge history with the keys it covered.
Related
Section titled “Related”- Surrogate keys: every kind of key and what a key may contain
- Automatic invalidation: what a GraphQL mutation purges on its own
- Verifying a purge: where the purge is recorded