188 lines
11 KiB
Markdown
188 lines
11 KiB
Markdown
# Cache diagnostics and behavior in 0.22.3
|
|
|
|
This describes implemented source, not a verified Rust runtime. See
|
|
[TEST_REPORT.md](TEST_REPORT.md).
|
|
|
|
## 0.19.1 cache identity safety
|
|
|
|
The frontend scheme is part of the primary cache identity. `http://host/path` and
|
|
`https://host/path` therefore cannot share an object, preventing responses that depend
|
|
on `X-Forwarded-Proto` or equivalent origin logic from crossing schemes.
|
|
|
|
## 0.11.5 cache changes
|
|
|
|
The cache follows the Varnish built-in model for the behaviors that most directly
|
|
affect hit ratio, with the 0.19.1 scheme-isolation difference noted above:
|
|
|
|
- GET and HEAD use the cache; other ordinary methods pass without clearing unrelated objects.
|
|
- The logical primary identity is frontend scheme + normalized Host authority + exact URL inside the current vhost/config scope.
|
|
- Selected backend-pool member and unrelated request headers do not fragment the primary object.
|
|
- Response `Vary` chooses representation variants under that object.
|
|
- GET and HEAD share the same stored representation; a cacheable HEAD miss can fill it from a backend GET.
|
|
- `X-Cache-Resident-Age` survives 304 revalidation and hot/cold tier movement.
|
|
|
|
RustProxy still deliberately bypasses Cookie and Authorization requests by default,
|
|
matching Varnish built-in safety behavior. The configured static-extension optimization
|
|
can ignore Cookie only for selected static paths, subject to response privacy guards.
|
|
|
|
## Why objects used to disappear after POST/PUT/etc.
|
|
|
|
Before 0.11.5 a successful non-safe request could call a global cache purge. A periodic
|
|
POST, API heartbeat or form submission could therefore erase every cached static object
|
|
even though the request was unrelated to those URLs. In 0.11.5 POST/PUT/PATCH/DELETE,
|
|
OPTIONS and TRACE pass through and do not implicitly purge the shared cache.
|
|
|
|
Only configuration changes that alter cache/origin compatibility invalidate incompatible cache
|
|
state; unrelated control-plane saves keep reusable hot/cold objects. Explicit administrator purge
|
|
still invalidates the selected cache state. Use the purge action when the application really needs
|
|
explicit invalidation.
|
|
|
|
## `Age` versus `X-Cache-Resident-Age`
|
|
|
|
These are different clocks:
|
|
|
|
- `Age` is HTTP freshness age. It can already be thousands of seconds when RustProxy
|
|
receives an object because the origin/CDN `Date`/`Age` contributes to it.
|
|
- `X-Cache-Resident-Age` is RustProxy-specific and counts whole seconds that the stored
|
|
representation has remained resident in this cache.
|
|
|
|
In 0.11.5 resident age is preserved across a successful backend 304 and across
|
|
hot-to-cold/cold-to-hot movement. An immediate first HIT can legitimately show `0`
|
|
because less than one full second has elapsed; a long-lived object should then keep
|
|
increasing rather than repeatedly resetting to zero.
|
|
|
|
## Practical HIT test
|
|
|
|
Enable cache plus `X-Cache`, `X-Cache-Hits` and `X-Cache-Resident-Age`, then make
|
|
repeated identical requests that read the complete body:
|
|
|
|
```bash
|
|
for n in 1 2 3; do
|
|
curl -q -sS -D - -o /dev/null \
|
|
-H 'Host: app.localhost' \
|
|
http://127.0.0.2:8888/cache
|
|
sleep 2
|
|
done
|
|
```
|
|
|
|
For an eligible response, expect MISS then HIT/HIT. `X-Cache-Resident-Age` should grow
|
|
between later HITs. A browser is a poorer diagnostic because its own cache may satisfy
|
|
a navigation without sending any request to RustProxy.
|
|
|
|
HEAD is now eligible too. It shares the GET representation, but GET is still the best
|
|
basic test because it proves that the body was completely consumed and published.
|
|
|
|
## Vary test
|
|
|
|
A response with:
|
|
|
|
```text
|
|
Vary: Accept-Language
|
|
```
|
|
|
|
stores language variants under one primary scheme + Host + URL object identity. Two English
|
|
requests can HIT each other; a Polish request must not receive the English body. If the
|
|
origin later changes between no `Vary` and `Vary: Accept-Language`, incompatible older
|
|
variants are removed rather than used as a fallback.
|
|
|
|
`Vary: *` is not cacheable.
|
|
|
|
## Cookie/static behavior
|
|
|
|
`request_cookie` is a BYPASS by default. With `static_ignore_cookies=true`, configured
|
|
static extensions such as `.css`, `.js`, fonts and images ignore the request Cookie for
|
|
cache eligibility/keying. This is intentionally limited to static paths. A response
|
|
with `Set-Cookie`, `Cache-Control: private/no-store`, or `Vary: Cookie` still cannot be
|
|
shared through that optimization.
|
|
|
|
This setting is useful for raising the asset hit ratio for logged-in users without
|
|
turning dynamic personalized pages into shared objects.
|
|
|
|
## Conditional requests and reloads
|
|
|
|
Fresh cached objects can satisfy `If-None-Match`/`If-Modified-Since` locally. Request
|
|
`Cache-Control: no-cache`, `max-age`, `min-fresh` and legacy `Pragma: no-cache` select
|
|
validation instead of creating a separate cache key. If a stored validator exists and
|
|
the backend returns 304, RustProxy refreshes metadata and reuses the body.
|
|
|
|
Hard browser reloads commonly add revalidation directives, so they may produce
|
|
`cache_revalidated` instead of a plain `cache_hit`. This is different from a cache miss.
|
|
|
|
## Reason tokens
|
|
|
|
When cache diagnostics are enabled, these fixed reason tokens explain decisions:
|
|
|
|
| Token | Meaning |
|
|
|---|---|
|
|
| `cache_disabled` | Effective cache is disabled. |
|
|
| `route_disabled` | Selected route explicitly disables cache. |
|
|
| `request_method` | Method is not GET/HEAD. It passes and does not purge the cache. |
|
|
| `request_cookie`, `request_authorization` | Private request state bypasses shared cache. Static Cookie exception may apply. |
|
|
| `request_no_store` | Client forbids use/storage of shared cache. |
|
|
| `request_no_cache`, `request_max_age`, `request_min_fresh`, `request_pragma_no_cache` | Client requests validation. |
|
|
| `request_body`, `request_range`, `request_precondition` | Request is outside reusable shared-object semantics. |
|
|
| `query_excluded`, `path_excluded` | Configured exclusion matched. |
|
|
| `tinyauth`, `sticky_cookie` | Authentication/affinity policy deliberately bypasses shared cache. |
|
|
| `response_set_cookie`, `response_cache_control` | Origin response is not safely shareable. |
|
|
| `response_vary`, `response_ttl_size_or_metadata` | Vary/freshness/size/metadata prevents storage. |
|
|
| `response_headers_policy` | Transformed client-response policy is not cacheable. |
|
|
| `cache_miss`, `cache_hit` | No reusable variant was found, or a variant was reused. |
|
|
| `cache_conditional_hit` | Fresh cache metadata satisfied the client validator locally. |
|
|
| `cache_revalidated` | Backend 304 validated the stored body and it was reused. |
|
|
| `cache_revalidated_uncached` | Validation succeeded but refreshed policy no longer permits storage. |
|
|
|
|
A MISS only means lookup found no reusable object. The later origin response can still
|
|
fail storage because of privacy headers, size, incomplete streaming, trailers or a
|
|
changed policy.
|
|
|
|
|
|
## Memory accounting in 0.16.0
|
|
|
|
The process **RSS total** is an operating-system measurement and is intentionally shown separately from RustProxy's own cache accounting. Do not add the rows together: they overlap. The process block reports:
|
|
|
|
- `RSS total` (`VmRSS`) - all resident pages currently attributed to the process;
|
|
- `RSS anonymous` (`RssAnon`) - anonymous resident pages, which typically include heap/stacks and other private allocations;
|
|
- `RSS file-backed / shared` (`RssFile` / `RssShmem`) - resident mapped file/shared-memory pages;
|
|
- `data address space / swap` (`VmData` / `VmSwap`) - address-space and swapped-page context, not extra resident bytes.
|
|
|
|
The cache block separately reports memory RustProxy can attribute to the cache subsystem:
|
|
|
|
- `hot logical` - visible cached body bytes (`body.len()`);
|
|
- `hot allocated` - the hard-budget charge for retained backing allocations plus entry metadata (`resident_bytes` remains a compatibility alias);
|
|
- `transient` - bounded cache allocations temporarily outside the hot store;
|
|
- `fill` - active response buffers being assembled before publication;
|
|
- `cold queue` - complete bodies waiting for cold-tier persistence;
|
|
- `cold I/O` - bodies currently being written to or read from the cold tier.
|
|
|
|
This explains why, for example, a process RSS around 630 MiB can coexist with only about 100 MiB of reported hot cache: the old `Pamięć hot` card was never a whole-process memory counter. TLS/QUIC/Hyper/Tokio allocations, thread stacks, configuration/routing state, active request bodies and allocator-retained pages are also part of RSS but are not falsely assigned to a RustProxy subsystem when the source cannot measure them directly.
|
|
|
|
The existing Linux/glibc allocator-release path remains in place: large cache demotion/purge paths call `malloc_trim(0)` through `release_allocator_memory()`. Since 0.24.5, HOT allocation, active fills and bodies moving to/from COLD are explicitly budgeted. COLD queue admission is byte-bounded as well as count-bounded. RSS is still a process-wide view containing the same cache memory plus TLS/QUIC/Hyper/Tokio, stacks and allocator retention, so cache gauges must not be added to RSS. Long-running acceptance should verify that HOT stays within `hot_limit_bytes`, transient stays within `transient_limit_bytes`, demotion drains transient bytes, and process RSS reaches a practical plateau under representative traffic.
|
|
|
|
## Hot and cold tiers
|
|
|
|
HOT RAM is bounded by allocated/charged `max_bytes` and `max_entries`; `hot_logical_bytes` separately exposes payload length. Fill and COLD handoff/read memory are bounded by `transient_max_bytes`, and queued demotions also obey `cold_queue_max_bytes`. Optional cold storage demotes LRU
|
|
or sufficiently idle hot objects asynchronously. Cold hits verify the body digest and
|
|
promote the object back to RAM. Version 0.11.5 uses cold metadata format 2 to persist
|
|
Vary identity and resident age; old-format metadata is not reused.
|
|
|
|
Cold is not required for normal hot-cache stability. Disabling cold must not cause hot
|
|
objects to disappear on a timer. With cold disabled, hot objects leave RAM only because
|
|
of expiry, hot capacity/LRU pressure, explicit purge, configuration generation changes,
|
|
or replacement of the same logical representation.
|
|
|
|
## Cache panel and API
|
|
|
|
The Cache panel shows hot/cold object counts, RAM/disk usage, HIT/MISS/BYPASS, hit ratio,
|
|
fill pressure, evictions/expiry, per-domain decisions and stored objects. Live polling
|
|
refreshes the view; it does not itself purge cache objects.
|
|
|
|
`GET /api/cache?offset=0&limit=50` returns aggregate and object data. Add `vhost=ID` to
|
|
filter. `POST /api/cache/purge?vhost=ID` invalidates one vhost; omitting the query purges
|
|
all cache objects. Mutations remain authenticated, CSRF-protected and audited.
|
|
|
|
Hit ratio is `HIT / (HIT + MISS)`. BYPASS requests are excluded, so a large number of
|
|
cookie/authenticated requests can reduce backend offload without mathematically lowering
|
|
that displayed ratio. For the highest safe reusable-object ratio, give static assets
|
|
long explicit public TTLs, keep their URLs stable/immutable, leave the static-cookie
|
|
optimization enabled when appropriate, and avoid unnecessary `Vary` fields.
|