Cache hit/miss testing
Cache hit ratio is the basic health metric for a CDN. A high hit ratio means the edge serves most requests without touching your origin. A low hit ratio means 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 in MaxoPerf.
Before you start
Section titled “Before you start”- Read the CDN testing overview to see all the CDN test types.
- Confirm that your CDN returns
Cache-Control,Age, orX-Cacheresponse headers. Runcurl -I https://your-cdn-url/asset.jsto check before you write a test. - Get a smoke test passing against your CDN URL first.
Understanding cache state headers
Section titled “Understanding cache state headers”Before you write assertions, learn 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 CDN is not caching the response. |
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. Use it to confirm 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 useful 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 shows what the cache is worth. If warm TTFB is 20 ms and cold is 200 ms, the CDN absorbs 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 you precise 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: after the cold priming test finishes, ramp to production VU count. 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 | A nearby edge node serves the responses. |
| p50 TTFB > 100 ms | Most responses come from origin. Check the Age header assertions. They may be failing. |
| Assertion failure rate > 0 % | Some responses bypass the cache. Drill into the Log tab to see which URLs failed which assertion. |
| High p99 relative to p50 | Occasional cache misses cause long-tail latency spikes. TTL may be expiring 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 then shows 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 split the cache into per-client copies with a 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: find out why hit ratio may be lower than expected.
- TTFB and asset delivery: how to measure TTFB and what drives it.