Shielding
Plans Jet (Starter) and above
Without shielding, each edge location holds its own cache: the first request for an entry at every location is a miss that goes to your origin. Shielding puts one edge location, the shield, between every other location and your origin. A miss anywhere asks the shield first, so the shield’s cache is in effect shared by the whole network and your origin sees about one miss per entry per lifetime, however many locations request it.
Shielding is off by default. It is worth switching on when your audience is spread across many regions, or when your origin is expensive to call; it is less useful when most traffic arrives at one or two locations anyway.
Switch it on
Section titled “Switch it on”Name the shield location in the [origin] table of gpilot.toml and deploy:
[origin]shield = "eu-frankfurt"- Pick the location closest to your origin. The shield is the only location that connects to your origin, so its distance to the origin is what every shielded miss pays. The values are in the shield location table; a value that is not in it refuses the deployment, and the refusal suggests the closest values when there are any.
- Your plan decides whether the setting is used. Shielding is included in every paid plan.
On Glider (Free) a configured shield still deploys, with a warning on
origin.shieldthat names the location and says it is inactive on this plan and turns on by itself after an upgrade, with no redeploy needed. The edge checks the plan on every request, so an upgrade switches the shield on and a downgrade to Glider switches it off without a new deployment. While it is off, every miss goes to your origin directly. - The portal shows it. The service’s Config view names the shield’s city and country next
to
origin.shield, and says whether the shield is active on the plan. See schema and config findings.
To switch shielding off, remove shield and deploy again.
Example: an origin in Frankfurt
Section titled “Example: an origin in Frankfurt”shop-api runs in a Frankfurt data center, and a third of its traffic comes from North America
and Asia. Without a shield, each of those edge locations fills its own cache from Frankfurt.
eu-frankfurt is the shield location next to the origin:
[origin]shield = "eu-frankfurt"A value from another vocabulary is refused before anything is deployed. The CDN’s own code for
the same location, frankfurt-de, fails the deployment with:
unknown shield location "frankfurt-de", did you mean "eu-frankfurt"?After the deployment, a miss at an edge location in Chicago asks the shield first. The request’s
Overview tab in Insights shows the Shield set with Frankfurt as the location: the first
request for an entry reads Result MISS there, and the same request from Singapore a moment
later reads HIT, because the shield already holds the entry and your origin is not called. The
Shield section of the Cache hit card sums this up for the time range; its Offload is
the share of requests your origin never saw. Usage rises with it: a miss through the shield counts
at two edge locations (see request units).
How a shielded request flows
Section titled “How a shielded request flows”- A request arrives at the nearest edge location and is looked up in that location’s cache. A hit is answered there, exactly as without shielding.
- On a miss, the edge location sends the request to the shield instead of to your origin. It sends the request as the client sent it, signed so the shield can tell it came from a GraphPilot edge location and which client address it was for.
- The shield looks the entry up in its own cache, under the same cache key and the same partition. On a hit there, it answers and your origin is not contacted.
- On a miss at the shield, the shield calls your origin with the same TLS, request signing, timeouts and retries as any other origin request, and stores the response.
- The response travels back to the edge location, which stores its own copy and answers the client. Later requests at that location are answered from its own cache.
The edge location stores the shield’s response with what is left of its lifetime: the shield’s
age is subtracted from the max-age, so both copies expire together. When nothing is left, the
response is served but not stored at the edge location
(CACHE_SKIPPED_SHIELD_ENTRY_EXHAUSTED). A response the shield answered from a stale copy, or
without consulting its cache, is served but not stored at the edge location either
(CACHE_SKIPPED_SHIELD_RESPONSE_NOT_STORABLE); see stale windows with
shielding.
Misses from many locations meet again at the shield, so request collapsing works across locations too.
Which requests go through the shield
Section titled “Which requests go through the shield”Only a cache miss goes through the shield, and only when the request is one of these:
| Request | Through the shield |
|---|---|
| A GraphQL query | Yes |
| A GraphQL mutation | Yes, see mutations and purges |
| A GraphQL request carrying only a persisted query hash | Yes; the shield resolves the hash |
| A GraphQL document the edge location cannot parse | Yes; the shield refuses it the same way |
A plain HTTP GET or HEAD | Yes |
| A GraphQL subscription, any other plain HTTP method, a WebSocket upgrade | No, straight to your origin |
A request that never consults the cache (a PASS with a CACHE_PASS_* code, for example one matching cache.) | No, straight to your origin |
| A request served in lean mode | No, straight to your origin |
A background refresh of a stale entry at an edge location also goes through the shield.
Mutations and purges
Section titled “Mutations and purges”A mutation goes through the shield, so its automatic purge runs at the shield first and at the edge location after it, on the way back. Purging in that order keeps the edge location from refilling from a shield copy that is about to be purged.
A purge through the API, the CLI or the portal is sent twice on a shielded service, so an edge location purged before the shield cannot refill from the shield’s copy. A purge clears the entry at the shield too, because purges are global.
When the shield cannot be used
Section titled “When the shield cannot be used”The edge location sends the shield exactly one request and does not retry it; retries run between the shield and your origin. What happens when the hop fails depends on the request:
| Failure | A query, GET or HEAD | A mutation | A request whose kind the edge cannot see (only a hash, or a document it cannot parse) |
|---|---|---|---|
| The shield could not be reached before the request was sent (for example DNS, connect or TLS) | Sent to your origin by the edge location (FALLBACK) | 502, nothing sent to your origin (UNAVAILABLE) | Sent to your origin by the edge location (FALLBACK) |
| The send failed in a way that leaves unclear whether the shield received it, or the answer did not come from the shield | Sent to your origin by the edge location (FALLBACK) | 502 (UNAVAILABLE) | 502 (UNAVAILABLE) |
| No usable answer within the time budget, or the connection broke off | 504 (TIMEOUT) | 504 (TIMEOUT) | 504 (TIMEOUT) |
| The shield answered with a body the edge location could not decode | 502 | 502 | 502 |
| The shield answered, including with an error status from your origin | Passed through as it is | Passed through as it is | Passed through as it is |
A mutation never falls back to your origin, because the edge location sending it itself could run
it twice and would purge this location before the shield. A fallback is logged as
SHIELD_UNAVAILABLE_FALLBACK, a refused mutation as SHIELD_UNAVAILABLE_NO_FALLBACK, and a
timeout answers GATEWAY_TIMEOUT; see codes.
The reason on a SHIELD_UNAVAILABLE_FALLBACK or SHIELD_UNAVAILABLE_NO_FALLBACK line names the
step that failed; quote it to support.
While the shield cannot be reached, a service keeps working on queries at the cost of the per-location warm-up, as without shielding.
The time budget. The edge waits for the shield’s whole origin sequence (every attempt your
[origin.retry] policy allows, and the gaps between them)
plus a fixed 5 seconds for the hop, never more than the 120-second
budget. An unset first_byte_timeout_ms counts
as 15000 ms here. A configuration whose total exceeds the 120-second budget is refused by the
deployment build, naming origin.shield. See connection to the
origin.
Headers
Section titled “Headers”- What your clients see does not change. The response headers come from the edge location
that answered the client:
gp-cachesays whether that location had the entry, so a response the shield supplied readsMISSthere. See cache statuses. - The hop headers stay inside GraphPilot. The headers the shield and the edge location exchange never reach your client or your origin, and the same names are removed if your origin sends them (see reserved names).
- Your origin sees the client, not the edge location. With
origin.forward_client_ip,gp-client-ipcarries the address the first edge location saw, never a GraphPilot address. See headers. - Your origin is called from the shield. Keep the whole GraphPilot address set in your origin allowlist; requests that do not go through the shield, and fallbacks, still come from other locations.
Insights and logs
Section titled “Insights and logs”A shielded request is one request in Insights, with the hop recorded on it:
- The request detail panel shows a Shield set on its Overview tab: the shield location with its city and country, how the hop ended (Result) and, when the shield called your origin, that call’s status, latency and retries; the Diagnostics set names the shield server that answered. On the Cache tab, a request the shield answered without storing shows the shield’s own reason, marked Decided at the shield. When the shield called your origin, the GraphQL tab shows the document your origin received as the shield recorded it; when the shield answered from its cache, the tab says nothing was sent to the origin.
- The list can be filtered by Shield result and show it as a column, on every plan. A
request the shield sent to your origin (
MISSorPASS) is marked when GraphPilot changed its document, and the Rewritten by GraphPilot filter finds it. Shielded requests recorded before runtime 1.6.0 stay unmarked. - GraphQL errors your origin answered the shield with are recorded on the request, as on an
unshielded one: the GraphQL errors column and preset, the error groups and the alert rule
GraphQL errors in your responses count them, including errors removed before the client saw
them. A request the shield answered from its cache (
HITorSTALE) records none. Shielded requests recorded by runtime 1.8.0 or earlier carry no GraphQL errors. - Origin outcome reads Called via shield when the shield called your origin, Not called (shield cache) when it answered from its cache, and Shield failed when it gave no usable answer, so a shielded request is never counted as served without your origin when it was not.
- The Cache hit card has a Shield section on a service that had shielded traffic in the
time range: Hit (the share of shielded requests the shield answered from its cache, fresh or
stale), Offload (the share of all requests the shield answered, so your origin never saw
them), Fallback (the share of shielded requests where the shield could not be used:
FALLBACK,TIMEOUTorUNAVAILABLE) and Shielded (how many requests went to the shield). A click on Hit or Fallback filters the list to those requests. A falling Hit with a low Fallback means the shield is reachable but misses; a rising Fallback means the shield is not being used.
In the log fields, shield_pop names the shield location and shield_result how the hop ended
(HIT, MISS, STALE, PASS, FALLBACK, TIMEOUT or UNAVAILABLE). shield_node names the
server within the shield that answered, so it is empty when no answer came from the shield
(FALLBACK, TIMEOUT, UNAVAILABLE). The fields are empty on a request that did not go to the
shield. See log fields.
Request units
Section titled “Request units”Usage is counted in request units, the way the CDN bills: per request at each edge location that ran it, by the size of the body that location produced. The shield is an edge location too, so a request sent through the shield usually uses twice the request units of the same request without a shield:
- A 2 KB response through the shield is 2 request units, a 25 KB one 6 (two locations, three started 10 KiB each).
- A request the shield ran counts at both locations, whatever it answered, and also when the hop timed out.
- A request that fell back before the shield ran it counts at the edge location only.
- A background refresh through the shield is a request of its own, counted at the shield.
- A hit at the edge location, and any request that does not go through the shield, counts at one location as before.
Insights still shows a shielded request as one request, so the request units for a period are higher than the request count Insights shows. The full table is on quotas and lean mode.
Where the shared copy lives
Section titled “Where the shared copy lives”An entry is also stored at the shield location, whichever location requested it. A shield in the EU keeps that shared copy in the EU; the copies at the requesting locations stay where they are. Take the shield location into account in a data protection review. See where data lives.
Related
Section titled “Related”- Edge locations: why the cache is per location, and the shield location table.
- Connection to the origin: timeouts, retries and fallback in detail.
- Configuration reference:
origin.shield. - Codes: the
SHIELD_*codes andshield_resultvalues.