v0.34.0_8

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-18 08:26:35 +02:00
1 parent b6a3e12225
commit 480c3ec769
92 files changed
+298 -10035

No files matched your search

+1 -1
View File
@@ -21,7 +21,7 @@ additional requests receive 429. Ambiguous duplicate control headers and
request bodies are rejected. See
[CACHE](CACHE.md#http-ban--purge-from-authorized-client-ips) for request examples,
scoping, confirmation and reverse-proxy requirements, and
[0.31.0 security review](SECURITY_AUDIT_0.31.0.md) for audit findings. The authenticated
[security review](SECURITY_AUDIT.md) for audit findings. The authenticated
`/api/cache/invalidate` endpoint remains unchanged.
## Browser documentation and anonymous surface
+1 -1
View File
@@ -232,7 +232,7 @@ 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
[0.31.0 security audit](SECURITY_AUDIT_0.31.0.md).
[security audit](SECURITY_AUDIT.md).
## Eligibility and freshness
+3 -6
View File
@@ -54,15 +54,12 @@ Native Rust test file: `src/cache/tests/hot_hit_034_tests.rs`.
./dev.sh check
CARGO_INCREMENTAL=0 ./dev.sh test
CARGO_INCREMENTAL=0 cargo build --release --locked
python3 scripts/tests/cache_hot_034_static_test.py
cargo test --release --locked --lib manual_hot_lookup_microbenchmark -- --ignored --nocapture
```
For real HTTP RPS use the supplied process regression and same-host nginx
benchmark (`scripts/performance/README.md`) with **identical** configs. The
standard nginx comparator benchmark explicitly disables the cache; it cannot
measure this HOT-specific improvement. Compare two compiled RustProxy 0.34.0
binaries with the same cache-enabled, cache-warmed vhost and fixed no-query URL.
For real HTTP RPS, compare two compiled RustProxy 0.34.0 binaries
with the same cache-enabled, cache-warmed vhost and fixed no-query URL.
A cache-disabled nginx comparison cannot measure this HOT-specific improvement.
Check `X-Cache: HIT`, body SHA-256, exact `Cache-Status`, transform marker, Age,
ETag, p95/p99, CPU/request, memory and warm-up/eviction counts while measuring.
-147
View File
@@ -1,147 +0,0 @@
# RustProxy 0.34.0 DEV — 1 KiB lock contention follow-up
## Goal and baseline
**Priority 1: reverse proxy, HTTP/1.1 keep-alive, uncompressed 1,040-byte
responses, cache OFF.** Aiming for approximately 100,000 RPS and at least 80%
of nginx under the same pinned-CPU benchmark. Priority 2: local static WWW,
measured separately. No feature removals and **no version bump**.
The user supplied a **new executable** (SHA-256
`1a84c58b20945bba9fdd1bc1a44d9e9d6ac7659d714343a46a1a8511a78331fa`,
`rust-proxy 0.34.0`), apparently built from the prior DEV revision. It passed
151 actual black-box HTTP tests with 0 failures and 1 skipped optional zstd
decoder. This runtime coverage belongs to the **supplied executable only**,
not to the freshly edited source below. Neither `cargo` nor `rustc` is
available in the editing environment; the new revision has NOT been compiled.
## Modified source
### `Cache::record` and `Cache::record_reason`
Before, every HIT, MISS and BYPASS acquired the same `Store` mutex used by the
HOT object store, exact LRU and cache inventory. With cache disabled, a 1 KiB
request still contested that mutex to increment `cache_disabled`.
`src/cache/stats.rs` now holds bounded per-(scope,domain) statistics separately:
- An `ArcSwap`-published immutable, borrowed-lookup BTreeMap of domain handles.
**The normal path does not lock or allocate**, even for BYPASS. New domains
alone use a registration mutex and a copy-on-first-visit registry update.
- Atomic counts for HIT, MISS, BYPASS, stored objects and saved body bytes.
The common static reasons (`cache_disabled`, `cache_hit`, `cache_miss`,
`request_cookie`) use direct atomic counters. Other reason names use another
immutable published index and atomic counters; they are registered once.
- Hard cardinality remains **4096 (scope, domain) pairs**. When full, new
unregistered domains are skipped as before. Scope purge preserves stats.
Only a revision-bearing global purge resets the per-domain registry.
- The dashboard still exposes the same `DomainCounters`, reason strings,
saved byte counts, ratio and per-vhost/object views. Counter snapshots are
quiescent-exact, but under concurrent requests individual fields can reflect
slightly different instants, consistent with other atomic metrics.
- HOT object and COLD representation/LRU access, barriers, memory accounting,
cache statuses, hit policy, retry and backend security paths are unchanged.
Trade-offs: a first-visit registration copies the bounded domain index and
allocates one counter set; this is not an adaptive, unbounded cache. The hot
path's ArcSwap guard and lookup still cost CPU. Measure that cost in profiles;
compare under high-cardinality traffic to identify any excessive registration
cost before production acceptance.
### Traffic analytics queue saturation
Previously `Exchange::drop` cloned the vhost, route, method, path, IP, user
agent and country before trying to enqueue the `Sample`. If the bounded
2,048-element MPSC queue was full, all those clones were discarded.
`Analytics::push_lazy` now acquires the same bounded Tokio queue permit with
`try_reserve` first and creates the sample only after a successful reservation.
Rejections increment `queue_dropped` exactly as before. Neither the analytics
worker, counters/top lists, reset behavior nor request log path is removed.
This improves allocation costs **on overflow** but does not turn dropped
analytics into delivered samples; monitor the existing drop counter.
### Per-vhost duration histogram atomics
Previously `VhostMetrics::complete` incremented every cumulative latency bucket
whose threshold covered the observation and the `+Inf` bucket. For an ordinary
short response this meant **11 shared atomic RMWs** for the histogram alone,
on top of existing exchange/status/byte/latency metrics.
It now increments **exactly one exclusive bucket** chosen from the same 10
latency boundaries. Both the regular vhost and Docker-vhost Prometheus exporters
compute the original cumulative `le` buckets, `+Inf` and `_count` from the 11
exclusive buckets **at scrape time**. The metric names, labels, boundaries,
body-lifetime timing and measurement collection remain intact. The cumulative
snapshot may be concurrent with request completions, as before. Added boundary
unit tests for exact thresholds and long-duration `+Inf`.
This is source-only: neither the throughput benefit nor compiled Rust behavior
has been verified in the editing environment. Review exporter parity and
under-load histogram correctness on the newly compiled executable.
## Verification and untested claims
- PASS: source preflight, cache/HOT/LB/RPS/histogram/static source guards and <=500-line
production Rust modules.
- PASS: SQLite migration + preset constraints, upgrade/lock and source
replacement tests.
- PASS: compiled user-supplied binary: 151 real HTTP checks, 0 failed,
1 optional zstd skip. This binary predates the source changes above.
- NOT RUN: Rust compile, Rust unit tests, native regressions or RPS after
rebuilding the new source. No numerical speedup attributed to this patch.
- Current diagnostic A/B on the user-supplied binary may use
`scripts/performance/ab_1kb_dev.py` and must remain labelled *old vs already
supplied binary*, **not** old vs newly edited source. The `wrk` 30 s x 7
pinned-CPU benchmark remains the production acceptance gate.
## Repeat on a compiler-enabled benchmark host
```bash
./dev.sh doctor
./dev.sh check
CARGO_INCREMENTAL=0 ./dev.sh test
CARGO_INCREMENTAL=0 cargo build --release --locked
python3 scripts/performance/regression.py \
--binary previous=/absolute/path/to/supplied-rust-proxy \
--binary edited="$(realpath target/release/rust-proxy)" \
--out /tmp/rustproxy034-counter-http
python3 scripts/performance/lb_regression.py \
--binary previous=/absolute/path/to/supplied-rust-proxy \
--binary edited="$(realpath target/release/rust-proxy)" \
--out /tmp/rustproxy034-counter-lb
python3 scripts/performance/transport_regression.py \
--binary previous=/absolute/path/to/supplied-rust-proxy \
--binary edited="$(realpath target/release/rust-proxy)" \
--out /tmp/rustproxy034-counter-transport
python3 scripts/performance/benchmark.py \
--binary previous=/absolute/path/to/supplied-rust-proxy \
--binary edited="$(realpath target/release/rust-proxy)" \
--nginx /usr/sbin/nginx --wrk /absolute/path/to/wrk \
--out /tmp/rustproxy034-counter-benchmark \
--modes proxy --sizes small --concurrency 16,64,128 \
--duration 30 --warmup 5 --repeats 7
```
Explicitly set `max_idle_per_host=64` on **both** before/after vhosts, with the
same TLS/H1/H2/health/cache/logging/settings. Verify 1,040 bytes and SHA-256,
no failures, latency percentiles, per-request CPU, RSS, file descriptors,
backend connects and all log/traffic drop counters. Test concurrent global
purges and per-vhost purges during traffic and verify the stats reset behavior
and cache HIT/MISS/BYPASS/reason distribution. Run long soaks and both cold/HOT
cache tests. The 100k RPS / nginx 80% goal is still **OPEN**.
## Build fix after first compiler feedback (OPT2-FIX1)
The first user build of OPT2 failed with six Rust `E0753` diagnostics because
`src/cache/stats.rs` starts with `//!` *inner module* documentation but is
injected via `include!("stats.rs")` after other items in `src/cache/mod.rs`.
The six documentation lines are now ordinary `//` comments; there is no
behavior change. Three unnecessary `mut` bindings in
`src/cache/dashboard.rs` were also removed (warnings only). Version remains
0.34.0; no performance or feature semantics have changed in this fix.
The compiler-enabled host should rerun `./dev.sh check`,
`CARGO_INCREMENTAL=0 ./dev.sh test` and an optimized release build, then
performance benchmarks. This editing environment still lacks `cargo` and
`rustc`, so the fix has been statically checked, **not compiled** here.
-137
View File
@@ -1,137 +0,0 @@
# RustProxy 0.34.0 DEV: 1 KiB reverse-proxy throughput audit
## Goal and evidence boundaries
Priority 1 is short (~1 KiB) HTTP reverse-proxy responses: aim for approximately
100,000 requests/s, and eventually at least 80% of nginx under the **same**
controlled benchmark, without disabling authentication, ACLs, caching semantics,
rate limits, traffic accounting, request IDs, logs, body streaming or protocol
support. Priority 2 is local WWW serving, to be tuned and measured separately.
This is a development revision; Cargo/package version remains **0.34.0**.
**This source is not compiled in the current environment.** Neither `cargo`
nor `rustc` is available. Only the user's pre-existing executable (SHA-256
`453757748455d7b9b50a6140e322ee4d4cf4a030eea29318686f2e28c594b644`)
was run. Live configuration A/B tests therefore substantiate a **configuration
bottleneck**, not the throughput of this edited source.
## Real baseline experiment (existing executable only)
Disposable loopback nginx upstream, 1,040-byte body, HTTP/1.1 persistent
frontend connections, caching disabled, identity response encoding, four Tokio
workers, ApacheBench (`ab -k -n 12000`), 16/64 concurrent frontend clients.
Checks outside the timed intervals verified HTTP 200, exact 1,040 bytes and
SHA-256 `62a08d75cdde0416c883d72458385b5e56ae8c14466934ad784587ddb93a548b`;
0 failed requests in the shown samples. The host has shared CPU resources;
results vary and are not a pinned-CPU or production acceptance benchmark.
| Existing executable configuration | 16 clients, RPS | 64 clients, RPS |
|---|---:|---:|
| max_idle_per_host = 4 | 18,226 | 17,935 |
| max_idle_per_host = 64 | 30,906 | 24,872 |
| Relative change (configuration only) | +69.6% | +38.7% |
A separate repeat experiment at 64 clients (two runs per variant, in order)
produced: `idle4` 14,028 / 14,834; `idle16` 22,205 / 24,711;
`idle32` 25,580 / 29,088; `idle64` 36,521 / 33,847;
`idle64 + recycle 250,000` 31,168 / 29,753;
`idle32_again` 34,798 / 29,194; `idle64_again` 34,677 / 29,965;
`idle4_again` 23,503 / 23,514 RPS. The order drift warns against
reporting a single percentage as a robust speedup. Raising generation
recycling from 25,000 to 250,000 did not establish any improvement; its
original limit is retained. All shown variants reported zero HTTP failures.
A preliminary nginx comparison gave ~75k/117k RPS at 16/64 clients, but later
nginx samples were ~50k/93k on the shared host. Do not compute a definitive
"% nginx" from these non-isolated runs. The 100k/80% goals remain **OPEN**.
## Source modifications
| Location | Change | Preserved behavior / caveat |
|---|---|---|
| `src/balance/model.rs` | New vhost default `max_idle_per_host`: **4 -> 64** | Configured values **1..64** remain validated; existing explicitly saved `4` stays `4`. Disabled keepalive, 30 s idle timeout, 25k-request and 3,600 s recycling rules are unchanged. Increased idle sockets use more file descriptors and memory. |
| `src/admin/web/vhosts/editor.js` | Match the new `64` default when the field is absent | Existing values, limits and all editor settings remain unchanged. |
| `src/proxy/body.rs` | At `Exchange::drop`, use exclusive `Mutex::get_mut()` rather than lock/unlock; move owned `route`, `client_ip`, `country`, `upstream_error` into final observation where possible | Exactly the same log fields, analytics sample, counters, permit lifetime, errors and websocket/H3 fields. Analytics still gets a clone when a vhost sample is queued; history still receives the original. |
| `src/proxy/headers.rs` | Skip reserved cookie sanitization immediately if the corresponding `Cookie`/`Set-Cookie` header is absent | Full sanitization, admin-cookie separation and restoration restrictions still apply when the header is present. |
| `src/balance/tests.rs`, `src/proxy/body/tests.rs`, `src/proxy/headers/tests.rs` | Add compatibility, completion and cookie regression assertions | These Rust tests **require** a compiler before they count as run. |
| `scripts/tests/*hotpath*` | Update source-only guards for the new **bounded** default | Static assertions do not prove functional or runtime performance equivalence. |
**Persistence note:** old vhosts may store `keepalive.max_idle_per_host=4`
explicitly; the new code default does not rewrite them. To apply the measured
configuration improvement before rebuilding, change that option to `64` in the
relevant vhost and verify actual idle socket/FD usage. Where hundreds of origins
share a host, test `16` or `32` if the FD/memory trade-off is preferable.
## Bottlenecks to profile next, not silently disable
1. `src/cache/dashboard.rs::Cache::record`: obtains a **shared cache-store
mutex on every outcome**, including `BYPASS` when cache is disabled through
`src/proxy/forward/cache_lookup.rs`. This may compete with HOT cache
operations and admin dashboard reads; profile `pthread_mutex`/futex and
lock hold times. Candidate: separate bounded, sharded domain/reason
counters from the HOT object store while preserving exact dashboard totals,
reason labels and reset semantics.
2. `src/logging/format.rs::Logs::push`: per-request bounded history
`try_lock`, potentially causing recorded log drops under high concurrency.
Preserve *existing* drops/accounting, live subscribers and external sinks.
Candidate: worker-local bounded batches or sharded history with deterministic
bounded merge; validate log order, recent history, retention and drops.
3. `src/proxy/body.rs::Exchange::drop` + `src/traffic.rs::Analytics::push`:
request-sized log strings, sampling queue sends, atomics and lifecycle
observations. Profile allocations and CPU per completed request. Candidate:
compact shared immutable identity fields or conditional cloning solely
where consumers require ownership, without removing logs or analytics.
4. `src/proxy/forward/route_cache.rs`: request-target path copies,
header policy and several route/cache eligibility steps. Measure with
sampling `perf record -g` before extracting additional fast paths; preserve
query handling, canonical host, ACL, authentication, retries and WebSockets.
5. `src/upstream/*`: profile H1 pool misses, TCP connect rate, Hyper
connection reuse and generation swaps. Increasing recycle thresholds or
changing HTTP/2/H2C modes without an isolated performance case is not
recommended.
6. **Separate WWW priority:** benchmark short static objects independently;
existing `sendfile: auto` applies to eligible cleartext H1 objects >=64 KiB
and cannot accelerate 1 KiB bodies.
No lock removal, tracing/log disabling, caching-policy weakening, bypass of
security checks, or transformation of complete bodies into buffered copies is
proposed without a test proving semantic equivalence.
## Acceptance gate on a machine with Rust toolchain and isolated load generator
```bash
./dev.sh doctor
./dev.sh check
CARGO_INCREMENTAL=0 ./dev.sh test
CARGO_INCREMENTAL=0 cargo build --release --locked
python3 scripts/performance/regression.py \
--binary after="$(realpath target/release/rust-proxy)" \
--out /tmp/rustproxy034-1kb-regression
python3 scripts/performance/lb_regression.py \
--binary before=/absolute/path/to/old-rust-proxy \
--binary after="$(realpath target/release/rust-proxy)" \
--out /tmp/rustproxy034-1kb-lb
python3 scripts/performance/transport_regression.py \
--binary before=/absolute/path/to/old-rust-proxy \
--binary after="$(realpath target/release/rust-proxy)" \
--out /tmp/rustproxy034-1kb-transport
python3 scripts/performance/benchmark.py \
--binary before=/absolute/path/to/old-rust-proxy \
--binary after="$(realpath target/release/rust-proxy)" \
--nginx /usr/sbin/nginx --wrk /absolute/path/to/wrk \
--out /tmp/rustproxy034-1kb-benchmark \
--modes proxy --sizes small --concurrency 16,64,128 \
--duration 30 --warmup 5 --repeats 7
```
Use the **same explicit** `max_idle_per_host=64` and all other vhost settings
for the *source A/B* test. Run 4 vs 64 as a separate *configuration A/B* test;
never attribute a settings difference to code. Verify full content checksums,
HTTP 2xx/4xx/5xx, p50/p95/p99, drop counters, backend connections,
FD/RSS trends, CPU/request and packet/TCP retransmit rates. Test H1/H2/H3,
TLS, WebSockets, sticky/rate/ACL, cache MISS/HIT/BYPASS, admin sessions, active
failover, cancellation, idle timeouts and a sustained memory/connection soak.
Profile the freshly **built** executable separately from timed RPS runs.
Until compilation and those gates pass, this is a **development source-only
candidate**, not a claim of reaching 100k RPS or 80% nginx.
-88
View File
@@ -1,88 +0,0 @@
# RustProxy 0.34.0 DEV — 1,040-byte response, OPT3
## Scope
Primary: uncached reverse proxy, HTTP/1.1 keep-alive, 1,040-byte body,
no compression, ACL/security/logging/analytics/counters fully operational.
Goal: ~100,000 RPS and >=80% of nginx on the **same isolated host**.
Secondary: local Web Server/static files, measured independently.
**Version remains exactly 0.34.0**; existing configuration and defaults are
unchanged; no functions, checks or logs are disabled.
## New code optimizations (relative to OPT2-FIX1)
1. `src/acl.rs` / `src/proxy/cache_helpers.rs`: borrow ordinary validated
routing paths using `Cow<str>` during immutable route selection. Calls
with escaped (`%xx`) or repeated separators still use the original strict
allocator/decoder. Invalid control bytes, dot segments, UTF-8 and ambiguous
encodings remain rejected; the original wire path is not rewritten.
2. `src/proxy/forward_entry.rs` plus cache/Web Service call sites: retain the
original `http::Uri` value so normal paths can remain borrowed during the
entire mutable forwarding/rewrite operation. Cache-refresh tasks that need
ownership still allocate explicitly and hold their own `String`.
3. `src/forwarding.rs`: a **direct, untrusted** TCP peer now obtains the same
seven forwarding headers without allocating hop-chain `Vec` structures.
Trust chains and the configured `trust_forwarded_proto` logic use the
unchanged full parser. IPv4/IPv6 RFC 7239 formatting and true listener
port/scheme are preserved; untrusted spoofed values are still ignored.
4. `src/proxy/forward_entry.rs`: when effective client IP is identical to
the transport peer, reuse the IP stored by observation. No redundant
`IpAddr::to_string()` and no per-exchange mutex write. Different/trusted or
IPv4-mapped addresses are still stored in canonical form.
5. `src/proxy/headers.rs`: plain single-valued `Connection: keep-alive`
avoids the temporary `Vec<HeaderName>` while all standard hop-by-hop fields
are removed; comma-separated, repeated and complex Connection fields keep
the original removal parser to prevent nominated-header leaks.
6. `src/proxy/request_id.rs`: preserve SHA-256, CSPRNG, per-process sequence,
unpredictable 64-char hexadecimal IDs and wire headers, but write the hex
digits into one preallocated byte buffer before UTF-8 verification.
7. `src/cache/stats.rs`: select the most frequent `cache_disabled` reason
counter directly. The same exact atomic totals, reason labels, 4096-domain
limit and reset behavior are retained.
The changes avoid repeated work in common cases; no numerical throughput
benefit has been measured for this new source. In particular, no cache, log,
metrics, retry or auth feature was switched off for a synthetic benchmark.
## Evidence and known limits
- Provided newly compiled *OPT2-FIX1* executable SHA-256:
`6037e98d059672b716f2e1411976bf305cac927901a6f6662d8b703829ff8196`.
- PASS **89** HTTP proxy/policy/cache black-box assertions, zero failures,
testing that supplied executable, **not** the new OPT3 code.
- Source checks, migration/upgrade checks and ZIP checks are separate
static/offline verification; they are not equivalent to cargo/test/live.
- A single interrupted diagnostic A/B sample at c=64, 3 s, same idle=64,
gave older binary 33,752 RPS and OPT2-FIX1 binary 33,556 RPS, zero failed
requests; do **not** infer a true performance difference from one sample.
- NOT RUN here: `cargo check`, `cargo test`, rebuilt OPT3 integration tests,
post-build A/B RPS, long soaking, real TLS/H2/H3 compatibility and 80%-of-nginx
acceptance. No Rust toolchain is available in this editing container.
- Resource/latency trade-offs (including log/analytics drops, RSS/FDs) require
the same measured and pinned server/peer/generator CPU sets for before/after.
## Rebuild and measure
From the unpacked `RustProxy/` directory:
```bash
./dev.sh check
CARGO_INCREMENTAL=0 ./dev.sh test
bash dev.sh
python3 scripts/performance/regression.py \
--binary new=/ABSOLUTE/PATH/NEW/rust-proxy --out /tmp/rustproxy-opt3-http
python3 scripts/performance/ab_1kb_dev.py \
--binary before=/ABSOLUTE/PATH/OPT2FIX1/rust-proxy \
--binary after=/ABSOLUTE/PATH/OPT3/rust-proxy \
--nginx /usr/sbin/nginx --ab /usr/bin/ab \
--out /tmp/rustproxy-opt3-1kb --concurrency 16,64 \
--duration 5 --repeats 3 --workers 4 --max-idle 64
```
For an acceptance-grade evaluation, run the `scripts/performance/benchmark.py`
paired before/after/nginx test with separate pinned CPU sets, a verified origin
and 30-second runs repeated 7 times. Report median and p95/p99 latency,
errors, CPU, RSS, file descriptors, dropped log/analytics events, and all HTTP
status/body hashes. Retest TLS/H2/H3/WebSocket, auth, cache semantics and
connection/backpressure limits. Compare the **rebuilt** OPT3 binary against
OPT2-FIX1 at exactly the same listener/backend settings and compiler profile.
-97
View File
@@ -1,97 +0,0 @@
# RustProxy 0.34.0 DEV / OPT4: HTTP/1.1 small-body follow-up
## Objective and validity
Primary goal: 1,040-byte reverse-proxy response, HTTP/1.1 keep-alive,
cache disabled; target about 100k RPS and at least 80% of nginx on the **same
isolated host**. Secondary goal: local static Web Server, benchmarked separately.
No version bump or feature, metric, logging, ACL, authentication, TLS,
HTTP/2/3, stream, retry or cache-policy bypass is introduced.
The **provided and compiled OPT3 binary** (SHA-256
`2efe273340f9cf92c5900c85c0bd1b775c87a9d1eaff1a10ec7635d268b080b1`)
passed 151 black-box HTTP assertions: 0 failures and 1 optional Zstandard
case skipped. This is evidence for the **input** binary, not for the new
OPT4 sources. OPT4 still requires a native Rust build, cargo tests and a
post-build benchmark. The diagnostic same-host CPU shares resources and is
not an acceptance benchmark.
## Code changes relative to OPT3
1. `src/proxy/forward/route_cache.rs`: store normal public path/query as
`Cow<'_, str>` referencing the independently retained, immutable original
URI. Without a path rewrite both public/origin suffixes stay borrowed;
strict encoded/rewrite handling remains an owned validated string.
Cache refresh tasks explicitly call `.to_string()` only when they actually
must retain an owned target across spawned tasks.
2. `src/proxy/upstream_helpers.rs`: skip allocating a request-body metering
adapter **only** when `Body::is_end_stream()` establishes that it contains
no DATA frames or trailers. Every nonempty/streaming request retains the
original metering, timeout, backend lease accounting and cancellation.
The response adapter still retains the exchange/inflight permit and handles
completion/response bytes for empty requests.
3. `src/proxy/types.rs`, `src/proxy/forward/upstream.rs`: consume owned
`http::Uri` when preparing the upstream target. HTTP/1.1 returns it directly;
H2 takes the same URI into parts for strict authority rewriting. Remove an
unnecessary intermediate URI clone; replay still builds independent URIs.
4. `src/forwarding.rs`: reuse the formatted IPv4 peer address when creating
RFC 7239 `Forwarded` as well as X-Real-IP/X-Forwarded-For. Preserve quoted
IPv6 bracket and proxy-chain logic for trusted and untrusted peers.
5. `src/proxy/upstream_helpers.rs`: fast-path byte-safe truncation of short
or ASCII host/path log values, keeping the original max-*characters*
semantics for non-ASCII UTF-8.
6. `src/proxy/headers.rs`: avoid nominated-field token parsing for absent,
lone `keep-alive`, or lone `close` Connection values. A `close` field on
the wire still forces the general parser, so a nominated `Close` header
is removed exactly as before. Multi-value, comma lists and malformed tokens
retain the previous defensive parser.
7. `src/proxy/headers.rs`: similarly avoid the generic token parser when
checking HTTP upgrade on plain keep-alive, close, or absent Connection.
Complex multi-valued requests and explicit Upgrade remain unchanged.
## Tests and limits
- New Rust cases validate empty vs nonempty request-body permits and bytes,
H1 URI/authority/query, ASCII/Unicode truncation, and Connection variants
including a literal nominated `Close` field. Existing regression coverage
still includes sticky backends, cache, ACL and trusted forwarded identity.
- Source, SQLite migration and upgrade tests are necessary but **not** Rust
compilation or performance evidence. The compile check must run before use.
- Run `./dev.sh check && CARGO_INCREMENTAL=0 ./dev.sh test`, followed by
the native black-box acceptance suite (proxy, policy, cache, transform,
compression, static serving), plus TLS/H2/H3 and websocket tests.
## Measurements
An initial diagnostic same-host `ab` sample at concurrency 16 gave
OPT2 33,042.55 RPS and compiled OPT3 36,962.11 RPS, without HTTP errors;
this single sample is not proof of a reliable uplift. A separate complete
12-sample run (two 3-second repetitions each at 16/64 clients) produced:
| Concurrency | OPT2 median RPS | OPT3 median RPS | nginx median RPS | OPT3/nginx |
| --- | ---: | ---: | ---: | ---: |
| 16 | 21,608 | 29,906 | 92,586 | 32.3% |
| 64 | 38,060 | 34,781 | 123,809 | 28.1% |
All 12 samples had valid 200 responses and correct 1040-byte body checks.
OPT3 was slower than OPT2 at concurrency 64; the variability is too large to
claim a sustained uplift or regression based on two short same-host samples.
Raw evidence includes run order, response checksum and nginx comparator.
No result here measures OPT4, which was not built in this environment.
## Next evaluation commands
```bash
./dev.sh check
CARGO_INCREMENTAL=0 ./dev.sh test
bash dev.sh
python3 scripts/performance/regression.py --binary opt4=/absolute/path/target/debug/rust-proxy --out /tmp/opt4-http
python3 scripts/performance/ab_1kb_dev.py --binary opt3=/absolute/path/opt3/rust-proxy --binary opt4=/absolute/path/target/debug/rust-proxy --nginx /usr/sbin/nginx --ab /usr/bin/ab --out /tmp/opt4-ab --duration 5 --repeats 3 --concurrency 16,64 --workers 4 --max-idle 64
```
For authoritative acceptance: pin client/server/origin to nonoverlapping CPU
sets, align build profile and all flags, compare 30s x7 runs, record median,
p95/p99, correct 1040 B response checksum and status, CPU, RSS, FDs, cache
stats, analytic/sink queue drops, errors, and both HTTP/1.1 and optional
HTTP/2 endpoints. Reject changes on new 4xx/5xx, body mismatches, regressions
of access controls or material memory/fd increases.
-93
View File
@@ -1,93 +0,0 @@
# RustProxy 0.34.0 DEV — OPT5, 1,040-byte reverse-proxy optimization
## Acceptance boundary
The source package preserves **0.34.0** and all existing configurable services,
logs, dashboards, cache/ACL/auth policies, streaming, TLS/H2/H3/WebSocket,
failover, retry and bounded-resource behavior. OPT5 is a **source-only** candidate:
no Rust compiler is available in the editing environment. The new code's
compilation, Rust unit tests, binary throughput and full-protocol interoperability
are unverified until the developer builds it. The compiled OPT4 executable is
the known test input, **not** a benchmark of OPT5.
## Compiled OPT4 input verification
Input: `/mnt/data/rust-proxy(4)`, SHA-256
`1c4c701f11b57b1293020061c8af21d5a9df0c86f03290018a90edc007667b24`.
- The real HTTP regression runner completed **151 passing**, **0 failing** cases;
**1 skipped** because Python `zstandard` is not installed. Protocol tests for
TLS/H2/H3 are a separate acceptance gate and were **not** passed by this run.
- Same-host ApacheBench HTTP/1.1 keep-alive, 1,040-byte body verified by exact
SHA-256, cache OFF, 4 Tokio workers, 64 idle HTTP1 upstream connections,
3-second warm-and-measure samples, two repeats per variant/concurrency:
| RPS, median of 2 samples | 16 clients | 64 clients |
| --- | ---: | ---: |
| OPT3 | 37,017 | 40,668 |
| OPT4 | 39,054 | 36,425 |
| nginx | 99,808 | 110,783 |
| OPT4 vs OPT3 | +5.5% | -10.4% |
| OPT4 / nginx | 39.1% | 32.9% |
There were zero HTTP failures and the response body was verified before/after
measurements. Shared CPU and only two short samples are diagnostic, NOT a stable
speedup or production proof. The 100k RPS and 80%-of-nginx goals remain unmet.
## OPT5 changes (8 coherent hot-path optimizations)
1. `src/proxy/request_id.rs`: eliminate per-request SHA-256. Keep the existing
OS-seeded/reseeded thread-local CSPRNG and 256-bit ID; XOR a monotonic
sequence into the first 8 nonce bytes. Output remains uniformly random,
64 lowercase hex characters and the protected `X-Request-ID` correlation
remains. Random IDs continue to be unique with overwhelming probability.
2. `src/forwarding.rs` and `src/proxy/forward_entry.rs`: only construct
`Context.original_url` for a frontend with configured Tinyauth. This is the
*only* runtime consumer of that absolute URL. Public `context(...)` still
constructs it by default. Trusted/untrusted forwarding and all validation
still execute identically; IPv4/IPv6 and `x-original-url` for Tinyauth remain.
3. `src/balance/lease_runtime.rs`: borrow `"ok"` for successful backend health
observations instead of allocating a `String`. 5xx failures still record the
exact original `status=NNN` detail and slow/passive health counters are kept.
4. `src/traffic.rs`: receive 64 samples at a time (Tokio `recv_many`) and apply
them under a single dashboard-state mutex. Order within the existing bounded
2,048-sample queue is preserved; waiting is outside the mutex and producer
backpressure and drop accounting are unchanged.
5. `src/traffic.rs`: reuse map entries with borrowed vhost names. New keys are
cloned only when a domain first appears, preserving the max 64-vhost bound.
6. `src/traffic.rs`: classify status 1xx–5xx using static labels; preserve
unknown extension status labels such as `7xx`.
7. `src/traffic.rs`: replace per-sample lowercase allocation for User-Agent
family detection with ASCII-insensitive byte-window comparisons, and avoid
copying all TopTable maps when displaying a single vhost.
8. `src/proxy/upstream_helpers.rs`: skip resolving inherited/vhost/web-service
compression settings when no encoding is accepted even under the global
codec ceiling (for example `Accept-Encoding: identity`). Existing compression
handling is retained for every accepted codec and request method.
The caller's front-end policies remain evaluated on every request. No log,
metric, access control or authentication feature was disabled or made optional.
## Verification and outstanding gates
Static source, SQL migration and managed-upgrade tests are executed and their
outcomes supplied in the external evidence archive. New Rust test fixtures cover
request IDs, forwarding/trusted IPv6, compression, healthy/5xx backend detail,
analytics batching, top classes, country-independent User-Agent matching,
reset and the bounded queue.
To verify the new build on the developer's system:
```bash
./dev.sh check
CARGO_INCREMENTAL=0 ./dev.sh test
./dev.sh build
```
After building, repeat the actual HTTP regression, LB regression and TLS/H2/H3
transport tests and collect at least seven 30-second CPU-isolated, warmed,
interleaved OPT4/OPT5/nginx measurements with `wrk`, p95/p99 and CPU/RSS.
Treat the non-tested binaries, short shared-host tests and simulated Python
checks as distinct types of evidence. Measure the secondary Web Server path
separately. Do not alter the version or silently relax cache, auth or timeout
policies to reach a RPS target.
-84
View File
@@ -1,84 +0,0 @@
# RustProxy 0.34.0 — reverse-proxy hot-path candidate
## What was changed
1. **Routes:** `Snapshot.routes` and `fallbacks` now hold
`Arc<CompiledRoute>` handles. The same compiled location is shared across
hostname aliases. `observe` resolves vhost/location once from the accepted
listener/TLS-state and stores a request-local handle. `forward_request`
consumes the same immutable snapshot/route; internal error fallbacks still
resolve the explicitly configured named location independently.
2. **Host matching:** wildcard candidate strings are generated lazily only
after an exact host failed, without changing longest-specific-suffix order.
3. **LB:** single-backend non-cookie pools use a direct admitted lease without
temporary candidate vectors or scheduler mutex. Multi-backend weighted
round-robin retains its ordering and mutex; least-connection/traffic,
adaptive, IP hash and cookie-affinity retain their behavior. IP hash avoids
repeated canonicalization/temporary `format!` strings for each candidate.
4. **Health:** a `healthy_cached` atomic allows checking healthy backends
without the health mutex; all state transitions remain inside the existing
state machine, including passive quarantine and half-open recovery.
Resolved active/passive per-member policies are held in shared immutable
`Arc` handles so a new request lease does not deep-clone health paths,
headers or optional host strings.
5. **Fallback/retry:** request body+HeaderMap replay templates are made only
for configured error fallbacks. Retry headers are cloned only for eligible
safe replay requests where another member or H2 graceful close makes retry
possible; writes, streaming bodies and WebSockets are not replayed.
6. **Telemetry:** three per-request mutex-backed scalar tags are now atomic;
their original event strings and `Option<bool>` log fields are decoded at
exchange completion. Request ID, method, host and path are moved into the
terminal log record instead of deep-cloned in Drop; the traffic sample
still receives its own required copies. Logs avoid a broadcast clone if
there are no subscribers, preserving history, sink and counters.
## Preserved boundaries
All vhost, ACL, GeoIP, Basic Auth, Tinyauth, WebSocket, retry, cache,
transform, compression, log, metrics, `sendfile`, PHP-FPM, HTTPS/H2/H3 and
admin panel configuration paths are intended to remain compatible. Defaults
and SQLite schema are unchanged; no new performance toggle was added. The
optimization reuses route objects within one immutable snapshot but does not
allow stale snapshot cache fills (existing revision ticket barrier remains).
Sticky cookies deliberately retain the general selection path, and upstream
request-count recycling is unchanged (default 25,000 per generation).
## Locally executed checks vs required checks
- **Executed locally:** scoped-cache/sendfile/hot-path *source-only* guards,
Python syntax, SQLite migration and upgrade preservation tests, selected JS
panel scripts and simulated editor tests. These do **not** prove Rust builds.
- **Prepared, NOT executed:** Rust unit tests for shared routing, single-member
LB capacity/health, passive ejection/recovery, atomic log fields, retained
sendfile, cache and protocol behavior.
- **Unmeasured:** 0.34.0 requests/sec, latency, CPU cost, memory and nginx gap.
Do not infer a throughput gain from fewer source-code locks/allocations.
## Build and acceptance
Run on the intended Linux builder with exactly the same compiler, dependencies,
release flags and workload as the 0.33.8 reference:
```bash
./dev.sh doctor
./dev.sh check
CARGO_INCREMENTAL=0 ./dev.sh test
CARGO_INCREMENTAL=0 cargo build --release --locked
python3 scripts/tests/proxy_hotpath_034_static_test.py
python3 scripts/tests/cache_scope_034_static_test.py
python3 scripts/tests/sendfile_034_static_test.py
python3 scripts/tests/dashboard_presets_sql_test.py
python3 scripts/tests/upgrade_performance_test.py
```
Next run `scripts/performance/regression.py`, `lb_regression.py`,
`transport_regression.py` and `concurrency_regression.py` against both binary
versions. After functional acceptance, run the paired
`scripts/performance/benchmark.py` suite against the unmodified baseline,
this candidate and nginx; use the instructions in
`scripts/performance/README.md` including separate CPU affinities.
Measure RPS/p95/p99, CPU microseconds per request, RSS, FDs, pool rotations,
health transitions, cache HIT ratio and logger drops. For each payload report
`(0.34.0 RPS / 0.33.8 RPS - 1)*100` and `0.34.0 RPS/nginx RPS*100` from real
medians. A source-only revision is **not production-accepted**.
-78
View File
@@ -1,78 +0,0 @@
# RustProxy 0.34.0: hot-path throughput revision
**Status: uncompiled source candidate.** This revision does not change the
application version, public API, dashboard controls, cache policy or tuning
defaults. It must not be labelled production-ready until the new Rust binary
passes compile, unit tests, live regressions and paired performance comparisons.
## Code changes and intended cost reductions
| Focus | Change | Kept behavior / risk to verify |
|---|---|---|
| Per-request identity | Draw a 256-bit nonce from the OS-seeded/reseeded thread-local CSPRNG instead of asking `getrandom` on every request. Preserve SHA-256 of nonce+monotonic sequence and the existing 64-hex ID format. | IDs remain non-predictable. Check ID uniqueness and raw upstream headers. |
| Upstream HTTP request target | Compile the validated scheme/authority `http::Uri` once with each immutable backend; parse only the request path+query and rebuild using typed `Uri::from_parts`. | Never accept a client-supplied authority, bypass DNS/address policies or change SNI. Test H1, H2, IPv6, query and percent-encoding. |
| Load balancing | Do not clone the member ID string for an existing smooth-WRR scoreboard entry. Continue the previously introduced no-mutex single-member non-cookie path. | Sticky-cookie modes, capacity limits, retry exclusions, health and drain remain unchanged. |
| Active health | Use already-compiled `Arc<HealthCheck>` when dispatching probes, avoiding a clone of its path/headers for each scheduled probe. | Same 250-ms supervisor, timeout, enabled/claim gate and health state transitions. |
| Per-frame streaming | Capture the leased backend once at first streamed request DATA (response already captured at wrapper construction). Do not take `Exchange.backend` mutex on every upload chunk. | Request/response byte counters, mid-stream failures, idle timeouts and lease lifetime remain active. |
| Pool recycling | Preserve exact request-count and elapsed-time recycling checks per request, with the existing independent 250-ms maintenance cycle. The pool factory/connection policy is unchanged. | Exact lifecycle semantics are more important than saving one cheap clock read; tune idle/age limits only after profiling. |
| Log history | Move completed `LogEntry` directly into bounded history when there are no live viewers or enabled external sinks, instead of deeply cloning its strings. Continue cloning and delivering to live viewers/sinks when enabled. | Same JSON fields, history size, drop counters, file/Loki/Logstash forwarding. |
| Route/HTTP instrumentation | Remove redundant route-ID mutex/write in forwarding and collapse H1/H2 version tallying into one branch. | Route is recorded once before forwarding; H1/H2 counters remain. |
These historical changes build on the previous 0.34.0 route-Arc, single-backend LB,
conditional retry/fallback-header cloning, atomic exchange flags and cache HOT
LRU changes. There are no new UI toggles. This **earlier** revision retained `max_idle_per_host`
4 and `pool_recycle_requests` 25,000. The **subsequent DEV 1 KiB revision**
changes only the default idle limit to 64, based on live configuration A/B
with the old binary (not source A/B); see `docs/PROXY_1KB_HOTPATH_DEV_0.34.0.md`. `sendfile: auto` remains opt-in and applies only to eligible Linux
plaintext H1 static WWW responses, not reverse proxy or TLS/H2/H3.
## Cache transform/query policy
An enabled `bypass_query` is a deliberate policy: `/theme.css?v=...` is a
`BYPASS` even when the origin sends `Cache-Control: public`. Turning the bypass
off permits a transformed CSS object to follow `MISS -> HIT` and appear in the
cache dashboard. An existing transformed HOT row with hit count and TTL is
positive evidence of this. This throughput revision does not modify the
cache eligibility logic or discard Age/ETag/Cache-Control headers.
## Verification already possible here
- **Old/reproducible reference executable only:** 110 passed, 0 failed, 1 skipped
(`compression.codec.zstd` skipped because the optional decoder was missing),
from live proxy/policy/cache/transform loopback regression. This does **not**
exercise the newly modified Rust source.
- **Old/reproducible reference executable only:** query bypass enabled gives
`BYPASS -> BYPASS`, disabled gives `MISS -> HIT` and a transformed representation;
the older Age regression is also reproducible on this old executable.
- Source-only invariants: `scripts/tests/rps_hotpath_034_static_test.py` plus
existing route/cache/sendfile checks. SQL migration and upgrade scripts pass.
- **Not performed:** Rust compilation or newly added Rust unit tests, live
behavior of the optimized binary, HTTP/2 live TLS, comparative RPS, CPU
profiling and long-duration memory/health soak. The environment does not
include `cargo`, `rustc` or `wrk`. No post-change RPS gain is claimed.
## Acceptance commands on a Rust-capable Linux host
```bash
./dev.sh check
CARGO_INCREMENTAL=0 ./dev.sh test
CARGO_INCREMENTAL=0 cargo build --release --locked
python3 scripts/tests/rps_hotpath_034_static_test.py
python3 scripts/tests/cache_age_transform_034_live.py \
--binary "$(realpath target/release/rust-proxy)" \
--output /tmp/rustproxy034-cache-after
python3 scripts/performance/regression.py \
--binary optimized="$(realpath target/release/rust-proxy)" \
--out /tmp/rustproxy034-http-regression
```
Run `scripts/performance/benchmark.py` from the documented reproducible
before/after/nginx command in `scripts/performance/README.md`. Compare the
unchanged old 0.34.0 binary with the newly compiled source, not a baseline
with different feature flags. Record RPS at 1,040 B, 64 KiB, 1 MiB, p95/p99,
CPU/request, RSS, connection counts, errors and exact response-body hashes.
Use `perf record -g -p <proxy-pid>` during a stable request interval and inspect
`perf report --stdio` to identify the next true bottleneck.
Do not interpret Python origin self-tests, source inspections, or speculative
Amdahl models as measured RustProxy throughput.
+2 -4
View File
@@ -7,7 +7,6 @@ This is the canonical index for the current project documentation. The authentic
- [Project README](../README.md) - product overview, installation entry points and current release notes.
- [ROADMAP](../ROADMAP.md) - planned and completed release scope.
- [CHANGELOG](../CHANGELOG.md) - user/operator-visible changes by release.
- [TODO](TODO.md) - open work only; completed work is not retained as TODO.
## Functional documentation
@@ -42,7 +41,6 @@ This is the canonical index for the current project documentation. The authentic
- [MOCK_SERVER](MOCK_SERVER.md) - local Python mock origin used by development/integration checks.
- [MOCK_AUTH](MOCK_AUTH.md) - local fake Tinyauth peer for development only.
- [MEMORY_STRESS](MEMORY_STRESS.md) - sustained-memory audit, offline load simulations, real RustProxy staging stress and RSS measurement procedure.
- [TEST_REPORT_0.34.0](TEST_REPORT_0.34.0.md) - 0.34.0 source-stage test evidence, simulated cache retention and required acceptance gates.
## Technical documentation
@@ -61,7 +59,7 @@ Completed roadmap history is retained separately as [ROADMAP_HISTORY](ROADMAP_HI
### Retained audits and historical context
- [SECURITY_AUDIT](SECURITY_AUDIT.md) and [SECURITY_AUDIT_0.31.0](SECURITY_AUDIT_0.31.0.md) - current security audit material and the 0.31.0 audit.
- [SECURITY_AUDIT](SECURITY_AUDIT.md) - security audit material.
- [CHANGELOG_TECHNICAL_ARCHIVE](CHANGELOG_TECHNICAL_ARCHIVE.md) and [ROADMAP_HISTORY](ROADMAP_HISTORY.md) - retained long-form technical history and completed milestones.
- [ACME_RFC2136](ACME_RFC2136.md) - historical DNS provider details.
- [INTEGRATIONS_040](INTEGRATIONS_040.md) and [MANAGEMENT](MANAGEMENT.md) - retained design and integration notes.
@@ -72,7 +70,7 @@ The old per-version implementation notes and audits removed from this trimmed so
## Maintenance rules
- Put user/operator-visible release changes in `CHANGELOG.md`.
- Put future release scope in `ROADMAP.md` and unresolved work in `docs/TODO.md`.
- Put future release scope and unresolved work in `ROADMAP.md`.
- Keep detailed implementation evidence in technical documents or version audits, not in CHANGELOG.
- When functionality changes, update the functional document, this index, Reader coverage and cross-links in the same change.
- Remove obsolete or duplicated documents when they no longer provide historical or technical value; retain useful audits with an explicit historical label.
-93
View File
@@ -1,93 +0,0 @@
# RustProxy 0.34.0 — poprawki cache, transformacji i kontrola RPS
Status: **zmodyfikowane źródła, niekompilowane lokalnie** (brak `cargo` i `rustc`). Numer wersji: 0.34.0.
## Co odtworzono na binarce użytkownika
Uruchomiono binarkę Linux ELF w izolowanej konfiguracji z lokalnym origin HTTP, z włączonym cache, transformacją CSS, `max_ttl_seconds=86400`, diagnostyką i API administracyjnym. HTTP/1.1 localhost, pełne GET, brak kontaktu z witryną produkcyjną.
| Odpowiedź | Pierwszy GET | Drugi GET | Liczba pobrań z origin |
|---|---|---|---|
| `/normal.css` | MISS | HIT | 1 |
| `/aged.css` | MISS | MISS | 2 |
| `/br.css` | MISS | HIT | 1 |
| `/aged-br.css` | MISS | MISS | 2 |
| `/private.css` | MISS | MISS | 2 |
| `/no-store.css` | MISS | MISS | 2 |
Zwykły CSS i skompresowany Brotli dostają HIT. Warianty z `Age: 1553483` mają `MISS → MISS` mimo `Cache-Control: public, max-age=2592000`. Przypadki `private` / `no-store` słusznie nie trafiają do cache. Zmiana keep-alive vhosta nie usuwała poprawnie zachowanego obiektu. `expose_only_hits=true` celowo ukrywa nagłówki na MISS.
## Główna przyczyna i poprawka
W `src/cache/policy/response.rs` obowiązywał wzór `ttl = min(origin_max_age, local_max_ttl)` oraz `remaining = ttl - corrected_age`. To jest sprzeczne z zadaniem lokalnego limitu: świeżość w origin nadal trwa, ale CDN `Age > local_max_ttl` dawał sztuczne 0 sekund lokalnej świeżości.
Poprawiony wzór:
```text
corrected_initial_age = max(apparent_age, upstream_age + response_delay)
remaining_origin_freshness = max(0, origin_max_age - corrected_initial_age)
remaining_local_freshness = min(remaining_origin_freshness, local_max_ttl)
entry_fresh_until_age = corrected_initial_age + remaining_local_freshness
```
Przykład z przesłanych nagłówków: wiek upstream 1 553 483 s = prawie 18 dni. Pierwotna świeżość 2 592 000 s (30 dni), więc pozostało około 12 dni w origin. Po naprawie RustProxy będzie mógł przechować świeżą odpowiedź przez lokalny limit (domyślnie 86 400 s = 1 dzień). Nagłówek Age nadal będzie około 1,55 mln sekund i będzie rósł na HIT — to poprawne.
`Age` nie wynika z `Last-Modified: 2016`. Najczęściej jest przekazany przez upstream/CDN, a stara data `Date` też mogłaby podnieść apparent_age. Rozróżnienie: `Age` = HTTP wiek całkowity; `resident_age_seconds` = pobyt w HOT/COLD.
## HOT RPS i panel
1. HOT HIT nie wyjmuje już i nie wkłada od nowa wpisu oraz indeksu `Vary`, tylko aktualizuje licznik HIT i kolejność LRU w miejscu. Zmiana ma zmniejszyć alokacje i krytyczną sekcję; **zysk RPS jeszcze niezmierzony**.
2. Panel Cache ma osobne kolumny **Age** i **Czas w RustProxy**. API `dashboard.objects[]` dodaje `resident_age_seconds` także dla COLD. Pozostałe nazwy pól nie zostały zmienione.
3. Licznik `x-cache-hits` dotyczy konkretnego aktualnego wpisu. Gdy obiekt zostanie wyparte z HOT (limit wpisów/pamięć), usunięty lub zastąpiony po MISS, licznik restartuje. W próbce użytkownika TTL wynosił około 604 tys. s (7 dni), więc późniejszy MISS po ~4 minutach **nie wynikał z normalnego wygaśnięcia TTL**. Konkretnej przyczyny epizodu nie można ustalić bez trendów `evictions`, `purges`, `hot_demotions`, `cold_hits`, kluczy/Vary oraz zmian konfiguracji.
4. Brak `x-cache` na MISS jest oczekiwany, gdy włączono **nagłówki diagnostyczne tylko na HIT**. Brak wpisu w API może mieć inne przyczyny (np. cache-control `private`, `no-store`, brak polityki origin, limit obiektu) i nie należy ich obchodzić na siłę.
5. Nie zmieniano uprawnień, `private`/`no-store`, kluczy Vary, TTL po stronie origin ani zasad selektywnej invalidacji.
## Wynik symulacji TTL
Wykonano **100 000 deterministycznych scenariuszy**. Dla każdego sprawdzono: po naprawie lokalna świeżość nie przekracza `local_max_ttl` ani świeżości pozostałej w origin.
| Scenariusz | Stary pozostały TTL | Po poprawce (symulacja) |
|---|---:|---:|
| Age = 1 553 483, local cap 1 dzień | 0 s | 86 400 s |
| Age = 1 553 483, local cap 7 dni | 0 s | 604 800 s |
| Origin kończy świeżość za 10 s | 0 s | 10 s |
| Origin jest już nieświeży | 0 s | 0 s |
W symulacji 33 387 / 100 000 losowych przypadków stara polityka błędnie dawała zero zamiast dodatniego TTL. **To nie jest procent regresji w realnym ruchu**, a wynik losowania parametrów testowych.
## Nginx — rzeczywiste pomiary starej binarki
Archiwalne wyniki samej binarki `rust-proxy 0.34.0` na tym środowisku (Linux, 4 vCPU, HTTP/1.1 loopback, ApacheBench, 64 połączenia, cache OFF, 3 powtórzenia), z nginx 1.26.3. Brak miarodajnego benchmarku **nowych źródeł** przed ponowną kompilacją.
| Endpoint | RustProxy RPS (mediana) | nginx RPS (mediana) | RustProxy / nginx |
|---|---:|---:|---:|
| proxy 1 KiB | 10,544 | 58,709 | 18.0% |
| proxy 64 KiB | 7,893 | 17,358 | 45.5% |
| proxy 1 MiB | 1,010 | 1,666 | 60.6% |
| www 1 KiB | 11,984 | 71,318 | 16.8% |
| www 64 KiB | 9,224 | 26,600 | 34.7% |
| www 1 MiB | 1,264 | 1,098 | 115.2% |
Ograniczenia porównania: ApacheBench może limitować klienta; wyniki nie reprezentują TLS/H2/H3 ani ruchu produkcyjnego. **Nie wolno tych RPS przypisywać nowej rewizji.**
## Zestaw testów dla nowej kompilacji
```bash
CARGO_INCREMENTAL=0 cargo test --locked --lib
CARGO_INCREMENTAL=0 cargo build --release --locked
python3 scripts/tests/cache_age_policy_034_model.py
python3 scripts/tests/cache_age_transform_034_live.py --binary target/release/rust-proxy --output /tmp/cache-test-034
python3 scripts/tests/cache_scope_034_static_test.py
python3 scripts/tests/proxy_hotpath_034_static_test.py
python3 scripts/tests/sendfile_034_static_test.py
```
Do testów jednostkowych dodano Age, transformowane CSS/HOT, democję do COLD i odczyt COLD po restarcie, logikę LRU/HIT oraz kontrolę zakazów `private`/`no-store`. Test lokalny sprawdza CSS/Brotli, origin count, API, keep-alive config i ukryte nagłówki. Test po kompilacji bez przełącznika `--expect-old-age-bug` ma wykazać `MISS → HIT` dla Age/Brotli. Na starej binarce odtwarzanie regresji przechodzi z `--expect-old-age-bug`.
Kontrole wykonane na źródłach: źródłowe testy cache scope, proxy hotpath, sendfile, składnia JS, parsowanie tłumaczeń. **Nie uruchomiono testów Rust ani benchmarków poprawionej binarki** — środowisko nie ma `cargo/rustc`.
## Następny pomiar po kompilacji
Porównać tę rewizję 0.34.0 z poprzednią 0.34.0 oraz nginx dla 1 KiB / 64 KiB / 1 MiB; osobno cache OFF, HOT HIT i transformacja/Brotli. Raportować RPS, p95/p99, procesor, RSS, nowe TCP, `cache.evictions` i `cache.purges`; nie modyfikować ustawień cache podczas benchmarku.
Uwaga: `curl -I -X GET` miesza zachowanie HEAD i GET; do testów reprezentacji zalecane `curl -sS -D - -o /dev/null URL`.
+1 -1
View File
@@ -1,6 +1,6 @@
# Historical roadmap archive
> Archived during the RustProxy 0.25 documentation cleanup. This file preserves completed historical roadmap entries. The active roadmap is [`../ROADMAP.md`](../ROADMAP.md); unresolved work is tracked in [`TODO.md`](TODO.md).
> Archived during the RustProxy 0.25 documentation cleanup. This file preserves completed historical roadmap entries. The active roadmap is [`../ROADMAP.md`](../ROADMAP.md).
# RustProxy roadmap
-148
View File
@@ -1,148 +0,0 @@
# RustProxy 0.31.0 - API, panel and HTTP cache-maintenance security review
Scope: source snapshot supplied for the v0.31.0 maintenance follow-up, reviewed
2026-10-10. This is a **code/contract review with offline regressions**, not a
penetration test or proof of production safety. The supplied archive has no
`.git` directory; the review does not establish parity with remote `master`.
## Interfaces and trust boundaries
| Surface | Exposure | Authentication and mutation rules | Assessment |
|---|---|---|---|
| Public administration bootstrap (`/`, login JS/CSS, `/api/auth/status`, setup/login POST) | Login/public administration listener | Host/origin validation; request marker for unsafe bootstrap actions; one-time setup capability and login throttling | Source-reviewed; live browser flow pending |
| Private panel HTML, JS, documentation, OpenAPI | Administration listener | Administrator session; `Cache-Control: no-store`; CSP, frame and MIME protections | Source and anonymous-response test reviewed; runtime pending |
| `/api/status`, `/api/system-info`, `/api/traffic`, `/api/cache`, logs, config, preferences, audit | Administration listener | Administrator cookie session; sensitive reads not on anonymous allowlist | Source and regression tests added; runtime pending |
| `/api/events`, live-log SSE | Administration listener | Session on subscribe and repeated validity checks; bounded viewers; heartbeat/disconnect cleanup | Source-reviewed; reconnect/load pending |
| State-changing admin APIs (including cache/config/preferences) | Administration listener | Valid session, Origin/Host, `X-RustProxy-Request: 1`, CSRF token; audit on cache commands | Source and regression tests added; runtime pending |
| Certificate API (`/api/certificates/{id}/reload`, deploy) | Administration listener | Narrow-scoped Key ID + Bearer grant; no general read access; HTTPS required remotely; origin validation when supplied | Source-reviewed; token rotation/revocation live tests pending |
| Public `BAN`/`PURGE` | Configured public vhost listener | **No admin session**: separate explicit IP/CIDR allowlist (empty by default), matched listener/vhost/ACL and optional rate limit | Hardened below; reverse/HTTP tests pending |
| Separate Prometheus metrics exporter | Distinct optional listener | Configured IP CIDRs and bearer-token policy; not the admin session | Unchanged; deployment exposure audit pending |
## Findings addressed in this follow-up
1. **E0716 / compilation defect (high functional impact).** `error()` accepts
`&'static str`; a reference to `format!(...)` was a temporary. Invalid
selector errors now log the detailed reason server-side and return a fixed,
non-sensitive message with status 422. This removes the reported lifetime
violation; the Rust compiler must still verify the entire crate.
2. **Cache protocol-scope confusion (medium).** Path-only `BAN`/`PURGE`
selectors could affect representations of the same host/path across both
HTTP and HTTPS. Targets are now built with the trusted external scheme,
validated to `http`/`https`, and the matched Host/vhost. IPv6 literal Hosts
are bracketed for the URL parser. Other tenants, schemes and hosts remain
outside the selector.
3. **Unbounded blocking maintenance work (medium availability risk).** Cache
invalidation may scan hot/cold entries. Both public HTTP and authenticated
admin cache purge/selective invalidation share two maintenance slots and
execute scans with `spawn_blocking`. Bursts receive 429 instead of piling
up unbounded blocking work on Tokio core threads. The permit stays with the
worker after a client disconnects. This is a concurrency bound, **not** a
per-client rate quota or a hard time limit for disk work.
4. **Ambiguous maintenance input (medium).** Duplicate TTL/confirmation or
`Content-Length` headers, transfer-encoded bodies, nonempty bodies and
upgrade requests are rejected; `Content-Length: 0` remains compatible with
CLI clients. TTL is limited to 1..604800 s. Broad `BAN /` requires the
explicit confirmation header; `PURGE` cannot use BAN-only controls.
5. **Audit ordering (low/medium).** Removal of an administrator-created BAN
rule now records the audit entry before mutating state. Unavailable audit
storage blocks the mutation rather than allowing an unaudited removal.
6. **Operator safety/usability (low).** An EN/PL help popover beside the HTTP
allowlist gives exact `curl` syntax, IP-only authentication semantics and
reverse-proxy identity requirements. No new global config variable is added.
## Existing protections checked in source
- Admin router's exact anonymous method/path allowlist contains only the
expected authentication bootstrap APIs; private docs and assets remain
session-protected. Request guard rejects duplicate Host/Origin and checks
browser `Sec-Fetch-Site` cross-site signals.
- Mutation guard uses an Origin/Host match, per-session CSRF comparison and
`X-RustProxy-Request` marker; cookie sessions are HttpOnly/SameSite=Strict
and use Secure under HTTPS/trusted forwarded protocol.
- Secure response policy includes CSP, `X-Frame-Options: DENY`,
`X-Content-Type-Options: nosniff`, referrer restrictions and no-store for
private material. A static scan checked 27 first-party browser JS files for
direct unsafe HTML injection and dynamic `eval`/`Function` sinks; this scan
**cannot establish that the entire DOM is XSS-free**.
- IP trust comes from the transport peer or a validated forwarding chain only
when the peer is configured trusted. An untrusted client-supplied XFF does
not independently grant BAN/PURGE permission. The cache allowlist rejects
`/0`, invalid CIDRs, duplicates and more than 32 entries. Requests are never
forwarded upstream.
- Certificate API tokens have explicit operation/ID grants and cannot use the
general admin read API; Bearer grant operations are intentionally separate
from cookie + CSRF authentication.
## HTTP maintenance CLI (allowlisted client only)
```sh
# One URL, including the query string: 200 with JSON result.
curl -i -X PURGE 'https://site.example/assets/app.js?v=3'
# Lazily ban previously cached objects under this path: 202 with JSON result.
curl -i -X BAN 'https://site.example/assets/'
# Entire host: explicit opt-in confirmation header.
curl -i -X BAN 'https://site.example/' -H 'X-RustProxy-Confirm-Wide: 1'
# Optional BAN expiration (30 seconds).
curl -i -X BAN 'https://site.example/assets/' -H 'X-RustProxy-Ban-Ttl: 30'
```
Use **Settings -> Cache -> HTTP BAN / PURGE IP allowlist** to grant the
caller IP/CIDR. This HTTP interface does not accept admin cookies/tokens as an
alternative to the allowlist. The domain, Host header, external scheme and
listener must select the intended vhost. Behind a reverse proxy, forward the
non-standard methods untouched and **replace**, rather than append untrusted,
XFF/Forwarded headers at the edge. Never add the entire reverse-proxy subnet
as a maintenance client merely because it is trusted for forwarding.
## Release security test plan (requires compiled RustProxy)
1. On the deployment host, run `cargo fmt --all -- --check`,
`./dev.sh check`, `./dev.sh test`, and `./dev.sh release`. Address any
compiler/regression errors before installation. `cargo audit` or an
equivalent advisory scan requires an up-to-date advisory DB and was not run
in this offline environment.
2. Start a disposable instance with a populated cache under both HTTP and
HTTPS for the same vhost/URL. Confirm `PURGE` returns 200 and drops the
exact query variant only; `BAN` returns 202 and invalidates only **older**
entries on the URL prefix. Confirm the *other* scheme and vhost stay hot.
3. From a denied IP, expect 403 on `BAN` and `PURGE`. With an empty allowlist,
all clients must be denied. Send duplicate TTL/confirmation/Content-Length,
TE/body and `BAN /` without confirmation: expect 400. On a trusted-proxy
hop with missing real client identity, expect 403. Test both IPv4 and IPv6.
4. From an allowed IP, send 10 concurrent broad maintenance requests; confirm
that only two workers run simultaneously and excess return 429. Verify
server CPU/IO and audit entries while the cold tier is populated.
5. Send GET requests without admin cookies to `/api/config`, `/api/status`,
`/api/system-info`, `/api/events`, `/api/logs`, `/docs/openapi.json` and
`/assets/app.js`; expect 401 and protective headers. With valid cookie but
missing/bad Origin, marker or CSRF on PUT/POST/DELETE, expect 403. Test
logout and password/session invalidation, live SSE reconnect/expiry.
6. Test the Certificate API with no grant, a valid certificate-specific grant,
revoked token, HTTP remote peer, and misuse against `/api/config`/logs.
Check errors for token disclosure and audit coverage.
7. Test actual browser startup, CSP console, output escaping and form save
with malicious HTML strings; inspect reverse-proxy Host/Origin forwarding.
Prefer HTTPS administration with a strict `allowed_hosts` list.
## Residual risk / non-claims
- **Not executed in this environment:** Cargo format/check/tests/release,
actual Rust listener requests, HTTP/2/3, fuzzing, penetration tests,
session revocation under load, proxy deployment configuration, dependency
CVE scanning and long-running cold-cache resource measurements.
- Proxy trust configuration is security-critical. A misconfigured edge that
accepts forged forwarded client addresses can let external users impersonate
an allowlisted IP. Restrict direct listener reachability and sanitize headers.
- A public IP allowlist is not user authentication; anyone using an allowed
IP/network can issue destructive maintenance commands. Use narrow CIDRs and
optionally enforce additional controls on the edge (mTLS, firewall).
- The two-worker concurrency bound mitigates bursts, not all denial-of-service
scenarios: configured cache size and storage latency still determine scan
cost. Per-IP quotas are not provided here.
- Proxy protocol path, certificate verification, arbitrary upstream code,
all application content and third-party packages are **not certified safe**
by this audit. Production acceptance remains open until the runtime test
plan is completed.
+1 -1
View File
@@ -350,7 +350,7 @@ The adjacent **info (`i`) popover** in Settings → Cache shows ready-to-copy
`curl -X PURGE` and `curl -X BAN` examples in English and Polish, including
the explicit `BAN /` confirmation. These public methods authorize by the
source IP/CIDR only; they do not accept a browser admin session as a bypass.
See [0.31.0 security audit](SECURITY_AUDIT_0.31.0.md) for reverse-proxy trust and tests.
See [security audit](SECURITY_AUDIT.md) for reverse-proxy trust and tests.
## Static-response transformation
-236
View File
@@ -1,236 +0,0 @@
# RustProxy 0.34.0 verification report
## Acceptance status
**Source candidate, not verified production release.** Rust (`cargo`, `rustc`) is
not installed in this execution environment, and the runtime has no access to
its dependency registry. Consequently the modified Rust source was **not
compiled**, the new Rust tests were **not executed**, and no 0.34.0 RustProxy
binary or live throughput figures are supplied. Source inspection and simulated
UI/cache tests do not close the runtime acceptance gate.
## DEV 1 KiB counters follow-up (source remains uncompiled)
The newly supplied binary `rust-proxy(1)` identifies as 0.34.0 and has
SHA-256 `1a84c58b20945bba9fdd1bc1a44d9e9d6ac7659d714343a46a1a8511a78331fa`.
It completed **151 passing real HTTP tests, 0 failures, 1 optional zstd
skip**. These tests were run on the supplied binary, not on the subsequently
modified source. The latest source moves per-domain cache statistics outside
HOT store mutexes and lazily allocates analytics samples only after a queue
permit is available. Histogram completion now increments one exclusive
latency bin instead of up to 11 cumulative bins; both Prometheus exporters
convert them to the same cumulative format at scrape time. Static
source/SQL/upgrade checks pass; **Rust compile,
Rust unit tests and live new-source RPS remain OPEN**. See
`docs/PROXY_1KB_COUNTERS_DEV_0.34.0.md` and the separate evidence archive.
## DEV 1 KiB reverse-proxy follow-up (October 2026; source only)
The supplied **old executable** passed loopback HTTP/1.1 1,040-byte content
checks and ApacheBench 12,000-request samples. The configuration trial
`max_idle_per_host=4 -> 64` increased short-run RPS, but shared-CPU noise
precludes a stable nginx-parity percentage. The edited sources now use 64 by
default for absent/new keepalive fields and reduce exclusive-Drop mutex/clone
and absent-cookie overhead. See `docs/PROXY_1KB_HOTPATH_DEV_0.34.0.md`.
**No newly modified binary was built or run.** Rust unit tests, runtime
regressions, long soaks and post-edit code A/B are still required.
Current DEV source checks performed on the modified tree:
| Check | Result | Scope |
|---|---|---|
| `./dev.sh doctor` | PASS | Source preflight; reports `cargo` and `rustc` absent |
| `scripts/tests/rps_hotpath_034_static_test.py` | PASS | Guardrails, defaults, retained routing/security and source-size limits only |
| `scripts/tests/proxy_hotpath_034_static_test.py` | PASS | Routing and telemetry source guards only |
| `scripts/tests/cache_scope_034_static_test.py`, `cache_hot_034_static_test.py`, `sendfile_034_static_test.py` | PASS | Cache and WWW source guards only |
| `scripts/tests/dashboard_presets_sql_test.py` | PASS | SQLite migrations and preset constraints |
| `scripts/tests/upgrade_performance_test.py` | PASS | Disposable upgrade/preservation/locking test |
| `scripts/tests/sendfile_034_panel_browser_test.py` | PASS, 2 widths | Simulated Chromium admin API, not live Rust |
| `scripts/tests/vhost_backend_popup_0337_browser_test.py` | PASS, 4 widths | Simulated Chromium admin API, not live Rust |
| Node `--check` (vhost editor), Python `compileall`, JSON parser (OpenAPI) | PASS | Syntax only |
| `./dev.sh check`, `./dev.sh test` | BLOCKED, exit 127 | Rust toolchain missing; no Rust unit/build acceptance |
| Old supplied binary, `ab`/nginx configuration A/B | COMPLETED, DIAGNOSTIC | 1,040-byte H1 payload; shared-CPU and no new code exercised |
## Later 0.34.0 reverse-proxy optimization revision
The refreshed source archive additionally reuses immutable route handles,
limits lazy host wildcard allocation, fast-paths one-backend non-cookie LB,
atomically caches healthy state and exchange scalar log fields, shares
compiled per-member health policies across leases, and avoids
unneeded fallback/retry header clones and empty-subscriber broadcast clones.
The existing cache/sendfile work and all panel controls remain. Added Rust
regression cases and the source-only hot-path guard are **not a substitute**
for compilation and live HTTP, health, capacity, sticky, and H2 retry tests.
No RPS improvement, corrected nginx gap, or full functional parity has been
measured for this new revision. Details: `docs/PROXY_HOTPATH_0.34.0.md`.
## RPS-focused follow-up source revision (not yet compiled)
See `docs/PROXY_RPS_0.34.0.md` for detailed changes in request IDs, validated
upstream URI construction, LB scheduling, probes, streaming byte accounting,
unchanged pool generation lifetimes and log-history ownership. Source-only invariants pass.
A live test of the PREVIOUS executable (not this source revision) completed
110 passing cases, 0 failing cases and 1 skipped optional zstd decoder in the
proxy/policy/cache/transform harness. The old executable also confirms query-
string bypass is deliberate and reproduces the older Age/TTL bug. The new
binary has NOT been built or tested and its RPS gain is unknown.
## HOT HIT follow-up source revision (not yet compiled)
See `docs/CACHE_HOTPATH_0.34.0.md`. Response header cloning has moved outside
the global HOT lock, and redundant bounded stats key allocation, empty BAN
checks and full HOT TTL sweeps were removed. A new Rust concurrency/regression
suite and an ignored microbenchmark have been added. Existing COLD and Vary
behavior, exact LRU and cache semantics remain. This source has not been built
or run under Rust in this environment; no RPS gain is claimed.
## Changes and correctness boundaries
- Operational per-vhost saves (upstream keepalive/recycling, connection limits,
load-balancing/health policy, timeouts, request gate and diagnostics) retain
HOT and COLD entries. Backend origin, routing, content, cache and transform
changes retain conservative invalidation. Vhost-specific changes purge only
that vhost; global cache, transform, forwarding, headers and Web Service or
PHP-FPM profile changes may conservatively purge all tiers.
- Scope-specific cold-cache records persist an origin/content compatibility
fingerprint. COLD startup rejects stale scope records and deletes their cache
artifacts. Legacy COLD metadata defaults its fingerprint to zero; production
vhosts have a nonzero SHA-derived fingerprint. A one-time cold refill can
occur on the 0.33.8 -> 0.34.0 upgrade because the global identity changed.
- A separate administrative configuration revision acts as a cache-ticket
publication barrier: a request bound to a pre-save routing snapshot cannot
create a new cache ticket after the save. The data revision stays stable for
operational-only saves, so previously cached objects remain available.
- Reverse-proxy response-body byte metering captures the backend lease once at
wrapper creation instead of acquiring a backend-selection mutex per DATA
frame. Request-body accounting, error paths and exchange ownership remain.
- An **opt-in `sendfile: auto`** setting is wired through Web Service serde,
compiled config, the Delivery UI and documented API. The Linux HTTP/1.1
TCP transport adapter calls `libc::sendfile` for eligible static files;
cache-enabled, ranged, compressed, transformed and TLS/H2/H3 paths retain
existing streaming. It is **disabled by default** and must pass native
compilation, correctness, transport and load tests before use. No persisted
SQLite schema migration or LB UI change is required.
## Local verification
| Check | Outcome | Coverage / caveat |
|---|---|---|
| `./dev.sh doctor` | PASS | Project source layout and tool inventory |
| SQL migration/preset test | PASS | All ten migrations re-applied, preset access and limits |
| Upgrade test | PASS | Stop/lock guards, SQLite/.env/language preservation, symlink rejection |
| Python compileall | PASS | Python syntax only |
| Vhost LB/Cache Chromium test | PASS | Simulated browser quick-view, 320/375/768/1440px |
| Other Chromium scripts | 15 PASS | Simulated API/CSS/JS, not a live Rust server |
| Sidebar navigation Chromium test | INCOMPLETE | Execution time budget; not counted as a pass |
| Three Node dashboard scripts | PASS | Simulated UI logic |
| `./dev.sh check` and `./dev.sh test` | BLOCKED | `cargo`/`rustc` unavailable |
| Rust unit/integration tests (including new scoped COLD regression) | NOT RUN | Requires Rust toolchain |
| Live HTTP/protocol/LB regressions | NOT RUN | Requires built 0.34.0 binary |
| 0.33.8 / 0.34.0 / nginx paired RPS benchmarks | NOT RUN | No verified 0.34.0 binary |
| Zero-copy sendfile WWW setting | SOURCE IMPLEMENTED / NOT RUNTIME VERIFIED | Linux plain H1 static GET only, `off` by default |
| Sendfile editor Chromium save/reload | PASS | 375/1440px, simulated admin API, off -> auto -> off |
| Sendfile Rust adapter + fallback test code | ADDED / NOT RUN | TCP marker replacement, persistent socket, HEAD, range, conditional; compiler unavailable |
| Amdahl sendfile/nginx sensitivity study | PASS MODEL | No runtime evidence for simulated improvements |
The 0.33.8 report in the supplied evidence archive recorded **706 passing
Rust tests**, **152 HTTP assertions**, **111 transport assertions** and
**17 LB assertions**. Those are *prior-version* results and are **not evidence
that 0.34.0 passes**. This report never transfers previous pass counts.
## Synthetic cache-retention scenario (not an RPS measurement)
Run:
```bash
python3 scripts/performance/simulate_cache_034.py
```
Assume 20 vhosts, 100 HOT + 100 COLD objects each, all public/cacheable with
infinite TTL and adequate cache capacity; 1,000 configuration saves (800
operational-only, 200 content changes to one vhost), followed by 500 uniformly
random GETs per save. A miss immediately reloads an object into its original
tier. No networking, filesystem, CPU timing, origin delay or concurrency is
modeled. Results with seed 3400:
| Synthetic metric | Old global purge | 0.34.0 scoped retention |
|---|---:|---:|
| Object invalidations | 473,540 | 37,300 |
| GET misses | 470,006 | 37,069 |
| GET hits | 29,994 | 462,931 |
| HIT ratio | 5.999% | 92.586% |
The modeled change yields **92.123% fewer invalidations**, **92.113% fewer
misses**, and **+86.587 percentage points** of synthetic cache HIT ratio.
**Measured 0.34.0 requests/s gain: unknown.** These values must not be
presented as a real speedup or a projection against nginx.
## Previously measured comparison (0.33.8, not this candidate)
From the attached 0.33.8 evidence, five paired benchmark runs with cache OFF,
median RPS:
| Mode / payload | 0.33.8 RPS | nginx RPS | 0.33.8 / nginx |
|---|---:|---:|---:|
| Reverse proxy 1,040 B | 29,334.12 | 170,610.16 | 17.19% |
| Reverse proxy 64 KiB | 20,735.07 | 35,393.29 | 58.58% |
| Reverse proxy 1 MiB | 4,461.19 | 2,730.23 | 163.40% |
| WWW 1,040 B | 24,906.20 | 351,605.13 | 7.08% |
| WWW 64 KiB | 20,856.67 | 203,709.91 | 10.24% |
| WWW 1 MiB | 2,833.71 | 12,611.49 | 22.47% |
The 0.33.8 1 MiB proxy path was **2.77% slower than rebuilt 0.33.7**.
Removing a per-response-frame mutex is a performance candidate but is **not**
a measured correction of that regression. No throughput gains are attributed
here to the new source changes.
## Hypothetical 0.34.0/nginx gap sensitivity (NOT MEASURED)
Run `python3 scripts/performance/simulate_nginx_gap_034.py`. Using the **real
0.33.8 measured medians** above, assume zero-copy removes 50% of the
read/copy part of per-request cost; that part hypothetically accounts for
20/40/60% of *total* cost for eligible static files. Model **1/3/5%** total
service-time savings from removed proxy-frame mutexes, independently.
Amdahl: `RPS_sim = RPS_0.33.8 / (1 - hypothetical_fraction_saved)`. The
result is a sensitivity calculation, **not a prediction or 0.34.0 benchmark**.
| Workload | Measured 0.33.8/nginx | Modeled low | Modeled middle | Modeled high |
|---|---:|---:|---:|---:|
| Proxy 1040 B | 17.19% | 17.37% | 17.73% | 18.10% |
| Proxy 64 KiB | 58.58% | 59.18% | 60.40% | 61.67% |
| Proxy 1 MiB | 163.40% | 165.05% | 168.45% | 172.00% |
| WWW 1040 B | 7.08% | 7.08% | 7.08% | 7.08% |
| WWW 64 KiB | 10.24% | 11.38% | 12.80% | 14.63% |
| WWW 1 MiB | 22.47% | 24.97% | 28.09% | 32.10% |
These ratios apply only to eligible Linux plain HTTP/1.1 static workloads
with cache OFF. TLS, HTTP/2, HTTP/3, ranged, compressed and object-cached
responses are **not** accelerated by this bridge. Bandwidth, contention,
queueing, realistic filesystem and actual CPU profiles are not modeled.
Even the favorable modeled 1 MiB case remains ~3.1x slower than nginx WWW;
small WWW remains ~14.1x slower. Proxy 1 MiB already exceeds nginx in the
**prior baseline**, not as a demonstrated new 0.34.0 improvement.
## Required rollout gates
1. On a trusted Linux build worker with the pinned Rust toolchain, run
`./dev.sh check`, `CARGO_INCREMENTAL=0 ./dev.sh test`, and a locked release
build. Resolve any compiler/test issues before a deployment.
2. Follow `scripts/performance/README.md` to run `regression.py`,
`transport_regression.py`, `lb_regression.py`, and
`concurrency_regression.py` against verified 0.33.8 and 0.34.0 binaries.
3. Exercise administration saves during simultaneous requests: save keepalive
values and confirm unchanged HOT/COLD entries and hashes; change one backend
origin and confirm only its scope is purged, both before/after restart. Check
that old snapshots cannot recreate invalidated objects.
4. Run the five-repeat, 15-second paired benchmark against 0.33.8, 0.34.0 and
nginx on the *same host and affinity*, including large proxy responses, CPU,
RSS, resource limits and response correctness.
5. Compile the source-level Linux `sendfile` bridge and validate on a running
RustProxy process: plain HTTP/1 sendfile path, headers/body exactness,
keepalive/pipelining, slow clients, cancellation, HEAD, range, cache,
compression, TLS/H2/H3 fallback, symlink security and observability. Do
**not** activate Auto on production before that gate passes.
6. Complete a mixed-traffic soak and staged rollback rehearsal. Until then,
do not describe 0.34.0 as production-accepted.
-19
View File
@@ -1,19 +0,0 @@
# RustProxy TODO
This file contains open work that remains after the current RustProxy 0.25 Web Server implementation.
## Web Server follow-up
- Multipart byte-range responses if required; current single-range/If-Range support remains the default scope.
- Named locations only if they can reuse the existing vhost/location router rather than introduce a second router.
- Optional PHP `PATH_INFO` script/path splitting after a confinement-safe rule is defined.
- Additional FastCGI observability (bounded active/error/latency counters) without per-script labels.
- A race-resistant non-Unix filesystem confinement implementation before enabling the Web Server on non-Unix targets.
## Release acceptance
- Run `./dev.sh fmt`, `./dev.sh check` and `./dev.sh test` on a host with the stable Rust toolchain.
- Run live static-file acceptance over HTTP/1.1, HTTP/2 and HTTP/3, including ACL/auth/custom-error/header behavior.
- Run a real PHP-FPM acceptance matrix over a Unix socket and loopback TCP, including `index.php`, front-controller `try_files`, POST bodies, timeouts and source-disclosure failure tests.
Completed release work belongs in `CHANGELOG.md`; planned version work belongs in `ROADMAP.md`; implementation/audit evidence belongs in the matching audit document.
+1 -1
View File
@@ -234,7 +234,7 @@ origin do not expose a selected backend or stale origin timing.
| `pool_recycle_seconds` | 3600 | 0 (off) or 60..604800 |
| `http1_max_buffer_bytes` | 131072 | 8192..1048576 |
The new default of 64 applies when this field is absent in a newly created or deserialized vhost; existing saved values (including 4) remain unchanged. Each idle upstream connection consumes a file descriptor and memory, so use a smaller value for resource-constrained deployments with many origins. See `docs/PROXY_1KB_HOTPATH_DEV_0.34.0.md` for the live configuration A/B evidence.
The new default of 64 applies when this field is absent in a newly created or deserialized vhost; existing saved values (including 4) remain unchanged. Each idle upstream connection consumes a file descriptor and memory, so use a smaller value for resource-constrained deployments with many origins.
These configure HTTP idle reuse and TCP keepalive; HTTP/2 also uses pings for active
connections, with a 10-second ping timeout and no pinging solely to keep idle
+1 -3
View File
@@ -134,9 +134,7 @@ does not change the listener protocol configuration.
tested against a running 0.34.0 RustProxy binary in the current environment.
The included Rust unit/integration test and real-network test cases must pass
on a Linux build worker before enabling in production. Sendfile cannot by
itself close the small-response gap to nginx. See
`docs/TEST_REPORT_0.34.0.md` and
`scripts/performance/simulate_nginx_gap_034.py`.
itself close the small-response gap to nginx. Live protocol and performance acceptance is still required.
### Intercept application HTTP errors (0.29.7)