Cache key and Vary testing
The cache key is the string a CDN uses to look up a cached response. Two requests with the same cache key get the same cached response; requests with different keys are cached independently. Getting cache-key design right is critical: too narrow a key fragments the cache and destroys hit ratio; too broad a key means different users receive each other’s personalised or content-negotiated responses — a correctness and security problem.
What makes up a cache key
Section titled “What makes up a cache key”By default, most CDNs build the cache key from:
- Scheme (
https://) - Host (
cdn.example.com) - Path (
/static/app.js) - Query string (optionally, and often configurable)
Extensions to the key depend on CDN configuration:
Varyheader — instructs the CDN to add specific request headers to the key.- Custom cache key rules — CloudFront, Fastly, and Cloudflare all allow adding cookies, custom headers, or stripping query parameters from the key.
- Normalisation — some CDNs normalise query-parameter order; others do not, resulting in
/api?a=1&b=2and/api?b=2&a=1being cached separately.
Query-string cache-key testing
Section titled “Query-string cache-key testing”Query strings are the most common source of unintended cache fragmentation. An analytics or session parameter (e.g. ?utm_source=email&_ga=2.12345) added by marketing tools causes every URL variant to be cached separately, effectively defeating the CDN for that resource.
Test for query-string fragmentation
Section titled “Test for query-string fragmentation”Send the same URL with different query strings and check whether all responses are served from cache:
scenarios: query-string-test: requests: # Clean URL — establishes cache entry - label: clean-url url: /static/app.js method: GET
# UTM parameter — should hit same cached entry if CDN ignores UTM params - label: utmed-url url: /static/app.js?utm_source=email&utm_campaign=launch method: GET assert: - contains: subject: headers value: 'X-Cache: HIT' # should be a cache hit if UTM is stripped
# Session parameter — should NOT vary the cache if caching is anonymous - label: session-url url: /static/app.js?session_id=abc123 method: GET assert: - contains: subject: headers value: 'X-Cache: HIT'If the UTM-parameterised URL returns a MISS but the clean URL returns a HIT, your CDN is including query parameters in the cache key. Configure the CDN to ignore tracking parameters for static assets.
Test for required query-string differentiation
Section titled “Test for required query-string differentiation”For API responses that use query parameters to vary content, the opposite is true: each query variation must be cached separately:
scenarios: api-cache-key-test: requests: # Different page numbers must return different cached responses - label: page-1 url: /v1/catalog?page=1&limit=20 method: GET assert: - equals: subject: http-code value: '200'
- label: page-2 url: /v1/catalog?page=2&limit=20 method: GET assert: - equals: subject: http-code value: '200'After warming both URLs, verify that each returns independent cached responses — and that page=1 does not receive the same body as page=2.
Vary header testing
Section titled “Vary header testing”The Vary response header tells the CDN which request headers to add to the cache key. A response with Vary: Accept-Encoding means compressed and uncompressed versions are cached separately. A response with Vary: User-Agent means every browser variant gets its own cached copy — an effective cache-killer.
Common Vary misconfigurations
Section titled “Common Vary misconfigurations”| Vary value | Impact |
|---|---|
Vary: Accept-Encoding | Correct for text assets. Compresses content is cached separately from uncompressed. |
Vary: Accept-Language | May be appropriate for internationalised responses, but fragments cache heavily. |
Vary: Cookie | Catastrophic for any public-facing resource — every user’s cookies produce a unique cache entry. |
Vary: User-Agent | Catastrophic — thousands of User-Agent strings effectively eliminate caching. |
Vary: * | Disables caching entirely for that response. Usually a configuration error. |
Detecting Vary problems in MaxoPerf
Section titled “Detecting Vary problems in MaxoPerf”Write a test that sends requests with different Accept-Language or User-Agent values and assert that the same cacheable asset is served as a hit regardless of those header values (if the content is the same across languages or clients):
scenarios: vary-test: requests: - label: default-agent url: /static/app.js method: GET headers: User-Agent: 'Mozilla/5.0 (compatible; load-test)' assert: - contains: subject: headers value: 'X-Cache: HIT'
- label: different-agent url: /static/app.js method: GET headers: User-Agent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)' assert: - contains: subject: headers value: 'X-Cache: HIT'If different-agent returns a MISS when default-agent returned a HIT, the CDN has Vary: User-Agent active on that response — a critical misconfiguration for a static asset.
Cache poisoning awareness
Section titled “Cache poisoning awareness”Cache poisoning is a security vulnerability where an attacker causes malicious content to be stored in the CDN cache and served to other users. While MaxoPerf is a load testing tool (not a security scanner), CDN load tests should be designed with awareness of cache poisoning vectors to avoid accidentally triggering them.
Common cache poisoning patterns to be aware of
Section titled “Common cache poisoning patterns to be aware of”- Unkeyed request headers — if a CDN includes an
X-Custom-Headerin cached responses but not in the cache key, an attacker who can control that header can poison the cache. When testing, do not send unusual headers to production CDNs unless explicitly testing for this. - URL normalisation differences — some CDNs normalise paths (
//static/app.js→/static/app.js) before caching but serve responses that reflect the original path. A request for//script.jscould be served a cached response for/script.js— or could create a new cache entry accessible at an unexpected URL. - Host header injection —
Hostheader manipulation can cause cross-site content to be cached. MaxoPerf load tests should always send the correctHostheader for the CDN endpoint.
Testing cache-key normalisation
Section titled “Testing cache-key normalisation”Some CDNs normalise query-parameter order before building the cache key; others do not. If your CDN does not normalise, /api?a=1&b=2 and /api?b=2&a=1 will be cached independently, halving hit ratio for that endpoint.
Test for this:
scenarios: normalisation-test: requests: # Forward order - label: params-ab url: /v1/catalog?category=shoes&limit=20 method: GET
# Reversed order — should hit the same cache entry if normalised - label: params-ba url: /v1/catalog?limit=20&category=shoes method: GET assert: - contains: subject: headers value: 'X-Cache: HIT'Do / don’t
Section titled “Do / don’t”Do:
- Audit
Varyheaders on every cached response type.Vary: Accept-Encodingis correct;Vary: User-AgentorVary: Cookieon public assets is almost always wrong. - Test with both clean URLs and query-parameterised URLs to confirm the CDN is stripping (or including) query parameters as intended.
- Document your CDN’s cache-key rules explicitly — undocumented variations are the root cause of most cache-fragmentation surprises.
Don’t:
- Use
Vary: *on any response that should be cached — it prevents caching entirely. - Assume CDN cache-key defaults are correct for your use case. Every CDN has different default query-string handling.
- Ignore
Varyheader misconfigurations in load test results — a suspiciously low hit rate with high TTFB on a widely-shared asset is often aVaryproblem.
Where to go next
Section titled “Where to go next”- Cache hit/miss testing — measure the overall impact of cache-key decisions on hit ratio.
- Cache invalidation and purge testing — cache-key design affects the scope and blast radius of purges.
- CDN testing do and don’t — comprehensive rules including cache poisoning and
Varyhygiene. - Origin shield and offload testing — cache-key fragmentation directly reduces origin offload percentage.