Measuring Purge Latency Across CDN PoPs

Problem Statement

An editor publishes a correction, the purge API returns 200, and a colleague in another country still sees the old text two minutes later. Nothing failed. The purge was accepted immediately and applied to each location asynchronously, and nobody had measured how long that takes.

Prerequisite Concepts

Step-by-Step Resolution

Step 1 — Publish a distinguishable marker

Measurement needs an unambiguous way to tell the old response from the new one. A build or revision identifier in a response header is the cleanest, because it does not depend on parsing the body:

HTTP/2 200
X-Content-Revision: 2026-07-31T14:22:07Z
Surrogate-Key: article-8841
Cache-Control: public, s-maxage=86400

Step 2 — Record the purge timestamp

The clock starts when the API accepts the purge, not when the content was edited:

PURGE_AT=$(date -u +%s)
curl -sX POST "https://api.fastly.com/service/$SERVICE_ID/purge/article-8841" \
  -H "Fastly-Key: $FASTLY_API_TOKEN" >/dev/null
echo "purge accepted at $PURGE_AT"
The three intervals between an edit and a correct pageOnly the third interval is purge propagation; the first two belong to the publishing pipeline and are frequently mistaken for CDN latency.Edit to webhookpublishing pipeline0sWebhook to purge callreceiver processing2sPurge to last PoPpropagation3sRepopulationfirst request refetches12s14s
Where the delay actually accumulates

Step 3 — Poll from several locations

A single vantage point measures one location. What matters is the slowest one, which requires distributed polling — either from a synthetic monitoring provider or from hosts in different regions:

poll() {
  local host=$1
  while true; do
    rev=$(curl -sI --resolve example.com:443:"$host" https://example.com/articles/8841 \
          | awk -F': ' '/[Xx]-[Cc]ontent-[Rr]evision/{print $2}' | tr -d '\r')
    if [ "$rev" = "$EXPECTED_REV" ]; then
      echo "$host caught up after $(( $(date -u +%s) - PURGE_AT ))s"
      return
    fi
    sleep 1
  done
}

for pop in 151.101.1.10 199.232.13.10 146.75.29.10; do poll "$pop" & done; wait

Resolving to specific edge addresses is what makes this a per-location measurement rather than a measurement of whichever node happens to be nearest.

Step 4 — Report the distribution

An average hides exactly the case that generates support tickets:

Time to serve the new content, by locationThe median location catches up quickly while the slowest lags several times longer, and it is the slowest that determines what an editor should be told.Median PoP3s90th percentile8sSlowest PoP21sShield tier2smeasured across 40 purges of the same tag
Seconds from purge acceptance to new content, by PoP

Step 5 — Feed the number into the workflow

The measured tail is the number editorial tooling should use. Three places benefit from it: the confirmation message shown after publishing, the delay before an automated screenshot or link check runs, and any incident runbook that says “purge and verify”.

What the measured tail should changeA propagation figure is only useful if it reaches the systems and people who act on it, rather than living in a one-off measurement.Uses the tail figure forConsequence if wrongPublish confirmationhow long to say "updating"editors re-purge unnecessarilyAutomated verificationwhen to check the new contentfalse failures on a slow PoPIncident runbookhow long before escalatingpremature escalation during a fix
Three places the number belongs

Step 6 — Separate propagation from the pipeline around it

The figure editors actually care about is the interval between pressing publish and the page being correct, and purge propagation is only one component of it. Measuring the whole chain end to end usually shows that propagation is not the largest term.

Instrument three timestamps: when the content management system recorded the edit, when the purge receiver called the API, and when the last location served the new content. The gap between the first two is your own pipeline — webhook delivery, queue depth, signature verification, tag lookup — and it is frequently longer than the CDN’s contribution, particularly when purges are batched or retried.

That distinction matters when someone asks for propagation to be faster. Propagation is a vendor property you cannot tune; queue depth and batching in your own receiver are entirely within your control. Presenting the three intervals side by side turns a vague complaint about the CDN into a specific, fixable number, and it usually points somewhere other than where the conversation started.

Expected Output / Verification

A healthy measurement produces a tight distribution with a tail in the low tens of seconds, and — importantly — no location that never catches up. A location still serving old content minutes later is not slow propagation; it is a purge that did not reach it, which is a different problem with a different fix.

151.101.1.10   caught up after 3s
199.232.13.10  caught up after 7s
146.75.29.10   caught up after 21s
median 7s, max 21s over 40 runs

Re-run the measurement after any change to the tagging scheme, because a purge that expands to a much larger URL set propagates more slowly than one that expands to a handful.

Edge Cases

  • A shield lagging behind the edge. If the shield repopulates after the edge, an edge miss can be answered from a stale shield copy and the old content reappears. Measure both tiers, not only the edge.
  • A location that never catches up. Almost always a purge that did not match — a tag that no response actually carried, or a truncated tag header. Verify the tag appears in the stored response before blaming propagation.
  • Browser caches outside the measurement. A purge reaches shared caches only. A visitor holding a browser copy under a long max-age sees the old content regardless of how fast the edge propagates.
  • Soft purge masking the interval. With a soft purge the edge keeps serving the old copy deliberately, so the measurement records the refresh completing rather than the eviction. Both are useful, but they answer different questions.
  • Measuring during low traffic. A location with no requests does not repopulate until someone asks. Polling itself generates the request, which makes the measured figure optimistic compared with a quiet region.
  • Rate limits during a bulk purge. A large batch can be throttled, stretching propagation far beyond the single-purge figure. Measure the batch case separately if bulk publishing is a real workflow.

Frequently Asked Questions

Does a 200 from the purge API mean the content is gone?

No. It means the request was accepted and queued. Propagation to every location happens asynchronously, and the interval between the two is what this measurement exists to establish.

What is a typical propagation time?

Vendors commonly quote a few seconds for tag purges and it is often accurate at the median, but the tail matters more: a distant or lightly-trafficked location can lag noticeably. Measure rather than quoting the vendor figure.

Why measure the slowest location rather than the average?

Because a user in that location sees stale content for the full interval. An average that hides a slow region gives editorial teams a false expectation and produces support tickets nobody can reproduce.

Can propagation be made faster?

Not directly, but its consequences can be reduced. A soft purge keeps the edge answering while it refreshes, and a short lifetime on volatile content limits how wrong a lagging location can be.


Back to Tag-Based Cache Invalidation Patterns