v0.34.0_1
This commit is contained in:
1 parent
9b3d6731cc
commit
00f29f92c8
68 files changed
+2599
-513
No files matched your search
@@ -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.
|
||||
|
||||
@@ -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()
|
||||
Reference in new issue
Block a user