Cache hit/miss testing
Cache hit ratio is the fundamental health metric for a CDN. A high hit ratio means most requests are served from the edge without touching your origin — exactly what you want. A low hit ratio means either your Cache-Control headers are misconfigured, your cache keys are too narrow, or your content changes faster than users can warm the cache. This page shows how to measure hit ratio, assert on cache-state headers, and compare warm vs cold cache latency using MaxoPerf.
Before you start
Section titled “Before you start”- Read the CDN testing overview to understand the full test landscape.
- Confirm that your CDN returns meaningful
Cache-Control,Age, orX-Cacheresponse headers. Run acurl -I https://your-cdn-url/asset.jsto verify before writing a test. - Have a smoke test passing against your CDN URL first.
Understanding cache state headers
Section titled “Understanding cache state headers”Before writing assertions, know what each header means:
| Header | Source | What it tells you |
|---|---|---|
Cache-Control | Origin server | Caching directives — max-age, s-maxage, no-cache, no-store, public, private. |
Age | CDN | Seconds the response has been in the cache. Age: 0 on the first hit; increasing on subsequent hits. A missing or zero Age on repeated requests suggests the response is not being cached. |
X-Cache | CDN (provider-specific) | HIT or MISS. CloudFront uses X-Cache: Hit from cloudfront. Fastly uses X-Varnish-Cache. Akamai uses X-Cache: TCP_HIT. Check your provider’s documentation. |
X-Cache-Hits | Fastly / some CDNs | Number of times this edge has served the cached response — useful for confirming warm state. |
Cf-Cache-Status | Cloudflare | HIT, MISS, EXPIRED, STALE, BYPASS, DYNAMIC — very granular. |
Warm vs cold cache test strategy
Section titled “Warm vs cold cache test strategy”The most informative CDN test separates two phases:
- Cold run — send one request per URL to prime the cache. Expect
MISSon all responses. Record latency; this is your origin baseline. - Warm run — send concurrent load after the cache is primed. Expect
HITon most responses. Record latency; this is your edge performance.
The ratio of warm to cold latency reveals cache value: if warm TTFB is 20 ms and cold is 200 ms, the CDN is absorbing 90 % of the cost.
Writing cache-header assertions in Taurus
Section titled “Writing cache-header assertions in Taurus”The following Taurus YAML runs a warm-cache load test and asserts that:
- The HTTP status code is 200.
- The response contains a
Cache-Controlheader withpublic. - The response
Ageis greater than zero (i.e., served from cache, not origin).
execution: - concurrency: 50 ramp-up: 30s hold-for: 3m scenario: cdn-warm-cache
scenarios: cdn-warm-cache: default-address: https://cdn.example.com requests: - label: homepage-js-bundle url: /static/app.js method: GET assert: - equals: subject: http-code value: '200' - contains: subject: headers value: 'public' # Cache-Control: public, max-age=... - not-contains: subject: headers value: 'no-store' # guard against accidental no-store
- label: hero-image url: /static/hero.webp method: GET assert: - equals: subject: http-code value: '200' - contains: subject: headers value: 'X-Cache: HIT' # adjust to your CDN's header name
- label: api-response-cached url: /v1/catalog/featured method: GET assert: - equals: subject: http-code value: '200'k6 cache-header assertion pattern
Section titled “k6 cache-header assertion pattern”k6 gives precise control over per-header checks and lets you record cache state for custom reporting:
import http from 'k6/http';import { check, sleep } from 'k6';
export const options = { stages: [ { duration: '30s', target: 50 }, { duration: '3m', target: 50 }, { duration: '30s', target: 0 }, ], thresholds: { http_req_duration: ['p(95)<100'], // CDN hits should be fast http_req_failed: ['rate<0.01'], },};
export default function () { const res = http.get('https://cdn.example.com/static/app.js', { tags: { name: 'cdn-asset' }, });
check(res, { 'status 200': (r) => r.status === 200, 'cache hit': (r) => (r.headers['X-Cache'] || '').includes('HIT'), 'cache-control public': (r) => (r.headers['Cache-Control'] || '').includes('public'), 'age is positive': (r) => parseInt(r.headers['Age'] || '0', 10) > 0, });
sleep(0.5);}Measuring warm vs cold cache latency
Section titled “Measuring warm vs cold cache latency”Run two separate MaxoPerf tests to compare:
- Cold test — one VU, one pass through all asset URLs. Note the average TTFB — this is origin RTT.
- Warm test — ramp to production VU count after the cold priming test has finished. Note p50 and p95 TTFB.
How to read cache-hit results in MaxoPerf
Section titled “How to read cache-hit results in MaxoPerf”After your run, open the Overview tab and look at:
| Signal | What it means |
|---|---|
| p50 TTFB < 30 ms | Responses are being served from a nearby edge node. |
| p50 TTFB > 100 ms | Most responses are coming from origin. Check Age header assertions — they may be failing. |
| Assertion failure rate > 0 % | Some responses are bypassing the cache. Drill into the Log tab to see which URLs failed which assertion. |
| High p99 relative to p50 | Occasional cache misses causing long-tail latency spikes. May indicate TTL expiry mid-test. |
Do / don’t
Section titled “Do / don’t”Do:
- Warm the cache before measuring steady-state hit latency.
- Label requests by asset type or URL group — the per-label breakdown in MaxoPerf makes it easy to spot which resource category has the worst hit ratio.
- Use assertion failure rate as a proxy for cache miss rate when
X-Cacheheaders are available.
Don’t:
- Measure cache performance from a single geographic location — edge nodes are regional. A
HITinus-east-1does not mean aHITineu-west-1. See Multi-region edge performance. - Confuse a
200response with a cache hit. The CDN may return200from origin on every miss. You need theX-CacheorAgeheader to distinguish. - Forget to check the
Varyheader on responses — a misconfiguredVary: User-Agentcan fragment the cache into per-client silos with near-zero hit ratio. See Cache key and Vary testing.
Where to go next
Section titled “Where to go next”- Cache invalidation and purge testing — test what happens when you purge cached content.
- Multi-region edge performance — measure per-region cache hit latency from multiple locations.
- Cache key and Vary testing — understand why hit ratio may be lower than expected.
- TTFB and asset delivery — deep dive into TTFB measurement and what drives it.