v0.34.0_fina;
This commit is contained in:
1 parent
1ca019d849
commit
c207e8a03f
69 files changed
+1496
-13838
No files matched your search
+45
-361
@@ -1,381 +1,65 @@
|
||||
# Shared hot/cold cache
|
||||
# HTTP cache — HOT, COLD and invalidation
|
||||
|
||||
See [CACHE_DIAGNOSTICS.md](CACHE_DIAGNOSTICS.md) for practical HIT/MISS/BYPASS diagnosis.
|
||||
RustProxy provides an optional shared HTTP response cache with a bounded in-memory **HOT** tier and optional persistent-disk **COLD** tier. Both the global cache and a Vhost's effective policy must permit caching. **COLD storage is disabled by default**, and newly created Vhosts do not automatically cache dynamic content.
|
||||
|
||||
## Scope
|
||||
## Enable caching safely
|
||||
|
||||
RustProxy implements a bounded reverse-proxy cache with a hot RAM tier and an optional
|
||||
persistent cold disk tier. Version 0.11.5 aligned request-method handling and most logical
|
||||
hash behavior with Varnish built-in semantics. Since 0.19.1 the logical primary identity
|
||||
includes the frontend scheme as well as Host + exact request URL, so `http://host/path`
|
||||
and `https://host/path` cannot reuse the same stored response. RustProxy still isolates
|
||||
different vhost/config scopes internally and uses response `Vary` to select representation
|
||||
variants.
|
||||
1. Open **Settings → Cache**, enable the global cache and choose memory limits. Enable COLD only if you want durable disk storage and have enough private disk capacity.
|
||||
2. On the appropriate Vhost, enable cache for public, repeatable responses. Restrict or disable it for login, personalized pages, uploads, APIs and responses carrying secrets.
|
||||
3. Ensure the origin supplies a valid public freshness policy, or configure a safe default TTL where supported. `Cache-Control: private` and `no-store` must prevent shared storage.
|
||||
4. Optionally enable HIT-only diagnostics. Send repeated identical requests using `curl`, then inspect **Cache → Domains / Stored objects** for `MISS → HIT`.
|
||||
5. Check TTL, revalidation and invalidation after updating the origin's content or configuration.
|
||||
|
||||
This is not a VCL implementation. Generic Varnish grace, ESI, public unauthenticated
|
||||
unrestricted public BAN/PURGE and distributed cache state are not implemented. RustProxy 0.22.1 adds bounded
|
||||
foreground request collapsing, authenticated selective PURGE/BAN and optional proactive
|
||||
refresh while keeping directive-controlled stale delivery through RFC 5861 and request
|
||||
`max-stale`; it is not an unconditional availability/grace layer. RustProxy also keeps its
|
||||
validator-based handling of response `no-cache` instead of reproducing Varnish hit-for-miss
|
||||
behavior exactly.
|
||||
## Stored object rules
|
||||
|
||||
## Web Server origins (0.29.4)
|
||||
- Reuse is scoped by frontend scheme, Host, exact client URL, Vhost/content policy and eligible `Vary` dimensions. HTTP and HTTPS objects do not cross-share.
|
||||
- GET and suitable HEAD requests may reuse a full object; changing request methods do not silently invalidate all cached GET results.
|
||||
- Authorization, dynamic cookies, `Set-Cookie`, sticky-affinity cookies, Tinyauth and other private signals normally cause **BYPASS**. A limited static-file cookie exception exists for configured static extensions and does not override `private`, `no-store`, `Set-Cookie` or `Vary: Cookie`.
|
||||
- Origin status, response size, TTL, unsupported metadata, range/conditional semantics and request/client cache directives can prevent fill or require validation.
|
||||
- A Web Service selected as a Vhost source uses the same cache policy for eligible local files. File changes can be checked with `ETag` / `Last-Modified`; a 304 validation may refresh a stored body without downloading it again.
|
||||
|
||||
A Web Service selected as the vhost source now uses the normal RustProxy shared
|
||||
object-cache pipeline when both global and vhost cache policy permit it.
|
||||
`Cache-Control: public, max-age=...` (or another eligible freshness policy)
|
||||
can produce `MISS` followed by `HIT` instead of a browser-only cache header.
|
||||
Vhost response rules run before cache fill, and transformed output is what is
|
||||
stored. Private/`Set-Cookie`/`no-store` origin responses remain ineligible,
|
||||
even if a later response-header rule replaces their visible cache headers.
|
||||
## Freshness and diagnostics
|
||||
|
||||
For Web Server sources (0.32.2), requests with `Cache-Control: no-cache`,
|
||||
`max-age=0`, or an expired/inadequately fresh representation check the local
|
||||
origin using the stored origin `ETag` / `Last-Modified`. When the Web Server
|
||||
returns `304 Not Modified`, RustProxy refreshes the same shared-cache object,
|
||||
keeps its body and increments `X-Cache-Hits`. The original browser conditional
|
||||
headers determine whether the client receives 304 or the cached 200 body.
|
||||
Changed static files cause a new response/cache fill; `no-store`/`private`
|
||||
origin policy continues to prevent shared storage. HEAD and single-range
|
||||
requests can reuse a successfully revalidated full object. A Web Server
|
||||
source does not run the remote-backend background revalidation worker or its
|
||||
stale-if-error fallback. A PHP handler that ignores validators may still
|
||||
return 200, which must be handled as a real origin response rather than an
|
||||
assumed unchanged object.
|
||||
| State | Interpretation |
|
||||
|---|---|
|
||||
| `MISS` | No suitable fresh cached representation was found; the origin was consulted. The response may still be ineligible for storage. |
|
||||
| `HIT` | A matching stored object was reused. |
|
||||
| `BYPASS` | Cache was disabled or a policy intentionally skipped it (for example authorization, cookies or a path exclusion). |
|
||||
| Revalidated | An existing representation was checked against the origin; a `304` can retain the body. |
|
||||
|
||||
## HTTP BAN / PURGE from authorized client IPs
|
||||
`Age` is total HTTP age, not only the time spent in RustProxy. The optional `X-Cache-Resident-Age` reports the cache residency interval. Debug headers such as `X-Cache`, `X-Cache-Hits` and reason labels are optional and should be enabled deliberately. Browser hard reloads commonly force revalidation, so test with `curl` when investigating.
|
||||
|
||||
The **Settings → Cache → HTTP BAN / PURGE IP allowlist** control sets
|
||||
`cache.http_invalidation_allowed_cidrs`. The default is **empty (disabled)**.
|
||||
Allow individual client IPs or small CIDR ranges, one per line. Saves validate
|
||||
IPv4/IPv6 CIDR syntax and reject `/0` networks. An allowed request must also
|
||||
pass the vhost listener binding, global/vhost ACL, and rate limits. The
|
||||
operation is restricted to the matched external HTTP/HTTPS scheme, `Host` and
|
||||
vhost; it cannot choose other tenants or the opposite scheme through headers. Commands use the existing
|
||||
cache invalidation engine, are audit-logged before execution and share a
|
||||
two-worker maintenance limit with authenticated admin invalidation (429 when
|
||||
busy). They are never forwarded upstream.
|
||||
Example, replacing the listener and Host values with yours:
|
||||
|
||||
- `PURGE /path?query` immediately invalidates exact cached representations of
|
||||
that URL. Returns `200` with JSON result/count. Other query variants are not
|
||||
matched.
|
||||
- `BAN /prefix` records a lazy prefix ban of older cached representations
|
||||
for that host/vhost. Returns `202` and ban ID, normally expires after one hour.
|
||||
Optional `X-RustProxy-Ban-Ttl: 30` sets 1..604800 seconds. Newly filled
|
||||
representations are allowed (Varnish-like lazy BAN).
|
||||
- `BAN /` requires `X-RustProxy-Confirm-Wide: 1` to prevent accidental
|
||||
whole-host invalidation. Request bodies, upgrades, arbitrary selectors and
|
||||
wildcard cross-vhost operations are rejected.
|
||||
|
||||
Examples from an allowlisted machine (replace the domain and path):
|
||||
|
||||
```sh
|
||||
curl -i -X PURGE 'https://site.example/assets/app.js?v=3'
|
||||
curl -i -X BAN 'https://site.example/assets/'
|
||||
curl -i -X BAN 'https://site.example/' -H 'X-RustProxy-Confirm-Wide: 1'
|
||||
```bash
|
||||
for n in 1 2 3; do
|
||||
curl -sS -D - -o /dev/null -H 'Host: app.example.com' \
|
||||
http://127.0.0.1:8080/public.css
|
||||
sleep 2
|
||||
done
|
||||
```
|
||||
|
||||
**Behind a reverse proxy:** forward nonstandard `BAN`/`PURGE` methods to the
|
||||
RustProxy public listener; do not let an upstream cache intercept them. Configure
|
||||
RustProxy's existing `forwarding.trusted_proxies` accurately and ensure the
|
||||
edge sanitizes/replaces untrusted `Forwarded`/`X-Forwarded-For` headers. When a
|
||||
trusted proxy does not provide a usable real-client chain, BAN/PURGE fails
|
||||
closed. Do **not** allowlist an entire proxy NAT network unless all its users
|
||||
should have destructive cache access. Unlike full Varnish VCL, this interface
|
||||
intentionally provides only exact PURGE and bounded URL-prefix BAN, not an
|
||||
arbitrary untrusted regex/VCL expression.
|
||||
Look for cache reason labels including `request_cookie`, `request_authorization`, `response_cache_control`, `route_disabled` and `cache_hit` when diagnostic headers are enabled. A `MISS` alone does not prove the object was admitted to HOT/COLD.
|
||||
|
||||
## Settings
|
||||
## Advanced behavior
|
||||
|
||||
| Field | Default | Bounds / meaning |
|
||||
|---|---|---|
|
||||
| enabled | true | Global master switch; vhosts may disable cache but cannot re-enable a globally disabled cache |
|
||||
| max_bytes | 268435456 | Hard HOT allocation budget (body backing allocation + charged entry metadata); minimum 64 KiB, no artificial high ceiling |
|
||||
| max_object_bytes | 16777216 | Maximum logical body accepted by one fill; minimum 1 KiB and at most `max_bytes` |
|
||||
| max_entries | 4096 | 1..16384 hot objects |
|
||||
| transient_max_bytes | 67108864 | Hard RAM budget for fill buffers and complete bodies moving between HOT/COLD |
|
||||
| cold_queue_max_bytes | 33554432 | Byte ceiling for complete demoted objects waiting for the COLD writer; at most `transient_max_bytes` |
|
||||
| hot_eviction_max_objects | 64 | 1..4096 LRU removals attempted for one HOT admission before the new object passes uncached |
|
||||
| hot_auto_trim_enabled | true | Periodically trim sufficiently idle HOT objects only while HOT usage is above the target waterline |
|
||||
| hot_auto_trim_after_seconds | 300 | 5..86400; minimum idle age before an object can be removed by automatic HOT trim |
|
||||
| hot_auto_trim_target_percent | 80 | 10..95; automatic trim stops once HOT usage reaches this percentage of `max_bytes` |
|
||||
| cold_enabled | false | Enable persistent disk tier; never implied by hot-cache enable |
|
||||
| cold_path | cache/cold | Relative path under the private RustProxy data directory |
|
||||
| cold_max_bytes | 4294967296 | 64 KiB..1 TiB disk budget |
|
||||
| cold_max_entries | 50000 | 1..1000000 cold objects |
|
||||
| cold_gc_interval_seconds | 60 | 5..86400; interval between asynchronous pressure-GC checks |
|
||||
| cold_gc_max_delete | 256 | 1..10000; maximum objects removed by one automatic GC pass |
|
||||
| cold_after_seconds | 300 | 5..604800; idle hot objects are eligible after this delay |
|
||||
| cold_on_idle | true | Move idle eligible hot objects to cold storage |
|
||||
| cold_max_hot_seconds | 3600 | 5..2592000; total RAM-residency age used when `cold_on_age=true` |
|
||||
| cold_on_age | false | Demote objects after the maximum hot-tier age even if they remain frequently accessed |
|
||||
| cold_on_lru | true | Move objects displaced by hot-tier LRU pressure to cold storage |
|
||||
| cold_on_shutdown | true | Best-effort direct hot-to-cold flush during ordinary graceful shutdown/restart |
|
||||
| default_ttl_seconds | 0 | Only explicit public responses without origin freshness |
|
||||
| max_ttl_seconds | 86400 | 1..604800; maximum configured retention/freshness cap |
|
||||
| bypass_query | false | Bypass requests containing a query when enabled |
|
||||
| bypass_prefixes | [] | Up to 64 normalized absolute path prefixes |
|
||||
| http_invalidation_allowed_cidrs | [] | Opt-in IP/CIDR allowlist (max 32) for HTTP BAN/PURGE. Empty blocks all HTTP methods; `/0` is invalid. |
|
||||
| static_ignore_cookies | true | For configured static extensions only, ignore request Cookie for eligibility/keying |
|
||||
| expose_status | false | Emit `X-Cache: HIT`, `MISS` or `BYPASS` |
|
||||
| expose_hits | false | Emit per-object `X-Cache-Hits` |
|
||||
| expose_cache_status | false | Emit standardized `Cache-Status` (RFC 9211) |
|
||||
| expose_resident_age | false | Emit `X-Cache-Resident-Age` on reused stored responses |
|
||||
| via_header | `RustProxy` | Append this `Via` response value; `null` disables it |
|
||||
| collapse_enabled | true | Collapse concurrent foreground fills for the same primary cache key |
|
||||
| collapse_wait_timeout_ms | 1500 | 10..60000 ms follower wait before fallback |
|
||||
| collapse_timeout_fallback | origin | `origin` or `stale_if_available` after follower timeout |
|
||||
| stale_if_error_timeout | true | Allow RFC 5861 stale fallback for timeout failures |
|
||||
| stale_if_error_connection | true | Allow stale fallback for connection failures |
|
||||
| stale_if_error_dns | true | Allow stale fallback for DNS failures |
|
||||
| stale_if_error_tls | true | Allow stale fallback for TLS failures |
|
||||
| stale_if_error_statuses | [500,502,503,504] | Up to 32 unique eligible HTTP 5xx statuses |
|
||||
| proactive_refresh_enabled | false | Schedule sampled cached objects for validator refresh before expiry |
|
||||
| proactive_refresh_before_expiry_seconds | 15 | 1..604800 seconds before expiry |
|
||||
| proactive_refresh_percent | 10 | 0..100 deterministic percentage of primary keys |
|
||||
| proactive_refresh_max_inflight | 4 | 1..64 concurrent proactive refreshes |
|
||||
- **Budget limits:** HOT RAM capacity, object count/size, transient fill memory and optional COLD byte/object limits. Configure based on real workload and monitor disk/RSS.
|
||||
- **HOT/COLD movement:** entries may be demoted, recovered or expired; a periodic/explicit COLD cleanup is available. Cache storage is not a permanent archive.
|
||||
- **Request collapsing:** eligible concurrent MISS requests can share a bounded fill, limiting origin bursts.
|
||||
- **Stale-if-error and refresh:** conditional options can help on eligible backend failures, but are not an unconditional stale-serving guarantee. Web Server local-file validation has a distinct policy.
|
||||
- **Compression/transformation:** cached variants and validators must remain compatible with client encoding and content-modification settings.
|
||||
|
||||
The hot tier enforces `max_bytes` and `max_entries` before publication. HOT accounting uses the retained body allocation (the fill buffer capacity transferred into `Bytes`) plus directly owned entry metadata and a conservative per-entry charge for the HOT hash/LRU/variant indexes, while `hot_logical_bytes` reports only visible body lengths. Rust container/allocator APIs do not expose every bucket/node allocation exactly, so `hot_allocated_bytes` is intentionally a conservative charged estimate rather than a promise that it equals process RSS. In-progress fills reserve a separate bounded transient budget before growing their buffers. When HOT pressure would exceed the budget plus a small allocator-rounding headroom (roughly 1/64 of HOT, capped at 1 MiB), RustProxy removes LRU objects first; if admission still cannot be made safe within `hot_eviction_max_objects`, the response continues normally without being cached. `max_object_bytes` is checked incrementally before every body-buffer growth, so an oversized response is not fully accumulated before rejection.
|
||||
## Clear or invalidate content
|
||||
|
||||
Automatic HOT trim is a separate low-pressure maintenance path. Every maintenance pass first checks whether HOT allocation is above `hot_auto_trim_target_percent` of `max_bytes`; if so, only least-recently-used objects that have been idle for at least `hot_auto_trim_after_seconds` are eligible. One pass is capped by `hot_eviction_max_objects`. Active/recent objects are never removed by this mechanism. Eligible objects follow the existing COLD-on-LRU policy when enabled; otherwise they are evicted. This is intended to release stale working-set memory after traffic bursts without periodically purging the cache or reloading vhosts.
|
||||
Authenticated Cache page/API operations can purge selected entries, domains or larger scopes. The optional public HTTP `PURGE`/`BAN` interface is **disabled by default**. Enable it only by listing trusted client IPs/CIDRs under **Settings → Cache → HTTP BAN / PURGE IP allowlist**; operations are additionally subject to Vhost binding and normal access controls. `PURGE` addresses an exact URL; `BAN` applies the supported bounded matching rules. Never allow untrusted Internet clients to purge all tenants' objects.
|
||||
|
||||
When cold storage is enabled,
|
||||
`cold_on_lru`, `cold_on_idle` and `cold_on_age` independently choose which hot objects are demoted.
|
||||
`cold_after_seconds` measures time since the last access. `cold_max_hot_seconds` measures total
|
||||
hot-tier residency, so it can move a frequently accessed object to disk after a configured age.
|
||||
The administration UI exposes these as separate policies to avoid conflating "old" with "unused".
|
||||
Demotions use an asynchronous queue bounded by both object count and `cold_queue_max_bytes`. A demoted `Entry` is removed from HOT accounting and moved, not copied, into a `ColdDraft`; its `Bytes` payload remains charged to `transient_max_bytes` until persistence completes and the draft is dropped. The persisted `ColdEntry` contains compact lookup/invalidation/LRU metadata rather than the body allocation. Since 0.27.3 it also does not retain the full response `HeaderMap` or origin validator values per COLD object; those are validated and loaded from the existing metadata JSON only for an actual COLD read. The in-memory body digest is 32 raw bytes and the map-owned cache key is not duplicated inside the entry. This reduces long-lived COLD index RAM without changing Cold format 3 on disk. This prevents the COLD writer queue from becoming a second unbounded RAM cache. If an HTTP response is already using a clone of the same `Bytes`, that last transport-owned reference can keep the backing allocation alive until the response finishes; the cache-owned HOT/COLD reference itself is released after persistence. Process RSS is therefore monitored separately from cache accounting. Supported
|
||||
glibc/Linux builds ask the allocator to release unused pages after bulk demotion. During
|
||||
ordinary graceful shutdown, `cold_on_shutdown` performs a best-effort direct flush of remaining hot
|
||||
objects before the store is closed. A **managed software update** (`update.sh` / `scripts/deploy/deploy.py`)
|
||||
uses a private one-shot marker and, on RustProxy 0.13.2+, forces this hot-to-cold handoff whenever
|
||||
the cold tier is enabled even if `cold_on_shutdown=false`. The updater gives the process a larger
|
||||
stop budget and removes the marker before backing up state. If the cold tier is disabled there is
|
||||
nowhere persistent to hand the hot objects, so RAM-only cache is still lost on process exit. The
|
||||
body is stored as a raw `.body` file and metadata as JSON with a SHA-256 body digest. A fresh cold hit is integrity-checked and promoted to
|
||||
RAM. Cold format 3, introduced in 0.21.0, stores objects below the first two hexadecimal
|
||||
characters of the SHA-256 cache key, for example `cache/cold/7f/<hash>.body` and
|
||||
`cache/cold/7f/<hash>.json`. Shard directories are created lazily. The previous flat
|
||||
Cold Cache layout is deliberately not migrated; legacy flat objects are removed and can
|
||||
be refilled from origin. Format 3 metadata also carries RFC 5861 windows, revalidation
|
||||
flags, origin validators, last access and hit count.
|
||||
In 0.34.0, changing operational Vhost settings is intended to preserve compatible HOT/COLD objects; content-affecting changes invalidate affected scopes and COLD carries an additional fingerprint against stale resurrection. **This new behavior still needs release-level verification**, so validate cache after configuration edits and during upgrades. Earlier persistent cache entries may need refilling following format/compatibility transitions.
|
||||
|
||||
Automatic Cold Cache GC is asynchronous. When either byte or entry usage reaches about
|
||||
90% of its configured limit, it selects old/rarely used candidates and cleans toward
|
||||
about 80%, removing at most `cold_gc_max_delete` objects in one pass. Metadata is
|
||||
snapshotted in small batches and disk deletion is queued instead of holding request-path
|
||||
state across a full-cache sort/delete loop.
|
||||
## Troubleshooting
|
||||
|
||||
## Varnish-style identity and variants
|
||||
- **Always BYPASS:** check both cache enable switches, login/cookies, exclusion rules, methods, Tinyauth and sticky sessions.
|
||||
- **MISS never becomes HIT:** check response `Cache-Control`, status, object size, `Vary`, `Set-Cookie`, incomplete origin streams and whether the same scheme/Host/query is used.
|
||||
- **Content unexpectedly old:** inspect Age, TTL, validators, stale rules and previous invalidation; purge only affected content after confirming its identity.
|
||||
- **Memory/disk pressure:** review HOT/COLD budgets and cleanup, rather than increasing limits without measurements.
|
||||
|
||||
The logical primary cache identity is:
|
||||
|
||||
- a stable cache-compatibility revision derived only from settings that can change
|
||||
cached representation/origin semantics, plus vhost scope (internal isolation),
|
||||
- normalized frontend scheme (`http`/`https`),
|
||||
- normalized original Host authority, including an explicit port,
|
||||
- exact request path and query.
|
||||
|
||||
Selected backend-pool member, route/backend IDs and unrelated request headers are not
|
||||
part of the representation hash. This means two healthy members of the same pool can
|
||||
serve/fill the same public cache object, while HTTP and HTTPS remain isolated. Headers
|
||||
such as User-Agent, X-Forwarded-For or browser client hints no longer create unnecessary
|
||||
variants by themselves.
|
||||
|
||||
The response `Vary` header provides the secondary representation identity. For example,
|
||||
`Vary: Accept-Language` stores independent language variants under the same scheme + Host + URL
|
||||
primary hash. `Vary: *`, malformed Vary fields or an excessive Vary field set are not
|
||||
stored. If an origin changes the Vary schema for a URL, incompatible old variants are
|
||||
removed so an obsolete no-Vary object cannot shadow a newer Vary representation (or
|
||||
vice versa).
|
||||
|
||||
Cookie and Authorization requests still pass by default, matching Varnish built-in
|
||||
safety behavior. RustProxy's static-cookie optimization can ignore Cookie only for the
|
||||
configured static file extensions; response `Set-Cookie`, `private`, `no-store` and
|
||||
`Vary: Cookie` still prevent unsafe shared reuse.
|
||||
|
||||
### Per-vhost forced-cache targets
|
||||
|
||||
A vhost can opt into an explicitly unsafe `cache.unsafe_force` list for narrowly selected public request targets. It is intentionally not available as a global cache setting. Each configured target starts with `/`; a target without `?` matches by normalized path prefix, while a target containing `?` matches the original path+query prefix.
|
||||
|
||||
For matching GET/HEAD requests, forced mode may ignore request-side bypasses caused by a query string, Cookie, Authorization, `Cache-Control: no-cache`/`no-store`/`max-age=0`, and `Pragma: no-cache`. Protocol-safety exclusions such as Upgrade, unsupported Range/preconditions and request bodies remain in force. Cookie and Authorization values are added to the cached representation's `Vary` identity in forced mode so different credentials/cookies cannot reuse each other's object.
|
||||
|
||||
Forced mode does **not** override response-side privacy policy. `Set-Cookie`, `Cache-Control: private`, `no-store`, `Vary: *`, partial/trailer/SSE/gRPC responses and the normal size/lifetime validation still prevent storage. Use this only for URLs whose origin semantics are understood; ordinary cache defaults remain safer.
|
||||
|
||||
## Request methods and invalidation
|
||||
|
||||
GET and HEAD participate in lookup. They share one representation identity. On a
|
||||
cacheable HEAD miss RustProxy fetches the backend representation as GET, may populate
|
||||
the shared object, and strips the response body before delivering the original HEAD
|
||||
request.
|
||||
|
||||
POST, PUT, PATCH, DELETE, OPTIONS and TRACE pass to the backend and are not stored.
|
||||
They do **not** implicitly purge the whole cache. This fixes the pre-0.11.5 behavior
|
||||
where any successful unsafe request could erase unrelated static objects and destroy
|
||||
the hit ratio.
|
||||
|
||||
Cache invalidation remains explicit:
|
||||
|
||||
- authenticated full purge can clear all objects or one vhost scope,
|
||||
- selective PURGE supports exact URL, URL prefix, `Cache-Tag`, safe regex and primary cache key, with optional host and vhost restrictions,
|
||||
- exact/prefix matching uses the stored request target including query; a match removes the whole `Vary` family under that primary cache key,
|
||||
- selective PURGE operates on RAM and Cold Cache and advances a publication generation so no fill that started before the operation can publish afterward, even if the selector depended on response metadata such as `Cache-Tag`,
|
||||
- BAN records a bounded TTL rule without synchronously scanning the entire store; objects created before a matching active BAN are rejected during lookup/insertion and are removed later by bounded GC,
|
||||
- new fills created after a BAN rule are allowed, so BAN invalidates the old representation set rather than permanently blocking a URL,
|
||||
- regex rules and prefix `/` are treated as broad operations and require explicit confirmation,
|
||||
- only cache-compatibility changes advance the normal cache generation/revision; unrelated logging/TLS/admin/metrics/response-presentation saves retain objects, and cache request-bypass edits plus response-only `X-Cache`/`Cache-Status`/resident-age/`Via` diagnostics also apply without purging stored objects.
|
||||
|
||||
Unrestricted anonymous cache-maintenance access is not exposed: the public
|
||||
HTTP BAN/PURGE interface is explicitly IP/CIDR-authorized (empty by default),
|
||||
separate from the administrator-only selective invalidation API. If an
|
||||
application changes a GET resource after POST/PUT/PATCH/DELETE, use origin
|
||||
freshness/validators or one of the explicit invalidation paths; RustProxy
|
||||
does not guess the affected URLs. For exposure review and test scenarios, see
|
||||
[security audit](SECURITY_AUDIT.md).
|
||||
|
||||
## Eligibility and freshness
|
||||
|
||||
Hard request bypasses include Cookie (outside the static exception), Authorization,
|
||||
Upgrade, `If-Match`, `If-Unmodified-Since`, nonempty request bodies, configured path/query
|
||||
exclusions and request `Cache-Control: no-store`.
|
||||
|
||||
A single `Range: bytes=...` request is special rather than a normal bypass. When a complete
|
||||
fresh cached representation is available, RustProxy evaluates `If-Range` and can return
|
||||
`206 Partial Content` directly with `Content-Range`, `Accept-Ranges`, the correct sliced
|
||||
`Content-Length`, validators and normal cache `Age`; an unsatisfied range returns `416`
|
||||
with `Content-Range: bytes */<length>`. A mismatching `If-Range` returns the complete cached
|
||||
representation. If origin access is needed, RustProxy forwards the Range request as an
|
||||
identity, non-transformed stream. Multiple/multipart, duplicate or otherwise unsupported
|
||||
Range forms are passed through to origin and are never stored as separate range objects.
|
||||
|
||||
A fresh cached representation can satisfy `If-None-Match`/`If-Modified-Since` locally.
|
||||
Request `no-cache`, `max-age=0` and legacy `Pragma: no-cache` require validation when a
|
||||
safe stored validator exists. `max-age=N` and `min-fresh=N` are freshness constraints:
|
||||
they reuse the cached response when its current Age/remaining freshness satisfies the
|
||||
client limit and otherwise drive normal revalidation/fetch behavior. Request `max-stale`
|
||||
can explicitly allow a retained stale response: bare `max-stale` accepts any retained
|
||||
staleness, while `max-stale=N` bounds it. Freshness expiry does not itself physically
|
||||
delete an object; normal RAM LRU/Cold Cache GC may still evict it. `must-revalidate` and
|
||||
`proxy-revalidate` override client stale permission. With a safe validator, an origin
|
||||
304 refreshes metadata and reuses the stored body.
|
||||
|
||||
Responses with `Set-Cookie`, `private`, `no-store`, `Content-Range`, trailers,
|
||||
SSE/gRPC, `Vary: *`, invalid freshness metadata or unusable size/lifetime are not
|
||||
stored. Response `Cache-Control: no-cache` may be retained only with ETag or
|
||||
Last-Modified and must validate before every reuse. Both origin and transformed response
|
||||
policy must remain eligible.
|
||||
|
||||
`s-maxage` takes precedence over `max-age`, then Expires/Date. `Age` uses the RFC 9111
|
||||
current-age model and includes origin age; it is not a measure of how long the object has
|
||||
been in RustProxy. Cache metadata keeps normal TTL, `stale-while-revalidate`,
|
||||
`stale-if-error`, `must-revalidate` and `proxy-revalidate` separately. A representation
|
||||
is therefore **fresh**, **stale-but-servable** only under an applicable directive, or
|
||||
**expired/unusable**.
|
||||
|
||||
Inside `stale-while-revalidate=N`, RustProxy may return stale immediately and starts one
|
||||
deduplicated background refresh for that cache identity. Inside `stale-if-error=N`, stale
|
||||
is used only after an eligible origin failure. Version 0.22 makes timeout, connection, DNS
|
||||
and TLS classes independently configurable and uses an explicit configurable 5xx status
|
||||
allowlist; defaults preserve the previous behavior (`500`, `502`, `503`, `504` plus all four
|
||||
transport classes). After TTL plus the corresponding stale window, that mode is unavailable.
|
||||
`must-revalidate` and `proxy-revalidate` still permit storage, but prohibit all stale delivery
|
||||
after expiry, including RFC 5861 and request `max-stale`.
|
||||
|
||||
Optional proactive refresh schedules a deterministic percentage of cache keys before normal
|
||||
expiry. It reuses the same validator-based refresh logic and background-revalidation guard,
|
||||
keeps only one schedule per key, and enforces a global concurrent-refresh bound. A newly
|
||||
completed fill can schedule this work immediately, so sampled objects do not require another
|
||||
client request to start the pre-expiry timer. The feature is disabled by default.
|
||||
|
||||
## Resident age
|
||||
|
||||
`X-Cache-Resident-Age` is the RustProxy-specific whole-second clock for how long the
|
||||
stored representation has remained in this cache. In 0.11.5 that clock is independent
|
||||
from the HTTP freshness `Age` clock and survives:
|
||||
|
||||
- ordinary hot-cache HITs,
|
||||
- successful backend 304 revalidation,
|
||||
- hot-to-cold demotion,
|
||||
- cold-to-hot promotion.
|
||||
|
||||
A value of `0` is valid only during the first sub-second of residency. Revalidation or
|
||||
tier movement must no longer reset a long-lived object to zero.
|
||||
|
||||
## Streaming and capacity
|
||||
|
||||
RustProxy tees response frames while delivering them; it does not wait for the complete
|
||||
origin body before sending the first bytes. Only a complete successful body is
|
||||
published. Oversize, body error, trailer and cancelled-fill paths discard the candidate.
|
||||
Large buffers grow incrementally and consume multiple bounded fill permits.
|
||||
|
||||
The configured resident byte limit accounts for body bytes, estimated header storage
|
||||
and fixed per-entry overhead; it is not total process RSS. Active HIT references,
|
||||
in-flight origin responses, compression buffers and runtime allocations also use memory.
|
||||
|
||||
With `collapse_enabled=true`, the first foreground MISS for a primary cache key owns the
|
||||
fill while concurrent followers wait for publication. The fill guard is held through the
|
||||
actual response-body tee, so followers cannot wake merely because origin headers arrived.
|
||||
Leader error/cancellation releases waiters. A follower that wakes to a different `Vary`
|
||||
representation re-enters the single-flight decision instead of immediately stampeding the
|
||||
origin. After `collapse_wait_timeout_ms`, the configured fallback is either an independent
|
||||
origin request or an eligible stale response. SWR/background revalidation keeps its separate
|
||||
existing deduplication guard.
|
||||
|
||||
## Diagnostics and API
|
||||
|
||||
`GET /api/cache` reports settings, HIT/MISS/BYPASS, hot/cold counts and bytes,
|
||||
configured COLD byte/object limits, promotions/demotions, errors, fill pressure, object
|
||||
views and per-domain reason counters. The `/cache` dashboard shows HOT and COLD
|
||||
occupancy against the configured object-count and byte limits with progress bars and
|
||||
explicit percentages. HOT byte occupancy uses the charged/allocated HOT value rather
|
||||
than logical body bytes; COLD byte occupancy uses persisted storage bytes. Hit ratio is
|
||||
`hits / (hits + misses)` and intentionally excludes BYPASS traffic.
|
||||
|
||||
`POST /api/cache/purge` requires the existing administrator session, same-origin marker
|
||||
and CSRF token. `?vhost=<id>` purges only that vhost scope; omitting it purges all cache
|
||||
objects. Purge is audited.
|
||||
|
||||
`POST /api/cache/invalidate` performs authenticated selective `purge` or `ban` using
|
||||
`exact`, `prefix`, `tag`, `regex` or `cache_key`. Optional `host` and `vhost` restrict scope;
|
||||
BAN accepts a bounded TTL. Regex and prefix `/` require `confirm_wide=true`. The result
|
||||
reports the selector/scope, affected entry count, elapsed time, status and BAN id when one
|
||||
was created. `GET /api/cache/bans` lists active rules and `DELETE /api/cache/bans/{id}`
|
||||
removes one. These operations use the normal administration audit path. The Cache panel
|
||||
exposes the same controls plus per-object **Purge URL**.
|
||||
|
||||
`POST /api/cache/cold/cleanup` starts a selective asynchronous Cold Cache cleanup using
|
||||
the same candidate/removal machinery as automatic GC. Criteria can select entries unused
|
||||
for a duration, cap removal to the oldest N objects, and/or require `hits == 0`.
|
||||
`GET /api/cache/cold/cleanup/{id}` reports job status, removed object count and freed
|
||||
bytes. Purge-all and purge-vhost remain the full-clear mechanisms; selective invalidation
|
||||
is deliberately separate.
|
||||
|
||||
`X-Cache`, `X-Cache-Hits`, `Cache-Status`, `X-Cache-Reason` and
|
||||
`X-Cache-Resident-Age` are optional diagnostics. `Via` is a separate configurable response header, enabled by default with value `RustProxy`; RustProxy appends it instead of deleting an upstream `Via` chain. `Cache-Status` is the standardized
|
||||
RFC 9211 field name; RustProxy does not rename it to the legacy/custom `X-Cache-Status`.
|
||||
HTTP/2 and HTTP/3 serialize field names in lowercase, so `cache-status:` on the wire is
|
||||
normal. When the cached representation was produced by the transform pipeline,
|
||||
RustProxy appends the extension parameter `transformed`, for example:
|
||||
|
||||
```http
|
||||
Cache-Status: RustProxy; hit; ttl=556488; detail="cache_hit"; transformed
|
||||
```
|
||||
|
||||
Origin lookalike values (`Cache-Status`, `X-Cache-Status` and the other RustProxy
|
||||
cache diagnostics) are removed before RustProxy emits its own diagnostics. HTTP `Age`
|
||||
is generated from cache freshness metadata on reused responses. For 0.26.6 configuration
|
||||
compatibility an explicit response-header rule may set/append/remove `Age`, but that rule
|
||||
runs only after cache lookup/revalidation/storage decisions and therefore cannot alter the
|
||||
internal freshness clock. The Cache dashboard also marks transformed objects explicitly.
|
||||
|
||||
## Pool behavior
|
||||
|
||||
Backend selection and health policy still run before an origin fetch. The selected pool
|
||||
member no longer fragments the cache identity in 0.11.5, so a fresh public object can be
|
||||
reused regardless of which healthy member would have been selected for a MISS. A pool
|
||||
with no eligible member still returns 503 before RustProxy can use the cache; this cache
|
||||
is not a stale-availability layer.
|
||||
|
||||
Cookie affinity and protected/authenticated traffic keep their existing privacy bypass
|
||||
rules. Per-vhost response/security policy also applies to HIT responses.
|
||||
See [Monitoring](MONITORING.md) for cache telemetry and [Security](SECURITY.md) for privacy boundaries.
|
||||
Reference in new issue
Block a user