Cache invalidation
A cached response is only useful while it is still true. GraphPilot removes an entry when the data behind it changes, so you can give responses a long freshness lifetime without serving old data after a write.
Invalidation works in two steps, the same for a plain HTTP API and a GraphQL API: tag, then purge. Every cached response is tagged with surrogate keys, labels that name what it contains. A purge names keys, and every entry carrying one of them is removed, whichever request produced it.
Ways to control this
Section titled “Ways to control this”| Through | What it controls |
|---|---|
| Your origin | The Surrogate- header: tags a read, purges on a write. |
| Your GraphQL schema | @surrogateKey on types and fields; a mutation purges what it returned automatically. |
gpilot.toml | Which fields count as keys and how far a mutation reaches ([surrogate_). |
| The API, the CLI and the portal | Purge on demand: gpilot purge, the purge mutations and POST /purge, and the Purges page. |
Every entry carries its own cache key (ck:…), so one response can always be purged on its own.
On top of that:
- Your origin names keys in a
Surrogate-Keyheader on the response to a read (aGETorHEAD, or a GraphQL query). Use it for anything that identifies what the response holds: a product, a customer, a content version. - For GraphQL, GraphPilot derives keys from your schema and the response data: one key per
object it returned (
Product:id:p01), one per type and operation, and any you declare with@surrogateKey. Clients do not have to select the fields a key needs; selection set expansion adds them.
A purge happens in one of three ways.
When a write comes back
Section titled “When a write comes back”| Your origin answers | GraphPilot purges |
|---|---|
A plain HTTP POST, PUT, PATCH or DELETE | Every key in the Surrogate- header on the response. |
| A GraphQL mutation | Every object the mutation returned, automatically, plus every key in the Surrogate- header on its response. |
For a plain HTTP API this is the whole mechanism: tag the GET with a key, name the same key on
the response to the write, and the next GET is a MISS. See the Surrogate-Key
header.
For GraphQL most of it needs no code: a mutation that returns the object it changed clears every cached response holding that object. It leaves some cases uncovered, most importantly a new item in a cached list that was not empty. Read automatic invalidation before you rely on it.
When you send a purge
Section titled “When you send a purge”For a change that never passes through GraphPilot (a database migration, a nightly import, an
admin tool), purge through the GraphPilot API, with gpilot purge, or from the portal. You can
address surrogate keys, one cached response by its cache key, a request from your telemetry, a
GraphQL query by its hash, or the whole cache. See purging through the API and
CLI, and verifying a purge to confirm what
it removed.
When the lifetime runs out
Section titled “When the lifetime runs out”Every entry has a freshness lifetime from your origin’s Cache-Control header or, for GraphQL,
your schema’s @cacheControl policy. When it runs out, the entry stops being served whether or
not anything purged it. That bounds how long an entry can be wrong in the worst case, but it is
not your invalidation strategy.
Example: one product page
Section titled “Example: one product page”The pages in this section use one running example: a product catalog with Product,
Category and Review types and a setPrice mutation, served by a service called shop-api.
Product carries @surrogateKey(of: "id"), @surrogateKey(of: "category.id") and
@surrogateKey(static: "catalogue"), so every product can be purged on its own, with its whole
category, or with the whole catalog.
Tag. A product page asks for one product, its category and its reviews. With the
gp-surrogate-key header switched on, the
response shows every key the entry carries:
curl -sS -D- -o /dev/null "https://$GRAPHPILOT_HOST/graphql" \ -H 'content-type: application/json' \ -d '{"query":"query ProductPage { product(id: \"p01\") { id name price category { id name } reviews { id rating } } }"}'gp-cache: MISSgp-surrogate-key: ck:95f9f0f4…72b0 q:product Category Product Query Review catalogue Category:id:audio Product:category.id:audio Product:id:p01 Review:id:r01 Review:productId:p01Purge on a write. A setPrice(id: "p01", price: 2450) mutation returns the product it
changed, so its response carries Product:id:p01 and GraphPilot purges it on the way back. The
next ProductPage request is a MISS with the new price. The full walk-through, with what
Insights and the Purges page show, is on automatic invalidation.
Purge you send. A nightly price import writes to the database directly, so no response passes through GraphPilot. The import job purges what it changed itself:
gpilot purge --yes --service shop-api Product:id:p01 Product:id:p07The same purge from a webhook, without the CLI, is under purging over REST.
Related
Section titled “Related”- Surrogate keys: every kind of key and what a key may contain
- The Surrogate-Key header: tagging and purging from your origin
- Automatic invalidation: exactly what a GraphQL mutation purges
- Verifying a purge: where purges are recorded