Shared response caching¶
Use a shared Varnish cache to serve repeated GET and HEAD requests without sending each one to the application. Other methods bypass it. Cache contents are divided among Varnish pods by a hash of the cache key.
Enable caching¶
Add this to your Helm values:
Apply the values through your Helm deployment, then add annotations to an Ingress:
This requests a 60-second cache lifetime for 200 responses. Responses with Set-Cookie,
Cache-Control: no-cache/no-store/private, or Vary: * aren't stored.
Use cache-ttl: auto to follow the origin's cache headers instead. See the
cache annotations
for exclusions, object-size limits, and other lifetimes.
Check cache behavior¶
Inspect the response's X-Cache header or the access log's cache field:
| Value | Meaning |
|---|---|
HIT |
Served a fresh cached response. |
MISS |
Fetched the response from the application. |
STALE |
Served an expired response within an allowed staleness window. |
The log's cache_age reports the object's age in seconds. If a response isn't
stored, cache_uncacheable_reason identifies why: excluded content type, excessive
size, Set-Cookie, status, or origin cache restrictions.
Vector exposes cache counters through the existing metrics
port: haptic_cache_status_total, haptic_cache_age_seconds_total,
haptic_cache_uncacheable_total, and haptic_degraded_cache_total.
Use access logs when you need to identify a particular route.
Cache keys and authentication¶
cache-key adds request-specific components to the cache key:
| Key | Effect |
|---|---|
consumer |
Separates responses by authenticated identity. Authentication and consumer-group checks still run on cache hits. |
src |
Separates responses by source IP. It doesn't establish an authenticated identity. |
header:<name>, cookie:<name>, query:<name> |
Adds the named request value. Combine components with commas. |
Only consumer allows caching requests that carry Authorization or Cookie.
With other keys, those requests bypass Varnish. Rate and bandwidth limits still
apply to cache hits; an internal cache-miss fetch doesn't consume a second budget.
HAPTIC also protects response variants from being combined by a proxy in front
of the cluster. Header and cookie keys add Vary values without replacing the
origin's existing values. Query parameters are already part of the URL.
consumer and src keys set Cache-Control: private because a downstream cache
can't reproduce those identities. These headers apply even when a request
bypasses Varnish or falls back to the application.
Handle expiry and cache failures¶
Choose how the route handles an expired object:
| Annotation | Behavior |
|---|---|
cache-stale-while-revalidate |
Serve the stale object immediately while refreshing it in the background. The default window is 10 seconds. |
cache-stale-if-error |
Wait for the origin, then use a stale object only if the refresh fails. |
cache-revalidate |
Retain expired objects for conditional requests that can return 304 Not Modified. |
The windows are independent. For example, cache-stale-while-revalidate: "30"
and cache-stale-if-error: "600" allow fast responses during refresh and a longer
fallback during an origin failure. Origin errors don't replace a good cached object.
HAProxy bypasses unhealthy Varnish pods. A failed or timed-out cache attempt
retries the application if response headers haven't reached the client. Once
response delivery starts, an idle partial response is terminated instead.
Set cache.haproxy.responseTimeoutMs above the application's normal time to first
byte to avoid duplicate fetches on cache misses.
Health checks cover Varnish's connection back to the origin. The default
NetworkPolicy allows requests only from the release's HAProxy pods and outbound
connections to DNS and those HAProxy pods' origin-fetch port. HAPTIC strips
client-supplied X-Haptic-Cache-* headers before routing.
Size and restart the cache¶
Cached objects don't survive a Varnish process restart. Rolling updates replace
one pod at a time; other shards stay warm. More cache.varnish.replicas reduce
the share of keys affected by one pod's restart.
Budget cache.varnish.resources.limits.memory for cache.varnish.malloc, the
shared-log buffer (80 MiB with the default image), compiled configuration, and
process overhead. The working directory is an executable, memory-backed
emptyDir; its memory also counts toward the container limit.
The working directory must remain resident in memory. Use a verified noswap
mount, container swap prohibition, or nodes without swap; tmpfs alone doesn't
prevent paging. See Kubernetes swap behavior.
If Varnish logs mlock() of VSM failed, upstream permits it
only when the environment prevents paging. Otherwise, raise the memory-lock limit in the container
runtime or disable swapping for these containers. The chart
keeps the non-root image and adds no capabilities or host-limit changes.
Collect routine access logs from HAProxy; Varnish's shared log is a circular
diagnostic buffer.