4.1 kB 99 lines
c4429a14
⋯
bench/results/write-latency.md
download raw history
at main Markdown

write latency #

scripts/write-latency-bench.sh <knot-static binary> (<knot config.toml> | --fixture <repos>)

an owner issue comment from spawn to exit, through writes::perform (the cgi path minus auth), with a firehose socket configured as on a deployed knot. budget: p95 <= 2000 ms (BUDGET_MS) on the single-writer scenarios; the script exits 1 when one is over. concurrent scenarios are reported, not gated. RUNS writes per scenario (default 5), KNOT_PROFILE=1 with a marks build prints the average breakdown.

scenarios:

  • quiet: nothing else running.
  • mid-render: a site hook stub that sleeps and takes 3 s to die on TERM; each write supersedes it.
  • during-drain: post-activate (the drain timer's work) started alongside each write.
  • first-after-deploy: a fresh copy of the binary, its prepare (the deploy unit), then the write.
  • 2 / 5 concurrent: writes started together to different repos (up to 5 that take an owner issue) or all to one.
  • huge repo (a kernel-sized knot): not run. prod hosts nothing near that size.

BENCH_REPOS="r1 r2 ..." picks the repos (the first takes the single-writer scenarios). before each run here, gc --force cleared what earlier runs retired: every write retires a generation, and until gc can tell nothing still reads them they wait out the drain window and pile up.

2026-11-10, mac (Apple M5, 10 cores, 24 GB, apfs), fc741ae release, no mbx #

3-repo fixture (--fixture 3):

scenario med ms p95 ms
quiet 291 307
mid-render 313 323
during-drain 343 419
first-after-deploy 360 363
2 concurrent, different 621 875
2 concurrent, same 621 678
5 concurrent, same 991 1238

92-repo clone of site-work:

scenario med ms p95 ms max ms
quiet 1311 1459 1459
mid-render 1731 1797 1797
during-drain 1739 1807 1807
first-after-deploy 1834 1930 1930
2 concurrent, different 4364 5762 5762
2 concurrent, same 3772 3949 3949
5 concurrent, different 11359 21062 21063
5 concurrent, same 5604 28598 43230

concurrent writes queue on the output lock, behind each other's builds and each write's detached gc + hook diff.

2026-11-10, chernobog (Ryzen 5 5600, 12 threads, btrfs), fc741ae nix build, 6G fence, nice 19 #

92-repo clone of site-work:

scenario med ms p95 ms
quiet 651 670
mid-render 868 873
during-drain 737 909
first-after-deploy 747 781
2 concurrent, different 1715 2232
2 concurrent, same 1541 1624
5 concurrent, different 4515 4626
5 concurrent, same 4134 4323

a repo that took many writes, chernobog #

a 5-repo fixture whose r1 took N owner comments one write at a time (seed.sh, not committed), nix builds of 5f72bd9 (before) and c79f9ca (after: an export merges the packs past its framed replies geometrically). each write used to add a pack to r1's export for good.

before after
r1 export packs at 500 writes 807 7
r1 export packs at 2000 writes 2036 7
one comment on r1 at 500 writes 0.77-0.83 s 0.48-0.64 s

the scenario bench at 2000 writes, r1 first:

scenario before med / p95 ms after med / p95 ms
quiet 2591 / 2760 1180 / 1189
mid-render 3049 / 3155 1190 / 1219
during-drain 3258 / 3349 1223 / 1462
first-after-deploy 3524 / 3665 1266 / 1297
2 concurrent, different 2560 / 5599 1606 / 2501
2 concurrent, same 4187 / 4299 2393 / 2468
5 concurrent, different 5954 / 7647 4069 / 5143
5 concurrent, same 5555 / 5684 3672 / 3802

a comment on r1 still costs more than on a fresh repo (1.2 s against 0.23 s): what else grows with a repo's write count is the next thing to profile.

concurrent writes share builds already: of five at once, some build and the rest find their change built by a later one. they wait out about two builds, so they speed up with the build itself.