Skip to content

fix(vanilla-epoll): lazy json-comp gz cache — drop ~135 MiB -gc none boot leak#959

Open
enghitalo wants to merge 2 commits into
MDA2AV:mainfrom
enghitalo:fix/json-comp-epoll-lazy
Open

fix(vanilla-epoll): lazy json-comp gz cache — drop ~135 MiB -gc none boot leak#959
enghitalo wants to merge 2 commits into
MDA2AV:mainfrom
enghitalo:fix/json-comp-epoll-lazy

Conversation

@enghitalo

Copy link
Copy Markdown
Contributor

What

Restore the lazy, shared, RwMutex-guarded json-comp gz cache on vanilla-epoll, reverting the lock-free boot precompute that shipped in #956.

(Split of #958 — this is the epoll half; the io_uring half is a separate PR.)

Why

The precompute compressed the full (count × m) grid (~800 keys) at boot. On this -gc none build, V's gzip.compress leaks ~157 KiB of internal scratch per call (vlang/v#27606), so the boot precompute leaked ~135 MiB of permanent RSS — CI showed baseline memory jump 77 → 213 MiB across every profile.

The lazy cache compresses only the 8 (count,m) pairs the profile actually sends (~1.3 MiB). This restores the exact json-comp code CI measured at 77 MiB in the #956 combo commit (pre-cleanup), plus a guard comment so the precompute isn't reintroduced.

The lock-free change had no upside: it was meant to fix the io_uring json-comp@16384 collapse (lock-contention hypothesis, enghitalo/vanilla#89), but CI refuted it — epoll is healthy at 16384c with the lazy lock.

Benchmark (already run on #958, vanilla-epoll)

metric before (precompute) this PR (lazy)
baseline 512 mem 213 MiB 77 MiB (−63.8%)
json-comp@16384 377 MiB 185 MiB (−50.9%), 2,314,750 rps, CPU 6389% (saturated)
throughput (all profiles) flat / slightly up, no regression

Memory dropped 30–67% across every profile. Happy to re-run /benchmark -f vanilla-epoll on this split PR to re-validate.

🤖 Generated with Claude Code

…boot leak

The json-comp lock-free precompute (from MDA2AV#956) compressed the full (count x m) grid
(~800 keys) at boot. On this `-gc none` entry V's gzip.compress leaks ~157 KiB of
scratch per call (vlang/v#27606), so the boot precompute leaked ~135 MiB of permanent
RSS — CI showed baseline mem jump 77 -> 213 MiB across every profile.

Restore the original LAZY, process-shared, RwMutex-guarded gz cache: compress only the
~8 (count,m) pairs the profile actually sends (~1.3 MiB) and append the cached bytes on
a hit. This is the exact json-comp code CI measured at 77 MiB in the MDA2AV#956 combo commit
(pre-cleanup), plus a guard comment so the precompute is not reintroduced.

The lock-free change had no upside: it was meant to fix the io_uring json-comp@16384
collapse (lock-contention hypothesis, enghitalo/vanilla#89), but CI refuted it — epoll
is healthy at 16384c WITH the lazy lock.

Validated (benchmarked as MDA2AV#958): baseline 213 -> 77 MiB (-63.8%), json-comp@16384
2.31M rps / CPU 6389% (saturated) / 185 MiB (-50.9%), no throughput regression.

Split out of MDA2AV#958 (one PR per engine).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@enghitalo

Copy link
Copy Markdown
Contributor Author

/benchmark -f vanilla-epoll --save

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

👋 /benchmark request received. A collaborator will review and approve the run.

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Benchmark Results

Framework: vanilla-epoll | Test: all tests

Test Conn RPS CPU Mem Δ RPS Δ Mem
baseline 512 3,718,336 6434.8% 78MiB +1.5% -63.4%
baseline 4096 4,149,547 6439.5% 159MiB +1.8% -45.5%
pipelined 512 39,802,105 6711.6% 73MiB +0.5% -65.6%
pipelined 4096 40,010,412 6510.3% 164MiB +0.4% -45.5%
limited-conn 512 1,001,860 3187.8% 67MiB +0.5% -67.5%
limited-conn 4096 1,023,743 2947.4% 71MiB +2.5% -71.1%
json 4096 2,156,907 6366.8% 190MiB +0.4% -48.1%
json-comp 512 2,089,475 6244.9% 95MiB -0.5% -57.4%
json-comp 4096 2,324,142 6278.4% 152MiB +0.7% -40.9%
json-comp 16384 2,298,436 6020.2% 261MiB -0.8% -30.8%
json-tls 4096 1,500,569 5941.1% 398MiB -0.3% -26.0%
upload 32 2,958 2036.3% 134MiB -0.1% -50.4%
upload 256 3,017 3658.0% 338MiB +0.4% -27.6%
api-4 256 63,173 369.1% 112MiB -2.8% -55.2%
api-16 1024 246,597 1521.7% 184MiB +2.3% -42.7%
static 1024 1,794,986 5727.8% 97MiB -2.2% -58.7%
static 4096 1,780,314 5623.0% 224MiB -1.6% -37.8%
static 6800 1,734,894 5339.1% 313MiB -2.2% -29.7%
async-db 1024 287,513 3010.6% 143MiB +0.1% -48.2%
crud 4096 310,881 780.7% 218MiB -0.5% -38.2%
fortunes 1024 166,925 6141.3% 257MiB -1.0% -34.9%
Full log
  Templates: 20
  Expected:  200
  Duration:  15s


  Thread Stats   Avg      p50      p90      p99    p99.9
    Latency   11.44ms   1.00ms   34.30ms   73.00ms   111.40ms

  4729208 requests in 15.00s, 4729208 responses
  Throughput: 315.23K req/s
  Bandwidth:  94.97MB/s
  Status codes: 2xx=4663216, 3xx=0, 4xx=0, 5xx=65992
  Latency samples: 4729208 / 4729208 responses (100.0%)
  Reconnects: 22062
  Per-template: 98706,121137,155734,187680,220190,250621,278684,305082,324736,337249,345068,345986,343201,346540,348756,347992,128198,64199,83219,96230
  Per-template-ok: 94756,119584,153739,185329,217439,247389,275183,300983,320327,332842,340549,341529,338906,342354,344348,343505,128198,62391,80672,93193

  WARNING: 65992/4729208 responses (1.4%) had unexpected status (expected 2xx)
[info] CPU 780.7% | Mem 218MiB

[run 2/3]
gcannon v0.5.3
  Target:    localhost:8080/
  Threads:   64
  Conns:     4096 (64/thread)
  Pipeline:  1
  Req/conn:  200
  Templates: 20
  Expected:  200
  Duration:  15s


  Thread Stats   Avg      p50      p90      p99    p99.9
    Latency   12.16ms   5.76ms   34.70ms   66.20ms   94.40ms

  4514976 requests in 15.00s, 4514976 responses
  Throughput: 300.94K req/s
  Bandwidth:  92.11MB/s
  Status codes: 2xx=4439147, 3xx=0, 4xx=0, 5xx=75829
  Latency samples: 4514967 / 4514976 responses (100.0%)
  Reconnects: 20935
  Per-template: 103114,123185,152822,179089,206630,236407,262840,286457,305159,314591,320531,322728,324984,322448,321159,320078,146175,78766,88980,98824
  Per-template-ok: 99535,121352,150316,176397,203212,232780,258560,281733,300245,309609,315358,317518,319715,317303,315823,314947,146175,76420,86467,95673

  WARNING: 75829/4514976 responses (1.7%) had unexpected status (expected 2xx)
[info] CPU 786.0% | Mem 226MiB

[run 3/3]
gcannon v0.5.3
  Target:    localhost:8080/
  Threads:   64
  Conns:     4096 (64/thread)
  Pipeline:  1
  Req/conn:  200
  Templates: 20
  Expected:  200
  Duration:  15s


  Thread Stats   Avg      p50      p90      p99    p99.9
    Latency   13.10ms   8.18ms   34.10ms   61.70ms   85.90ms

  4233666 requests in 15.00s, 4233665 responses
  Throughput: 282.20K req/s
  Bandwidth:  87.30MB/s
  Status codes: 2xx=4177319, 3xx=0, 4xx=0, 5xx=56346
  Latency samples: 4233658 / 4233665 responses (100.0%)
  Reconnects: 19433
  Per-template: 105905,121592,148719,175668,202994,228605,252610,271423,283133,285582,284874,285938,286618,285172,287944,287112,150838,90815,94013,104102
  Per-template-ok: 103524,120136,146622,173233,200260,225575,249256,268010,279257,281967,281216,282147,282810,281454,284194,283214,150838,89196,92083,102320

  WARNING: 56346/4233665 responses (1.3%) had unexpected status (expected 2xx)
[info] CPU 762.5% | Mem 232MiB

=== Best: 310881 req/s (CPU: 780.7%, Mem: 218MiB) ===
[info] input BW: 26.68MB/s (avg template: 90 bytes)
[info] saved results/crud/4096/vanilla-epoll.json
httparena-bench-vanilla-epoll
httparena-bench-vanilla-epoll

==============================================
=== vanilla-epoll / fortunes / 1024c (tool=gcannon) ===
==============================================
[info] resetting postgres for a clean per-profile baseline
[info] starting postgres sidecar
httparena-postgres
[info] postgres ready (seeded)
[info] waiting for server...
[info] server ready

[run 1/3]
gcannon v0.5.3
  Target:    localhost:8080/fortunes
  Threads:   64
  Conns:     1024 (16/thread)
  Pipeline:  1
  Req/conn:  unlimited (keep-alive)
  Expected:  200
  Duration:  5s


  Thread Stats   Avg      p50      p90      p99    p99.9
    Latency   4.76ms   4.00ms   8.48ms   14.50ms   34.10ms

  828850 requests in 5.00s, 828850 responses
  Throughput: 165.70K req/s
  Bandwidth:  3.84GB/s
  Status codes: 2xx=828850, 3xx=0, 4xx=0, 5xx=0
  Latency samples: 828850 / 828850 responses (100.0%)
[info] CPU 5873.0% | Mem 257MiB

[run 2/3]
gcannon v0.5.3
  Target:    localhost:8080/fortunes
  Threads:   64
  Conns:     1024 (16/thread)
  Pipeline:  1
  Req/conn:  unlimited (keep-alive)
  Expected:  200
  Duration:  5s


  Thread Stats   Avg      p50      p90      p99    p99.9
    Latency   4.64ms   3.96ms   8.56ms   13.10ms   17.00ms

  834628 requests in 5.00s, 834628 responses
  Throughput: 166.85K req/s
  Bandwidth:  3.87GB/s
  Status codes: 2xx=834628, 3xx=0, 4xx=0, 5xx=0
  Latency samples: 834623 / 834628 responses (100.0%)
[info] CPU 6141.3% | Mem 257MiB

[run 3/3]
gcannon v0.5.3
  Target:    localhost:8080/fortunes
  Threads:   64
  Conns:     1024 (16/thread)
  Pipeline:  1
  Req/conn:  unlimited (keep-alive)
  Expected:  200
  Duration:  5s


  Thread Stats   Avg      p50      p90      p99    p99.9
    Latency   4.77ms   4.13ms   8.64ms   13.20ms   17.30ms

  820786 requests in 5.00s, 820785 responses
  Throughput: 164.09K req/s
  Bandwidth:  3.80GB/s
  Status codes: 2xx=820785, 3xx=0, 4xx=0, 5xx=0
  Latency samples: 820782 / 820785 responses (100.0%)
[info] CPU 5962.7% | Mem 256MiB

=== Best: 166925 req/s (CPU: 6141.3%, Mem: 257MiB) ===
[info] saved results/fortunes/1024/vanilla-epoll.json
httparena-bench-vanilla-epoll
httparena-bench-vanilla-epoll
[info] skip: vanilla-epoll does not subscribe to baseline-h2
[info] skip: vanilla-epoll does not subscribe to static-h2
[info] skip: vanilla-epoll does not subscribe to baseline-h2c
[info] skip: vanilla-epoll does not subscribe to json-h2c
[info] skip: vanilla-epoll does not subscribe to baseline-h3
[info] skip: vanilla-epoll does not subscribe to static-h3
[info] skip: vanilla-epoll does not subscribe to gateway-64
[info] skip: vanilla-epoll does not subscribe to gateway-h3
[info] skip: vanilla-epoll does not subscribe to production-stack
[info] skip: vanilla-epoll does not subscribe to unary-grpc
[info] skip: vanilla-epoll does not subscribe to unary-grpc-tls
[info] skip: vanilla-epoll does not subscribe to stream-grpc
[info] skip: vanilla-epoll does not subscribe to stream-grpc-tls
[info] skip: vanilla-epoll does not subscribe to echo-ws
[info] skip: vanilla-epoll does not subscribe to echo-ws-pipeline
[info] rebuilding site/data/*.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/frameworks.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/api-16-1024.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/api-4-256.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/async-db-1024.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/baseline-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/baseline-512.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/crud-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/fortunes-1024.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/json-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/json-comp-16384.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/json-comp-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/json-comp-512.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/json-tls-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/limited-conn-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/limited-conn-512.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/pipelined-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/pipelined-512.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/static-1024.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/static-4096.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/static-6800.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/upload-256.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/upload-32.json
[updated] /home/diogo/actions-runner/_work/HttpArena/HttpArena/site/data/current.json
[info] done
httparena-postgres
httparena-redis
[info] restoring loopback MTU to 65536

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant