Skip to content

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:

cache:
  varnish:
    enabled: true

Apply the values through your Helm deployment, then add annotations to an Ingress:

metadata:
  annotations:
    haproxy-haptic.org/cache-enable: "true"
    haproxy-haptic.org/cache-ttl: "60"

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.

Found a problem on this page? Report it or edit the page with the pencil icon above the title.