v0.34.0_1

This commit is contained in:
Mateusz Gruszczyński committed 2026-10-17 20:34:13 +02:00
1 parent 9b3d6731cc
commit 00f29f92c8
68 files changed
+2599 -513

No files matched your search

+33
View File
@@ -6,6 +6,26 @@ controllable test **upstream**, used to verify streaming, non-replayable writes,
headers, cache policy and WebSocket frames. Performance measurements use nginx
as the shared upstream instead of Python.
## Reverse-proxy optimization revision 0.34.0
After compiling the refreshed candidate, measure the same unmodified 0.33.8
binary and nginx reference as before, without turning off logging, request
IDs, ACL, cache-policy enforcement or LB health. Profile request-size 1,040 B
first. Before RPS runs execute full proxy, LB, transport and concurrency
regressions (including single-member capacity, recovery, sticky cookies,
replays on safe methods, rejected non-replayable writes and H2 connection
shutdown). Treat `--max-idle` tuning and recycles as separately labelled
experiments rather than mixing them into the primary comparison. There are no
new measured RPS figures in the source candidate.
## Offline cache-retention model for 0.34.0
`python3 scripts/performance/simulate_cache_034.py` compares cache invalidation
policies under a deterministic synthetic sequence of vhost saves and uniform
cacheable GETs. The model contains **no RustProxy server**, no timing and no
network traffic; its percentages are **not measured RPS gains**. See
`docs/TEST_REPORT_0.34.0.md` for assumptions and the live acceptance gap.
## Requirements
- Linux with readable `/proc`, Python 3.10+, executable RustProxy binaries.
@@ -274,3 +294,16 @@ are not presented as proven server-side cancellations.
Memory trend is descriptive: switching caching and allocator retention affect
RSS. This is a 30-minute resource gate, not a 24-hour leak proof. A short
`--exploratory --duration ...` sanity run stays explicitly outside that gate.
## 0.34.0 hypothesis-only sendfile/nginx gap simulator
```bash
python3 scripts/performance/simulate_nginx_gap_034.py --output /tmp/nginx-gap.json
```
This is **not** a benchmark of RustProxy 0.34.0. It reproduces measured
0.33.8/nginx medians with cache OFF and applies explicitly hypothetical
Amdahl service-time reductions (WWW and reverse proxy separately). A real
RustProxy binary is required to validate the code path, detect regressions
and measure latency/RPS against nginx under identical conditions.
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""Deterministic analytical/synthetic cache-retention scenario, not a RustProxy load test.
Model: 20 vhosts with 100 HOT + 100 COLD uniformly requested objects each.
A new configuration is saved 1,000 times. Eighty percent of saves edit only
keepalive/LB operational controls, and 20% edit content policy on one vhost.
Every save is followed by 500 uniform GETs; every object is public/cacheable and
has infinite TTL. A miss immediately inserts the object in its original tier.
Capacity is assumed adequate and there is no origin latency or concurrent IO.
The baseline flushes both tiers globally on every save, as in 0.33.8.
The candidate changes only the affected vhost after content edits.
"""
import argparse
import json
import random
def simulate(vhosts=20, objects_per_tier=100, saves=1000, requests_per_save=500, seed=3400):
if min(vhosts, objects_per_tier, saves, requests_per_save) < 1:
raise ValueError("all counts must be positive")
rng = random.Random(seed)
objects = vhosts * objects_per_tier * 2
old = set(range(objects))
new = set(range(objects))
old_hits = new_hits = old_misses = new_misses = 0
old_evicted = new_evicted = 0
edits_operational = edits_content = 0
for step in range(saves):
changed_vhost = rng.randrange(vhosts)
content_edit = step % 5 == 0
if content_edit:
edits_content += 1
else:
edits_operational += 1
old_evicted += len(old)
old.clear()
if content_edit:
low = changed_vhost * objects_per_tier * 2
high = low + objects_per_tier * 2
removed = set(range(low, high)) & new
new_evicted += len(removed)
new.difference_update(removed)
for _ in range(requests_per_save):
object_id = rng.randrange(objects)
if object_id in old:
old_hits += 1
else:
old_misses += 1
old.add(object_id)
if object_id in new:
new_hits += 1
else:
new_misses += 1
new.add(object_id)
total = saves * requests_per_save
assert old_hits + old_misses == total
assert new_hits + new_misses == total
assert new_evicted <= old_evicted
assert new_misses <= old_misses
return {
"scenario": {
"vhosts": vhosts,
"hot_objects_per_vhost": objects_per_tier,
"cold_objects_per_vhost": objects_per_tier,
"saves": saves,
"operational_saves": edits_operational,
"single_vhost_content_saves": edits_content,
"gets_per_save": requests_per_save,
"total_gets": total,
"seed": seed,
"assumptions": "All objects public/cacheable, infinite TTL, instantly refilled, uniform GETs; no bandwidth/CPU/origin timings",
},
"legacy_global_purge": {
"invalidated_objects": old_evicted,
"misses": old_misses,
"hits": old_hits,
"hit_ratio_percent": round(100 * old_hits / total, 3),
},
"scoped_cache": {
"invalidated_objects": new_evicted,
"misses": new_misses,
"hits": new_hits,
"hit_ratio_percent": round(100 * new_hits / total, 3),
},
"improvement": {
"fewer_invalidations_percent": round(100 * (1 - new_evicted / old_evicted), 3) if old_evicted else 0,
"fewer_cache_misses_percent": round(100 * (1 - new_misses / old_misses), 3) if old_misses else 0,
"hit_ratio_gain_percentage_points": round(100 * (new_hits - old_hits) / total, 3),
"observed_rps_gain_percent": None,
},
}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--vhosts", type=int, default=20)
parser.add_argument("--objects-per-tier", type=int, default=100)
parser.add_argument("--saves", type=int, default=1000)
parser.add_argument("--gets-per-save", type=int, default=500)
parser.add_argument("--seed", type=int, default=3400)
parser.add_argument("--output", help="optional JSON results file")
args = parser.parse_args()
result = simulate(args.vhosts, args.objects_per_tier, args.saves, args.gets_per_save, args.seed)
encoded = json.dumps(result, indent=2, sort_keys=True)
if args.output:
from pathlib import Path
Path(args.output).write_text(encoded + "\n")
print(encoded)
if __name__ == "__main__":
main()
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Amdahl sensitivity study only: 0.33.8 measured baseline, 0.34.0 not measured.
No synthetic RPS is described as benchmarked RustProxy throughput. Amdahl uses
RPS_new = RPS_old / (1 - eligible_cost_fraction * fraction_eliminated).
"""
from __future__ import annotations
import argparse
import json
from pathlib import Path
# Verified medians reproduced from the supplied 0.33.8 evidence. Cache OFF.
BASELINE = [
("reverse_proxy", 1040, 29334.12, 170610.16, False),
("reverse_proxy", 65536, 20735.07, 35393.29, False),
("reverse_proxy", 1048576, 4461.19, 2730.23, False),
("www", 1040, 24906.20, 351605.13, False),
("www", 65536, 20856.67, 203709.91, True),
("www", 1048576, 2833.71, 12611.49, True),
]
# WWW scenarios: share of per-request service time due to file copy/read;
# assume sendfile removes HALF of that part, not the full service time.
WWW_SHARES = {"low": 0.20, "middle": 0.40, "high": 0.60}
COPY_COST_REMOVED = 0.50
# Proxy independent optimization: hypothetical reduction in *total* request
# service time, modeling removal of redundant response-frame mutex, nothing else.
PROXY_TIME_SAVED = {"low": 0.01, "middle": 0.03, "high": 0.05}
def run() -> dict:
items = []
for kind, payload_bytes, rust_rps, nginx_rps, eligible in BASELINE:
baseline_ratio = rust_rps / nginx_rps
row = {
"kind": kind,
"bytes": payload_bytes,
"baseline_version": "0.33.8",
"baseline_rust_rps": rust_rps,
"baseline_nginx_rps": nginx_rps,
"baseline_of_nginx_pct": round(100 * baseline_ratio, 2),
"factor_to_match_nginx": round(nginx_rps / rust_rps, 2),
"sendfile_eligible_only_if_plain_linux_http1_1_no_cache_no_compression": eligible,
"scenarios": {},
}
for level in ("low", "middle", "high"):
if kind == "www" and eligible:
saved_fraction = WWW_SHARES[level] * COPY_COST_REMOVED
elif kind == "reverse_proxy":
saved_fraction = PROXY_TIME_SAVED[level]
else:
saved_fraction = 0.0
simulated_rps = rust_rps / (1.0 - saved_fraction)
row["scenarios"][level] = {
"modeled_total_time_saved_pct": round(100 * saved_fraction, 2),
"simulated_rps": round(simulated_rps),
"simulated_rps_gain_pct": round(100 * (simulated_rps / rust_rps - 1), 2),
"simulated_of_nginx_pct": round(100 * simulated_rps / nginx_rps, 2),
"simulated_remaining_gap_to_nginx_pct_points": round(
100 * (1 - simulated_rps / nginx_rps), 2
),
}
items.append(row)
return {
"kind": "HYPOTHETICAL_AMDAHL_SENSITIVITY_NOT_A_BENCHMARK",
"comparison": "Existing 0.33.8 measurements (5 runs, cache off) vs measured nginx",
"not_measured": "0.34.0 server performance and sendfile syscall/runtime correctness",
"conditions": [
"Identical CPU and network assumptions to supplied 0.33.8 baseline",
"WWW zero-copy is limited to Linux, cleartext HTTP/1.1, GET >=64KiB, no cache, range, transform, compression or TLS",
"WWW low/middle/high: file-copy contributes 20%/40%/60% of total request time; modeled sendfile eliminates 50% of that component",
"Reverse proxy low/middle/high: removing response-frame mutex saves a hypothetical 1%/3%/5% of total service time",
"These fractions are chosen sensitivity parameters; not measured or inferred from profiling",
"No change is assumed for WWW 1040B or out-of-scope protocol paths",
"No network saturation, TLS record processing, queueing, origin latency, filesystem cache misses, CPU contention or concurrency effects modeled",
"No additional speedup due to cache-retention changes is assumed",
],
"formula": "RPS_sim = RPS_0.33.8 / (1 - fraction_of_total_service_time_eliminated)",
"entries": items,
}
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--output", type=Path, help="write JSON to this file")
args = parser.parse_args()
payload = json.dumps(run(), indent=2, ensure_ascii=False) + "\n"
if args.output:
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(payload)
print(f"wrote {args.output}")
else:
print(payload)
if __name__ == "__main__":
main()