Skip to content

feat(bench/http): Bier vs PostgREST HTTP benchmark harness#68

Merged
milmazz merged 8 commits into
mainfrom
fix/bench-http-load-stability
Jul 13, 2026
Merged

feat(bench/http): Bier vs PostgREST HTTP benchmark harness#68
milmazz merged 8 commits into
mainfrom
fix/bench-http-load-stability

Conversation

@milmazz

@milmazz milmazz commented Jul 12, 2026

Copy link
Copy Markdown
Owner

Summary

  • Adds a k6-driven benchmark harness (bench/http/) comparing Bier against PostgREST v14.12 across read (single-row PK, filtered page) and write (insert, update) scenarios
  • Includes a first complete run report (bench/http/REPORT.md) and the design spec/plan under docs/superpowers/

Test plan

  • bench/http/run.sh completes a full run end-to-end (see REPORT.md results)
  • Not part of mix test / CI — standalone harness, no lib/ changes

🤖 Generated with Claude Code

milmazz and others added 8 commits July 9, 2026 11:46
k6-driven native macOS benchmark (run.sh) comparing Bier and PostgREST
v14.12 across read/write scenarios, with parity checks, closed-loop
ceiling calibration, and open-loop latency measurement; plus the design
spec, plan, and generated REPORT.md.

Two stability fixes over the initial harness:
- run.sh raises `ulimit -n` to 65536 before launching either server;
  the open-loop latency burst otherwise hit macOS's 256-fd default and
  crashed the Bier BEAM with :emfile.
- Split the arrival-rate fraction per scenario class (reads 0.7/0.4,
  writes 0.3/0.2): the closed-loop ceiling over-predicts open-loop write
  throughput, driving PostgREST past its knee and tripping the
  dropped-iterations guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Four independent fixes, each verified by re-running the harness; together
they produced the first fully completed run (1 round, all 8 latency stages,
0 dropped iterations, 0 failed requests):

- run.sh: bounded server teardown. stop_server sent one SIGTERM and waited
  unbounded — an orphaned PostgREST once hung a run for ~5h. Now SIGTERM ->
  10s liveness poll -> SIGKILL -> reap any orphaned port listener; the EXIT
  trap sweeps both bench ports.

- scenarios.js: VU burst headroom (preAllocatedVUs 1000 -> 2000, maxVUs
  1000 -> 8000) so a server stall (PostgREST's GHC stop-the-world GC) is
  measured as open-loop queueing tail latency instead of shedding load and
  voiding the stage.

- run.sh: ephemeral-port drain guard before each open-loop stage. k6's
  arrival-rate executors round-robin iterations across the whole VU pool,
  opening ~2x preAllocatedVUs keep-alive connections per stage regardless of
  concurrency; on exit they all sit in client-side TIME_WAIT (30s), and
  back-to-back short stages exhausted macOS's 16,384-port ephemeral range
  (EADDRNOTAVAIL at the 5th consecutive stage). Wait for TIME_WAIT toward
  the bench ports to drop below 1000 before each latency stage.

- run.sh: full-run RATE_FRACTION_READ 0.7 -> 0.4, calibrated by probing
  PostgREST's open-loop knee (r1, 60s windows): 0.5-0.7 of the closed-loop
  ceiling queue-spiral into its 10s pool-acquisition timeout 100% of the
  time; 0.4 sustains with zero drops while still capturing GC pauses as
  p90/p99 tail. WARMUP/DURATION/ROUNDS/VUS are now env-overridable around
  the per-mode defaults (e.g. ROUNDS=1 for a single-round full run).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ZEyhWcdyTwk4e7M2qQrPo
…67 rerun report

The 0.4 read fraction is only sustainable against a cold-probed ceiling:
over the ~40-min run the rig drifts and PostgREST queue-spiraled in round 3
at the same arrival rate (p99 74ms -> 556ms -> 2.7s). 0.3 keeps the
late-run effective fraction at or below the cold 0.4 knee. Also pin k6's
name tag to URL templates so id-randomizing scenarios stop minting >100k
time series per stage.

REPORT.md regenerated post-#67 (3 rounds, clean): R2 flips from 0.80x to
3.85x throughput; Bier now leads max throughput in all four scenarios.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… tight M2 tails

~80k updates per M2 window tripped autovacuum every write stage; when the
pass landed inside a measurement window it inflated whichever server was
being measured (p99 5ms -> ~50-134ms, p50/p90 flat) — run 2 hit Bier,
run 3 hit PostgREST. TRUNCATE + post-reset ANALYZE already restore
identical state, so autovacuum only added nondeterminism.

Run 4 with the fix: M2 p99 tight across all rounds for both servers
(Bier 2.38ms 2.36-3.47, PostgREST 6.22ms 5.90-12.19). Throughput and
p50/p90 reproduce runs 2-4; read-side p99 remains rig-noisy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he bench harness

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@milmazz
milmazz merged commit ab5682c into main Jul 13, 2026
3 checks passed
@milmazz
milmazz deleted the fix/bench-http-load-stability branch July 13, 2026 00:11
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