# 基准测试 > ## Disclaimer > > **Benchmarks are synthetic.** They measure isolated behavior in artificial conditions. > Production workloads have different allocation patterns, concurrency levels, GC > pressure, network conditions, and hardware. The Varnish and NGINX configurations used > in these tests could likely be improved — a differently tuned Varnish or NGINX may > produce different results. Benchmarking is genuinely hard, and these numbers should be > treated as directional indicators, not as definitive performance claims. > > Always benchmark on your own hardware with your own workload before making > infrastructure decisions. ## Methodology All benchmarks were run on the same machine (Apple M5, darwin/arm64) with Go 1.27. Proxies were run in Docker containers via `docker compose`, with an origin server serving synthetic responses. Load was generated by [k6](https://k6.io/) at fixed RPS targets. ### Proxies tested | Proxy | Version | Configuration | |-------|---------|---------------| | **bouine** | v0.5.x (current main) | Default config with `stale_while_revalidate: 30s` | | **Varnish** | 7.x | Default VCL with grace period, SWR enabled | | **NGINX** | 1.25.x | `proxy_cache` with default settings | | **Envoy** | 1.29.x | HTTP cache filter with default settings | ### Scenarios | Scenario | Description | RPS | VUs | Hit rate target | |----------|-------------|-----|-----|-----------------| | **3.2 Hit-only** | Warm cache, all requests are cache hits | 3000 | 200 | 100% | | **3.3 Miss storm** | `Cache-Control: no-store` on all responses | 1500 | 100 | 0% | | **3.6 Mixed realistic** | 60% cacheable + 40% non-cacheable + revalidation | 3000 | 300 | ~73% (bouine) / ~93% (Varnish) | The mixed scenario uses a realistic mix of cacheable and non-cacheable responses with varying TTLs, conditional requests (ETag revalidation), and `Vary` headers. ## Results ### Scenario 3.2 — Hit-Only (3000 RPS, 100% cache hits) Pure cache-hit performance. Every request finds a fresh cached response. | Proxy | Avg latency | p90 | p95 | RPS achieved | |-------|-------------|-----|-----|--------------| | **bouine** | **0.166 ms** | 0.219 ms | 0.277 ms | 2996 | | Varnish | 0.177 ms | 0.201 ms | 0.263 ms | 2999 | | NGINX | 0.166 ms | — | 0.304 ms | 3000 | | Envoy | 0.232 ms | — | 0.300 ms | 2999 | Bouine and NGINX match Varnish on pure cache hits. Envoy is ~40% slower. ### Scenario 3.3 — Miss Storm (1500 RPS, all misses) Every request goes to origin (origin returns `Cache-Control: no-store`). | Proxy | Avg latency | p90 | p95 | RPS achieved | |-------|-------------|-----|-----|--------------| | **bouine** | **0.157 ms** | 0.186 ms | 0.232 ms | 1500 | | Varnish | 0.166 ms | 0.218 ms | 0.298 ms | 1500 | | NGINX | 541 ms (overloaded) | 1501 ms | 3002 ms | 1283 | | Envoy | 0.307 ms | 0.172 ms | 0.189 ms | 1500 | NGINX's `proxy_cache` path is not optimized for the no-store case and degrades under sustained miss traffic. Bouine and Varnish handle miss storms equally well. Envoy has low p90/p95 but higher average due to occasional spikes. ### Scenario 3.6 — Mixed Realistic (3000 RPS, ~73% hit rate) The most production-representative scenario: a mix of cacheable and non-cacheable responses, conditional revalidation, and `Vary` headers. | Proxy | Avg latency | p90 | p95 | Hit rate | |-------|-------------|-----|-----|----------| | **bouine** | **0.278 ms** | 0.258 ms | 0.334 ms | 72.8% | | Varnish | 0.233 ms | 0.260 ms | 0.385 ms | 93.0% | | NGINX | 22.1 ms (overloaded) | — | 0.625 ms | 72.9% | | Envoy | 0.281 ms | 0.273 ms | 0.354 ms | 0% (no caching) | Varnish's 93% hit rate vs bouine's 73% is the primary driver of the latency difference. Varnish serves stale objects unconditionally during revalidation (grace mode), while bouine's `stale-while-revalidate` is RFC 5861-compliant (only serves stale within the SWR window). Aligning bouine's grace semantics with Varnish's would close most of this gap. Envoy's cache filter did not cache any responses in this scenario (0% hit rate). ### Evolution: bouine before and after optimization | Scenario | bouine (original) | bouine (optimized) | Improvement | vs Varnish | |----------|-------------------|--------------------|-------------|------------| | Hit-only | 0.338 ms | 0.166 ms | **-51%** | 2.1x slower → **6% faster** | | Miss storm | 0.647 ms | 0.157 ms | **-76%** | 4.4x slower → **5% faster** | | Mixed | 0.485 ms | 0.278 ms | **-43%** | 2.85x slower → **1.19x slower** | ## Go micro-benchmarks Unit-level benchmarks isolating specific hot-path components, from the current `bench/results/current.txt` gates. `Handler_CacheMiss_Cacheable` is held to an allocation budget of 18 in CI (currently at 13 — see the v0.5.2 changelog). | Benchmark | ns/op | B/op | allocs/op | Description | |-----------|-------|------|-----------|-------------| | `Evaluate_Hit` | ~45 | 0 | 0 | RFC 9111 freshness evaluation | | `HotStore_Get_Hit` | ~18 | 0 | 0 | In-memory cache lookup (SIEVE) | | `BuildKey` | ~48 | 0 | 0 | Cache key computation (xxhash64) | | `FastPath_Hit` | ~129 | 0 | 0 | Full H1 fast-path cache hit (parse + lookup + writev) | | `H1Parse_Get` | ~192 | 0 | 0 | H1 request parse (h1parser) | | `Handler_CacheHit_ReusableWriter` | ~390 | 0 | 0 | Hit path with zero-alloc ResponseWriter | | `Handler_CacheMiss_Cacheable` | ~3.9 µs | 2311 | 13 (budget 18) | Cacheable miss (allocation-gated, not time-gated) | | `SIEVE_Access` | ~18 | 0 | 0 | SIEVE eviction policy access | All hit-path benchmarks enforce **0 allocs/op** in CI via benchmark gates. Any allocation on the hit path blocks merge. > **Comparability note.** The nightly load-test configuration enables > `experimental.h1_fast_path` (since v0.5.3) and `experimental.h1_reactor` > (since v0.5.5). Nightly proxy-comparison numbers from v0.5.2 and earlier > were measured without the fast path and are **not comparable**. ## Benchmark infrastructure The benchmark suite lives in `bench/` in the [bouine repository](https://github.com/bouine-cache/bouine): ``` bench/ ├── run.sh # Go micro-benchmark runner with gates ├── results/ # Baseline and current benchmark output └── loadtest/ ├── docker-compose.yaml # bouine + varnish + nginx + envoy + origin ├── config/ # Per-proxy configurations ├── scenarios/ │ ├── 3.2_hit_only/ # Pure cache-hit scenario │ ├── 3.3_miss_storm/ # All-miss scenario │ ├── 3.6_mixed_realistic/ # Production-like mixed workload │ └── ... # Cluster, chaos, and edge-case scenarios └── analysis/ # Python scripts for plotting and reporting ``` To reproduce the load-test results: ```bash cd bench/loadtest docker compose up -d origin bouine varnish nginx envoy # Run a scenario k6 run scenarios/3.2_hit_only/k6.js # Generate a comparison report python3 analysis/report.py results/ ``` To run the Go micro-benchmarks: ```bash make bench # runs all benchmarks, checks gates, saves to bench/results/current.txt make benchstat # compares current vs baseline with benchstat ```