Skip to content

Latest commit

 

History

History
754 lines (571 loc) · 52.9 KB

File metadata and controls

754 lines (571 loc) · 52.9 KB

Cycles Protocol Server - Benchmark History

Performance benchmark history across versions. All benchmarks use CyclesProtocolBenchmarkTest, CyclesProtocolReadBenchmarkTest, and CyclesProtocolConcurrentBenchmarkTest (Spring Boot + Jedis + Redis 7 via Testcontainers). 200 measured iterations after 50 warmup per operation.

Results are environment-dependent. Use for relative comparison across versions on the same hardware, not as absolute SLA targets. Latencies include the full HTTP round-trip: Spring Boot request handling, auth filter, JSON serialization, Redis EVALSHA, Lua execution, and response building.

Run benchmarks: mvn test -Pbenchmark (requires Docker).

Release coverage

Benchmarks are captured when a release contains hot-path code changes (Lua scripts, repository methods, controller logic). They are deliberately skipped for releases whose changes are wire-format-neutral or test-only — running them would only measure environmental noise. Skipped releases:

  • v0.1.25.8 — admin-on-behalf-of dual-auth (new auth filter, no change to the reserve/commit/release hot path).
  • v0.1.25.9 — test additions only, no production code changes.
  • v0.1.25.10 — metrics instrumentation (Micrometer counter increments on success/failure paths; no Redis/Lua changes). The ReservationExpiryService prefix fix is off the request hot path (runs in the background sweep).
  • v0.1.25.11 — test-only release (concurrent-retry / counter- accuracy regression tests).
  • v0.1.25.12sort_by + sort_dir on GET /v1/reservations list endpoint. The sorted path is opt-in (clients must pass the new params to activate it); legacy list behaviour is byte-for-byte unchanged and all existing benchmarks exercise the legacy path. Population benchmarks for the sorted path were added after v0.1.25.52 under #240; see the frozen pre-index baseline below.
  • v0.1.25.13 — hydration cap + enum wire annotations on the sorted list path. Write-path unchanged.
  • v0.1.25.14 — trace_id (W3C Trace Context) correlation. The new TraceContextFilter adds two regex matches + one secure-random read per request (~microseconds, no Redis / no locking / no allocation beyond a 32-char String). Request-path code only; Lua / reserve / commit / release / extend hot paths untouched. Benchmarks deliberately skipped — they would only measure environmental noise. [benchmark-skip]
  • v0.1.25.56 — distributed leases and bounded metrics for scheduled Redis maintenance only. No HTTP controller, request repository, ledger Lua, or reservation-list path changed. [benchmark-skip]
  • v0.1.25.57 — integration-only lost-response and rolling-upgrade Redis matrices. Production Java, Lua, Redis commands, and HTTP paths are unchanged. [benchmark-skip]
  • v0.1.25.58 — successful direct-event replays add one in-memory Micrometer counter increment. Fresh event, Redis, Lua, controller, and wire paths are unchanged. [benchmark-skip]

Last benchmarked release: v0.1.25.55.


v0.1.25.55 — Typed reservation-query boundary

Date: 2026-07-14

Tag: v0.1.25.55 (PR follow-up for #243; tag after merge)

Environment: Windows 11 Pro for Workstations, AMD Ryzen Threadripper 3990X 64-Core, Java 21, Docker + Redis 7 (Testcontainers)

Method: Paired same-host comparison: three complete benchmark trials of the candidate working tree and three trials from an isolated worktree at the merged v0.1.25.54 commit. Every trial completed all 17 cases with zero errors; each value below is the median of its three trials. The protocol YAML was provided through the validator's supported local-file override after the remote raw-GitHub fetch was unavailable.

Hot-path change: reservation listing crosses a typed immutable query boundary and uses a shared hash mapper. Redis commands, Lua, projections, candidate batch sizes, cursor encoding, and index readiness checks are otherwise unchanged.

Metric v0.1.25.54 paired median v0.1.25.55 median Delta
Sorted list, 1k p50 17.0ms 17.0ms 0.0%
Sorted list, 10k p50 16.9ms 17.6ms +4.1%
Reserve p50 15.6ms 14.0ms -10.3%
Reserve p99 21.3ms 18.6ms -12.7%
Commit p50 14.1ms 12.6ms -10.6%
Commit p99 23.8ms 20.3ms -14.7%
Release p50 14.4ms 14.0ms -2.8%
Event p50 14.3ms 13.6ms -4.9%
32-thread throughput 561.6 ops/s 794.2 ops/s +41.4%

Assessment: the refactored listing path is performance-neutral. Its 1k median is identical and its 10k median moved +4.1%, far below the 25% release threshold while retaining the flat population curve. The apparent write-path and throughput improvements are not attributed to this change: those paths were untouched, and the 32-thread trials contained host stalls, making their spread a measurement-noise indicator rather than an optimization result.


v0.1.25.54 — Completeness-gated created-at index

Date: 2026-07-14

Tag: v0.1.25.54 (PR follow-up for #240; tag after merge)

Environment: Windows 11 Pro for Workstations, AMD Ryzen Threadripper 3990X 64-Core, Java 21, Docker + Redis 7 (Testcontainers)

Method: Median of three complete mvn test -Pbenchmark trials; every trial completed all 17 write, read, and concurrency cases with zero errors. Sorted fixtures and request shape are byte-identical to the v0.1.25.53 baseline: half target-tenant rows, half unrelated rows, limit=20, created_at_ms desc, 50 warmups, then 200 measured requests.

Hot-path changes: reserve performs one additional per-tenant ZADD and, if readiness is already published, one metadata HINCRBY within its existing Lua script. The default sorted list reads 21 index candidates in one server-side batch and pipelines their projections; selective filters use bounded batches of 128. All other sort keys retain the full-SCAN implementation.

Default sorted-list latency (three-run medians)

Total rows Target rows v0.1.25.53 p50 v0.1.25.54 p50 Delta p95 p99
1,000 500 22.5ms 10.7ms -52.4% 12.8ms 14.1ms
10,000 5,000 164.9ms 11.1ms -93.3% 13.5ms 14.3ms

The 1k/10k p50 ratio fell from 7.3x to 1.04x. The remaining 0.4ms gap is consistent with the larger ZSET and fixture rather than global reservation hydration; request heap is bounded by the candidate batch.

Write and concurrency guardrails

Metric v0.1.25.52/53 reference v0.1.25.54 median Delta
Reserve p50 9.6ms 8.4ms -12.5%
Reserve p99 13.0ms 10.2ms -21.5%
32-thread lifecycle throughput 1,421.8 ops/s 1,376.0 ops/s -3.2%

v0.1.25.53 did not change the runtime path, so the v0.1.25.52 full-suite medians remain the same-host write/concurrency reference. The intended extra reserve write is below measurement noise; throughput and every parsed release metric remain comfortably inside the 25% regression threshold. The release workflow will independently compare its median against the benchmark-data runner history when the tag is cut.


v0.1.25.53 — Sorted-list scaling baseline

Date: 2026-07-14

Tag: v0.1.25.53 (benchmark-only follow-up; tag after merge)

Environment: Windows 11 Pro for Workstations, AMD Ryzen Threadripper 3990X 64-Core, Java 21, Docker + Redis 7 (Testcontainers)

Method: The runtime is unchanged from v0.1.25.52. Each fixture contains equal numbers of authenticated-tenant and unrelated-tenant reservation hashes, requests limit=20&sort_by=created_at_ms&sort_dir=desc, and measures 200 requests after 50 warmups. Fixture writes are pipelined and complete before timing starts. Values are medians of three same-host trials.

Total rows Target-tenant rows p50 p95 p99
1,000 500 22.5ms 40.9ms 48.3ms
10,000 5,000 164.9ms 210.2ms 232.0ms

Assessment: The 7.3x p50 increase for a 10x population confirms the global-SCAN/hydrate/sort path is the next material read bottleneck. These two p50s are parsed into nightly and release benchmark data so #240's planned indexed path has a machine-readable preceding-release comparison point.


v0.1.25.52 — Event replay storage and ledger-helper cleanup

Date: 2026-07-14

Tag: v0.1.25.52 (follow-up PR; tag after merge)

Environment: Windows 11 Pro for Workstations, AMD Ryzen Threadripper 3990X 64-Core, Java 21, Docker + Redis 7 (Testcontainers)

Method: Median of three complete mvn test -Pbenchmark trials; each trial completed all write, read, and concurrency benchmarks with zero errors.

Hot-path changes: new keyed events write one immutable response snapshot on the event hash instead of also writing a response-sized fast key. Normal event replays read that snapshot in one Redis command; only pre-0.1.25.52 rows without a snapshot consult the legacy fast key. Commit/event capped-charge branches call one shared per-scope marker using their already hydrated state, adding no Redis reads.

Single-Threaded Write-Path Latency (three-run medians)

Operation p50 p95 p99 min max mean
Extend 10.5ms 13.0ms 14.3ms 8.1ms 19.4ms 10.7ms
Reserve + Commit 22.5ms 26.7ms 27.5ms 19.8ms 32.1ms 22.9ms
Reserve + Release 19.5ms 23.7ms 26.2ms 15.8ms 31.1ms 19.8ms
Release 9.2ms 10.5ms 11.5ms 7.6ms 15.0ms 9.3ms
Reserve 9.6ms 11.2ms 13.0ms 7.9ms 14.9ms 9.8ms
Event 8.8ms 10.4ms 11.1ms 7.1ms 16.4ms 9.0ms
Commit 8.5ms 10.5ms 11.7ms 6.9ms 11.9ms 8.8ms
Decide 10.4ms 12.5ms 14.0ms 8.7ms 14.5ms 10.7ms

Single-Threaded Read-Path Latency (three-run medians)

Operation p50 p95 p99 min max mean
GET reservation 6.1ms 7.5ms 8.4ms 5.0ms 9.2ms 6.2ms
GET balances 6.3ms 7.7ms 8.3ms 5.2ms 9.0ms 6.4ms
LIST reservations 7.1ms 8.5ms 9.5ms 5.8ms 10.1ms 7.2ms
Decide (pipelined) 8.5ms 9.9ms 11.5ms 6.9ms 12.3ms 8.5ms

Concurrent Throughput (Reserve + Commit lifecycle; three-run medians)

Threads Total Ops Ops/sec p50 p95 p99 min max Errors
8 2,102 420.4 18.9ms 22.3ms 25.9ms 14.4ms 33.1ms 0
16 4,262 852.4 18.6ms 22.7ms 24.7ms 13.6ms 29.7ms 0
32 7,109 1,421.8 21.8ms 28.1ms 35.1ms 15.4ms 58.0ms 0

Regression assessment: Against the immediately preceding v0.1.25.51 same-host medians, reserve/commit/release/event p50 changed +12.9%/+3.7%/+5.7%/+10.0%, while 32-thread throughput improved 8.5%. All changes are below the 25% regression threshold. Untouched reserve had the largest latency increase (+12.9%), while untouched extend and all four read paths improved; that cross-path spread supports ordinary host/container variance rather than an event-path regression. The intended event optimization removes one response-sized write and expiring key; the request-latency benchmark is neutral within measurement noise. The release workflow's shared-runner rolling median remains authoritative.


v0.1.25.51 — Protocol-correct idempotency, replay, and expiry quarantine

Date: 2026-07-14

Tag: v0.1.25.51 (PR #238; tag after merge)

Environment: Windows 11 Pro for Workstations, AMD Ryzen Threadripper 3990X 64-Core, Java 21, Docker + Redis 7 (Testcontainers)

Method: Median of three complete mvn test -Pbenchmark trials; each trial completed all write, read, and concurrency benchmarks with zero errors.

Hot-path changes: reserve and dry-run now share endpoint idempotency state; live reserve performs a compatibility lookup for pre-v0.1.25.51 dry-run keys, and keyed events retain/backfill an immutable replay snapshot. The follow-up review also hardened claim expiry and value-shape handling. Expiry quarantine metadata and metrics are off the request hot path.

Single-Threaded Write-Path Latency (three-run medians)

Operation p50 p95 p99 min max mean
Extend 12.2ms 14.2ms 16.0ms 9.9ms 40.4ms 12.3ms
Reserve + Commit 23.9ms 26.7ms 33.7ms 18.2ms 51.7ms 23.8ms
Reserve + Release 21.2ms 25.2ms 27.3ms 17.9ms 52.3ms 21.4ms
Release 8.7ms 10.6ms 11.7ms 7.0ms 16.8ms 8.9ms
Reserve 8.5ms 9.9ms 12.2ms 6.9ms 37.7ms 8.7ms
Event 8.0ms 9.1ms 10.0ms 6.5ms 35.6ms 8.2ms
Commit 8.2ms 9.4ms 12.4ms 6.4ms 24.7ms 8.2ms
Decide 9.3ms 10.8ms 12.4ms 7.4ms 35.6ms 9.5ms

Single-Threaded Read-Path Latency (three-run medians)

Operation p50 p95 p99 min max mean
GET reservation 7.0ms 8.2ms 9.4ms 5.2ms 10.1ms 7.0ms
GET balances 7.0ms 8.1ms 8.8ms 5.3ms 38.1ms 7.2ms
LIST reservations 8.1ms 9.6ms 10.1ms 6.6ms 39.2ms 8.2ms
Decide (pipelined) 9.9ms 11.5ms 12.5ms 7.9ms 43.8ms 10.1ms

Concurrent Throughput (Reserve + Commit lifecycle; three-run medians)

Threads Total Ops Ops/sec p50 p95 p99 min max Errors
8 2,001 400.2 19.6ms 23.0ms 29.7ms 15.2ms 74.3ms 0
16 4,172 834.4 18.8ms 23.1ms 32.6ms 14.8ms 61.6ms 0
32 6,554 1,310.8 23.1ms 34.1ms 52.3ms 15.9ms 86.5ms 0

Regression assessment: The previous same-host v0.1.25.50 medians recorded in AUDIT.md were reserve/commit/release p50 of 14.5/13.7/14.3ms and 32-thread throughput of 698.2 ops/s. The current medians are -41.4%/-40.1%/-39.2% and +87.7%, respectively. The unusually favorable deltas are attributed to environment/container warmth, not to this correctness patch; the meaningful conclusion is that the additional compatibility lookup and snapshot handling caused no measurable regression. The shared-runner rolling median remains the release authority.


v0.1.25.7 — Typed Enums.ReasonCode refactor + flaky test fix

Date: 2026-04-11 Tag: v0.1.25.7 Base commit: (to be tagged after PR merge) Environment: Windows 11 Pro for Workstations, AMD Ryzen Threadripper 3990X 64-Core, Java 21, Docker + Redis 7 (Testcontainers)

Changes from v0.1.25.6:

  • DecisionResponse.reasonCode / ReservationCreateResponse.reasonCode retyped from free-form String to typed Enums.ReasonCode (6 values). Compile-time safety against drift from the companion spec enum in runcycles/cycles-protocol#26.
  • 10 .reasonCode(...) call sites in RedisReservationRepository (5 in evaluateDryRun, 5 in decide) updated to pass enum constants; 2 controller boundaries that feed EventDataReservationDenied (which keeps its String-typed reasonCode as that's its own webhook wire contract) now convert via .name() with a null guard.
  • EventEmitterServiceTest de-flaked (#82): 13 Thread.sleep(200) + verify() patterns replaced with Mockito timeout(5000) / after(200).never() verification modes.
  • No wire-format change. Jackson's default enum serialization produces the same JSON strings as the previous String-typed field. Zero runtime path impact expected, and observed — all deltas below are within environmental noise.

Single-Threaded Write-Path Latency

Operation p50 p95 p99 min max mean
Reserve 5.3ms 6.6ms 7.3ms 3.8ms 14.5ms 5.3ms
Commit 4.6ms 5.4ms 5.8ms 3.1ms 14.4ms 4.6ms
Release 4.8ms 5.8ms 6.5ms 3.4ms 6.9ms 4.8ms
Extend 7.5ms 9.4ms 11.0ms 5.7ms 11.8ms 7.5ms
Decide 4.7ms 5.8ms 6.3ms 3.3ms 6.8ms 4.7ms
Event 4.3ms 5.3ms 6.2ms 3.2ms 8.9ms 4.3ms
Reserve + Commit 14.0ms 16.5ms 17.6ms 9.7ms 20.6ms 14.0ms
Reserve + Release 12.1ms 14.1ms 14.9ms 8.1ms 25.0ms 12.1ms

Write-path analysis: All operations equal-or-faster than v0.1.25.6 within ±1ms (Reserve 5.3ms vs 6.0ms, Commit 4.6ms vs 5.0ms, Release 4.8ms vs 4.8ms, Extend 7.5ms vs 7.5ms, Decide 4.7ms vs 5.9ms, Event 4.3ms vs 5.0ms, Reserve+Commit 14.0ms vs 14.3ms, Reserve+Release 12.1ms vs 12.2ms). Deltas are environmental/warmth variance — the refactor is pure compile-time Java typing with Jackson serializing the enum to the same name() string the previous code emitted as a literal. No Redis calls, no Lua, no new allocation on the hot path. No regressions.

Single-Threaded Read-Path Latency

Operation p50 p95 p99 min max mean
GET reservation 4.4ms 5.3ms 5.8ms 2.1ms 7.2ms 4.4ms
GET balances 4.1ms 5.4ms 5.7ms 2.3ms 5.7ms 4.1ms
LIST reservations 4.5ms 5.5ms 5.8ms 2.5ms 7.0ms 4.4ms
Decide (pipelined) 5.8ms 6.9ms 7.1ms 3.9ms 12.0ms 5.8ms

Read-path analysis: Read paths are unchanged in code; numbers within ±1.2ms of v0.1.25.6 (GET reservation 4.4ms vs 3.7ms, GET balances 4.1ms vs 3.9ms, LIST reservations 4.5ms vs 3.9ms, Decide pipelined 5.8ms vs 4.6ms). Environmental noise on a loaded dev box, not a structural change. The Decide (pipelined) read path's slight uptick (+1.2ms) is worth watching but has no code explanation — likely JVM warmup variance since the benchmark doesn't pin CPU affinity.

Concurrent Throughput (Reserve+Commit lifecycle)

Threads Total Ops Ops/sec p50 p95 p99 Errors
8 3,933 786.6 9.9ms 12.0ms 21.9ms 0
16 5,382 1,076.4 14.7ms 20.4ms 24.5ms 0
32 13,158 2,631.6 11.8ms 16.6ms 20.7ms 0

Concurrency analysis: Throughput at 32 threads is 2,632 ops/s — essentially identical to v0.1.25.6's 2,624 ops/s (+0.3%, within noise). Scaling ratio from 8→32 threads is 3.3x (787 → 2,632), matching v0.1.25.6 and v0.1.25.5. p50/p95/p99 latencies at every concurrency level are within ±1.5ms of v0.1.25.6. Zero errors at all concurrency levels. The typed-enum refactor has zero runtime cost on the hot path — confirmed.


v0.1.25.6 — UNIT_MISMATCH vs BUDGET_NOT_FOUND detection on reserve/event/decide

Date: 2026-04-10 Tag: v0.1.25.6 Base commit: 89d2651 Environment: Windows 11 Pro for Workstations, AMD Ryzen Threadripper 3990X 64-Core, Java 21, Docker + Redis 7 (Testcontainers)

Changes from v0.1.25.5:

  • reserve.lua + event.lua — new units_csv ARGV slot; alternate-unit probe added to the #budgeted_scopes == 0 error path (returns 400 UNIT_MISMATCH with {scope, requested_unit, expected_units} when a budget exists at the scope in a different unit, else falls through to BUDGET_NOT_FOUND)
  • event.lua — defensive data-integrity branch aligned to the same response shape
  • RedisReservationRepository — shared probeAlternateUnits(jedis, scope, requestedUnit) helper used by evaluateDryRun and decide(); symmetric Java-side probe throws 400 UNIT_MISMATCH
  • No hot-path change: the probe fires only when every affected scope missed at the requested unit — benchmarks exercise the success path, so the probe is never triggered during measurement

Single-Threaded Write-Path Latency

Operation p50 p95 p99 min max mean
Reserve 6.0ms 7.3ms 7.8ms 4.0ms 8.8ms 6.0ms
Commit 5.0ms 6.2ms 6.7ms 3.5ms 11.6ms 5.0ms
Release 4.8ms 5.9ms 6.9ms 3.6ms 12.9ms 4.8ms
Extend 7.5ms 9.1ms 10.6ms 5.7ms 18.0ms 7.5ms
Decide 5.9ms 7.1ms 7.6ms 4.0ms 12.5ms 5.9ms
Event 5.0ms 6.1ms 7.4ms 3.4ms 13.2ms 5.0ms
Reserve + Commit 14.3ms 17.0ms 20.1ms 11.0ms 21.7ms 14.3ms
Reserve + Release 12.2ms 14.6ms 16.2ms 9.0ms 20.8ms 12.2ms

Write-path analysis: All operations equal-or-faster than v0.1.25.5 (Reserve 6.0ms vs 7.2ms, Commit 5.0ms vs 5.8ms, Release 4.8ms vs 6.0ms, Extend 7.5ms vs 9.0ms, Decide 5.9ms vs 6.3ms, Event 5.0ms vs 6.2ms, Reserve+Commit 14.3ms vs 16.7ms, Reserve+Release 12.2ms vs 14.2ms). The deltas are environmental/warmth variance — the probe block added to reserve.lua / event.lua only executes when #budgeted_scopes == 0, which never fires during benchmark runs (all measured requests hit valid budgets). The extra units_csv ARGV string per request is parsed only if the probe fires, so the hot path ignores it. No regressions; no measurable overhead from the fix.

Single-Threaded Read-Path Latency

Operation p50 p95 p99 min max mean
GET reservation 3.7ms 4.8ms 5.3ms 2.4ms 5.5ms 3.7ms
GET balances 3.9ms 4.7ms 4.9ms 2.3ms 6.1ms 3.9ms
LIST reservations 3.9ms 5.0ms 6.0ms 2.4ms 6.6ms 3.9ms
Decide (pipelined) 4.6ms 5.5ms 5.7ms 3.1ms 5.9ms 4.6ms

Read-path analysis: No read-path code was changed. Numbers are within ±0.4ms of v0.1.25.5 (GET reservation 3.7ms vs 3.5ms, GET balances 3.9ms vs 3.6ms, LIST reservations 3.9ms vs 3.9ms, Decide pipelined 4.6ms vs 4.2ms) — environmental noise, no structural change.

Concurrent Throughput (Reserve+Commit lifecycle)

Threads Total Ops Ops/sec p50 p95 p99 Errors
8 3,947 789.4 9.9ms 12.1ms 17.6ms 0
16 5,612 1,122.4 14.3ms 19.8ms 22.8ms 0
32 13,120 2,624.0 11.8ms 16.5ms 20.5ms 0

Concurrency analysis: Throughput at 32 threads is 2,624 ops/s — within 4% of v0.1.25.5's 2,520 ops/s (slightly higher, well within run-to-run noise). Scaling ratio from 8→32 threads is 3.3x (789 → 2,624), consistent with v0.1.25.5 (3.3x) and prior versions. p99 latency at 32 threads (20.5ms) improved vs v0.1.25.5's 34.5ms — again, environmental variance on a warm system. Zero errors at all concurrency levels. The wrong-unit probe has zero cost on the success path; its cost is paid only when a reservation/event/decide request misses every affected scope at the requested unit (a 4xx error path that no benchmark workload exercises).


v0.1.25.5 — Transition-Based Event Emission (Duplicate Event Fix)

Date: 2026-04-08 Branch: release/v0.1.25.5 Base commit: 9b2f4e1 Environment: Windows 11 Pro, AMD Ryzen Threadripper 3990X 64-Core, Java 21.0.5, Docker 29.3.1, Redis 7 (Testcontainers)

Changes from v0.1.25.4:

  • Lua scripts (reserve, commit, event) include pre-mutation pre_remaining and pre_is_over_limit per scope in balance snapshots
  • Java emits budget state events only on state transitions (not every matching post-state)
  • commit.lua ALLOW_IF_AVAILABLE path: HGET → HMGET (remaining + is_over_limit in one call)
  • commit.lua ALLOW_WITH_OVERDRAFT path: added is_over_limit to existing HMGET (4 → 5 fields)
  • event.lua: folded is_over_limit into existing HMGET (5 → 6 fields, removed separate HGET)
  • No extra Redis calls on any path

Single-Threaded Write-Path Latency

Operation p50 p95 p99 min max mean
Reserve 7.2ms 8.3ms 8.7ms 5.8ms 16.1ms 7.2ms
Commit 5.8ms 6.6ms 7.4ms 4.6ms 14.2ms 5.8ms
Release 6.0ms 6.9ms 7.2ms 4.4ms 14.2ms 6.0ms
Extend 9.0ms 10.3ms 12.9ms 7.4ms 19.3ms 9.0ms
Decide 6.3ms 7.5ms 8.4ms 4.4ms 10.5ms 6.3ms
Event 6.2ms 7.3ms 7.7ms 4.4ms 8.2ms 6.2ms
Reserve + Commit 16.7ms 18.9ms 20.6ms 13.0ms 28.5ms 16.7ms
Reserve + Release 14.2ms 17.2ms 21.3ms 11.5ms 25.3ms 14.2ms

Write-path analysis: All operations consistent with v0.1.25.4. Reserve (7.2ms vs 5.7ms), Commit (5.8ms vs 4.7ms), Release (6.0ms vs 4.8ms), Extend (9.0ms vs 7.6ms), Decide (6.3ms vs 5.5ms), Event (6.2ms vs 5.1ms) — all within normal container/JVM warmth variance across benchmark sessions. The pre-state caching in Lua adds zero extra Redis calls: reserve.lua caches from its existing validation HMGET, commit.lua from existing overage-path reads, event.lua folds is_over_limit into its existing HMGET. No regressions.

Single-Threaded Read-Path Latency

Operation p50 p95 p99 min max mean
GET reservation 3.5ms 4.3ms 4.8ms 2.0ms 5.2ms 3.5ms
GET balances 3.6ms 4.5ms 4.8ms 2.1ms 4.9ms 3.6ms
LIST reservations 3.9ms 4.6ms 4.8ms 2.4ms 4.9ms 3.8ms
Decide (pipelined) 4.2ms 5.0ms 5.5ms 2.9ms 6.2ms 4.2ms

Read-path analysis: No read-path code was changed. All operations consistent with v0.1.25.4 (GET reservation 3.5ms vs 3.8ms, GET balances 3.6ms vs 4.0ms). No regressions.

Concurrent Throughput (Reserve+Commit lifecycle)

Threads Total Ops Ops/sec p50 p95 p99 min max Errors
8 3,764 752.8 10.3ms 12.8ms 23.2ms 7.4ms 32.0ms 0
16 5,316 1,063.2 14.7ms 21.4ms 26.0ms 6.8ms 51.5ms 0
32 12,599 2,519.8 11.5ms 21.2ms 34.5ms 6.7ms 67.6ms 0

Concurrency analysis: Throughput at 32 threads is 2,520 ops/s — within 5% of v0.1.25.4's 2,655 ops/s. The scaling ratio from 8→32 threads is 3.3x (753 → 2,520), consistent with prior versions (v0.1.25.4: 3.4x, v0.1.25.3: 3.5x). p99 at 32 threads (34.5ms) is comparable to v0.1.25.4's 29.8ms. Zero errors at all concurrency levels. The transition fix adds no measurable overhead — pre-state caching piggybacks on existing Lua reads with zero extra Redis calls. Earlier runs on this day showed degraded numbers (~725 ops/s) due to TuneupUI background processes consuming CPU; after termination, normal throughput restored.


v0.1.25.4 — Event Data Payload Completeness

Date: 2026-04-07 Branch: release/v0.1.25.4 Base commit: 175f2fc Environment: Windows 11 Pro, AMD Ryzen Threadripper 3990X 64-Core, Java 21.0.5, Docker 29.3.1, Redis 7 (Testcontainers)

Changes from v0.1.25.3:

  • Populated all missing EventData fields for 6 runtime event types (admin spec compliance)
  • Added per-scope scope_debt_incurred tracking in commit.lua and event.lua (1 extra table insert per overdraft scope)
  • commit.lua reads 1 additional field (scope_path) in initial HMGET and returns 2 extra fields in response JSON
  • Added buildActor() helper extracting keyId/sourceIp from auth context per request
  • ObjectMapper.convertValue calls for action/subject maps on DENY path only (non-hot-path)
  • No changes to reserve.lua, release.lua, extend.lua, or expire.lua

Single-Threaded Write-Path Latency

Operation p50 p95 p99 min max mean
Reserve 5.7ms 6.6ms 6.9ms 4.4ms 7.2ms 5.7ms
Commit 4.7ms 5.6ms 5.9ms 3.2ms 6.5ms 4.7ms
Release 4.8ms 5.8ms 6.0ms 3.2ms 6.3ms 4.8ms
Extend 7.6ms 9.4ms 10.3ms 5.5ms 16.8ms 7.7ms
Decide 5.5ms 6.5ms 8.0ms 3.5ms 16.1ms 5.7ms
Event 5.1ms 6.0ms 6.6ms 3.9ms 14.0ms 5.1ms
Reserve + Commit 14.0ms 15.9ms 19.8ms 10.9ms 23.0ms 14.1ms
Reserve + Release 11.7ms 13.7ms 17.0ms 9.4ms 20.4ms 11.8ms

Write-path analysis: All write operations are within noise of v0.1.25.3. Reserve (5.7ms vs 6.2ms), Commit (4.7ms vs 4.1ms), Release (4.8ms vs 4.8ms), Extend (7.6ms vs 7.4ms), Decide (5.5ms vs 5.5ms), Event (5.1ms vs 5.2ms) — all within normal environmental variance. The extra HMGET field in commit.lua (scope_path) and the scope_debt_incurred table insert add no measurable overhead — both are in-memory Lua operations on a single Redis thread. The buildActor() helper is a lightweight SecurityContext lookup (no I/O). The ObjectMapper.convertValue for action/subject maps only executes on the DENY path (not the benchmark happy path). No regressions detected.

Single-Threaded Read-Path Latency

Operation p50 p95 p99 min max mean
GET reservation 3.8ms 4.7ms 5.2ms 2.3ms 5.8ms 3.8ms
GET balances 4.0ms 4.9ms 5.3ms 2.4ms 5.4ms 4.0ms
LIST reservations 4.3ms 5.2ms 5.3ms 2.8ms 5.8ms 4.3ms
Decide (pipelined) 5.1ms 6.2ms 6.5ms 3.6ms 6.6ms 5.1ms

Read-path analysis: Read operations are slightly higher than v0.1.25.3 (GET reservation 3.8ms vs 2.8ms, GET balances 4.0ms vs 2.9ms) — environmental variance from container state. No read-path code was changed. These numbers remain well within acceptable range and are consistent with v0.1.25.1 baselines (GET reservation 4.0ms, GET balances 4.1ms).

Concurrent Throughput (Reserve+Commit lifecycle)

Threads Total Ops Ops/sec p50 p95 p99 min max Errors
8 3,945 789.0 9.9ms 12.0ms 21.1ms 7.5ms 25.7ms 0
16 5,506 1,101.2 14.2ms 19.8ms 24.2ms 7.0ms 36.5ms 0
32 13,277 2,655.4 11.3ms 17.6ms 29.8ms 7.1ms 66.0ms 0

Concurrency analysis: Throughput at 32 threads is 2,655 ops/s — within 8% of v0.1.25.3's 2,873 ops/s, attributable to environmental variance. The scaling ratio from 8→32 threads is 3.4x (789 → 2,655 ops/s), consistent with prior versions (v0.1.25.3: 3.5x, v0.1.25.1: 3.2x). p99 at 32 threads (29.8ms) is higher than v0.1.25.3's 19.3ms but comparable to v0.1.24.3's 22.7ms — container GC variance. Zero errors at all concurrency levels. The benchmark happy-path lifecycle does not trigger overdraft logic, so the new scope_debt_incurred table tracking is a no-op during benchmarks. Real-world overhead for overdraft commits would be one additional Lua table insert per scope — negligible compared to Redis I/O.


v0.1.25.3 — Extended Runtime Event Emission + PROTOCOL_VERSION Fix

Date: 2026-04-03 Branch: release/v0.1.25.3 Base commit: 32293e4 Environment: Windows 11 Pro, AMD Ryzen Threadripper 3990X 64-Core, Java 21.0.5, Docker 29.3.1, Redis 7 (Testcontainers)

Changes from v0.1.25.1:

  • 4 new async event emissions: budget.exhausted, budget.over_limit_entered, budget.debt_incurred, reservation.expired
  • EventEmitterService.emitBalanceEvents() inspects post-operation balances (no extra Redis calls)
  • ReservationExpiryService.emitExpiredEvent() adds 1 HGETALL per expired reservation for event payload
  • Fixed PROTOCOL_VERSION constant: "0.1.24" → "0.1.25"
  • No changes to Lua scripts or core hot-path logic

Single-Threaded Write-Path Latency

Operation p50 p95 p99 min max mean
Reserve 6.2ms 7.3ms 7.9ms 4.6ms 16.3ms 6.3ms
Commit 4.1ms 5.2ms 5.7ms 3.1ms 6.3ms 4.1ms
Release 4.8ms 6.1ms 6.5ms 3.2ms 6.9ms 4.9ms
Extend 7.4ms 9.2ms 10.2ms 5.4ms 19.3ms 7.5ms
Decide 5.5ms 6.7ms 7.0ms 3.8ms 20.5ms 5.6ms
Event 5.2ms 6.2ms 6.9ms 3.3ms 8.4ms 5.2ms
Reserve + Commit 14.9ms 17.5ms 18.4ms 10.8ms 23.7ms 14.6ms
Reserve + Release 11.4ms 15.5ms 16.7ms 8.7ms 23.8ms 11.9ms

Write-path analysis: All write operations are within noise of v0.1.25.1, confirming the new emitBalanceEvents() calls add zero measurable overhead. Commit improved slightly (4.1ms vs 5.6ms) — environmental variance from a warmer container. Reserve (6.2ms vs 6.9ms), Event (5.2ms vs 6.0ms), and Release (4.8ms vs 5.8ms) all show minor improvements attributable to container warmth. The new balance event emission runs on the existing async thread pool and only iterates the in-memory balances list (no Redis calls), so it does not touch the request hot path. Extend (7.4ms vs 8.6ms) is consistent with its Lua script complexity. No regressions detected.

Single-Threaded Read-Path Latency

Operation p50 p95 p99 min max mean
GET reservation 2.8ms 3.6ms 4.0ms 2.0ms 5.3ms 2.8ms
GET balances 2.9ms 3.7ms 3.9ms 2.1ms 4.0ms 2.9ms
LIST reservations 3.3ms 4.6ms 5.2ms 2.3ms 5.9ms 3.4ms
Decide (pipelined) 3.5ms 4.5ms 5.7ms 2.8ms 6.9ms 3.6ms

Read-path analysis: Read operations improved from v0.1.25.1 (GET reservation 2.8ms vs 4.0ms, GET balances 2.9ms vs 4.1ms, LIST 3.3ms vs 5.0ms, Decide 3.5ms vs 5.6ms). No read-path code was changed, so these improvements are environmental — the v0.1.25.1 benchmark session had higher GC pressure from a different container state. These numbers are now comparable to v0.1.24.3 baselines (GET reservation 2.8ms vs 2.8ms, GET balances 2.9ms vs 2.1ms), confirming the event emission infrastructure adds no sustained overhead to read operations.

Concurrent Throughput (Reserve+Commit lifecycle)

Threads Total Ops Ops/sec p50 p95 p99 min max Errors
8 4,082 816.4 9.6ms 11.6ms 21.0ms 7.1ms 24.5ms 0
16 5,810 1,162.0 13.7ms 19.2ms 22.4ms 6.1ms 28.7ms 0
32 14,363 2,872.6 10.8ms 15.1ms 19.3ms 6.6ms 43.1ms 0

Concurrency analysis: Throughput at 32 threads is 2,873 ops/s — an 11% improvement over v0.1.25.1's 2,584 ops/s and 13% over v0.1.24.3's 2,534 ops/s. This is environmental variance (warmer container, Docker engine update from 29.2.1 to 29.3.1), not a code improvement. The scaling ratio from 8→32 threads is 3.5x (816 → 2,873 ops/s), consistent with prior versions. p99 at 32 threads (19.3ms) improved from v0.1.25.1's 27.2ms and v0.1.24.3's 22.7ms. Max latency (43.1ms) is consistent with expected Redis Lua serialization tail. Zero errors at all concurrency levels confirms the new balance event emission does not introduce contention — emitBalanceEvents() only reads the in-memory balance list on the async thread pool, never competing for Redis connections on the request path. The benchmark happy-path lifecycle (reserve+commit with sufficient budget) does not trigger any of the new events (no exhaustion, no over-limit, no debt), so the emit code path is a no-op during benchmarks. Real-world overhead would be one additional emit() call per triggered condition per scope — the same async fire-and-forget path already validated in v0.1.25.1.


v0.1.25.1 — Webhook Event Emission + TTL Retention

Date: 2026-04-01 Branch: claude/server-events-implementation-uEXga Base commit: 06134dd Environment: Windows 11 Pro, AMD Ryzen Threadripper 3990X 64-Core, Java 21.0.5, Docker 29.2.1, Redis 7 (Testcontainers)

Changes from v0.1.24.3:

  • Webhook event emission on reservation deny and commit overage (EventEmitterService + EventEmitterRepository)
  • 11 event model classes + 6 webhook model classes added
  • CryptoService for AES-256-GCM webhook signing secret encryption
  • Event/delivery TTL retention (90d/14d configurable)
  • RetentionCleanupService for hourly ZSET index trimming
  • No changes to core Lua scripts or hot-path Redis operations

Performance optimizations (post-initial benchmark):

  • Fixed commit overage emit condition: was firing on every commit (actual != null), now only fires when charged > estimated — eliminates emit overhead on normal commits
  • Async event emission: EventEmitterService uses CompletableFuture.runAsync() on a dedicated daemon thread pool, so emit never blocks the request thread
  • Pipelined Redis commands in EventEmitterRepository: event save + subscription lookup in 1 round-trip (was 6 sequential commands), subscription GETs pipelined, delivery creation pipelined

Single-Threaded Write-Path Latency

Operation p50 p95 p99 min max mean
Reserve 6.9ms 7.8ms 8.0ms 5.8ms 13.2ms 7.0ms
Commit 5.6ms 6.5ms 6.7ms 4.5ms 7.5ms 5.7ms
Release 5.8ms 6.5ms 6.8ms 4.6ms 7.0ms 5.7ms
Extend 8.6ms 10.1ms 13.3ms 5.9ms 16.3ms 8.8ms
Decide 6.5ms 7.3ms 8.2ms 5.2ms 17.5ms 6.6ms
Event 6.0ms 6.7ms 7.0ms 4.9ms 8.5ms 6.0ms
Reserve + Commit 16.0ms 17.9ms 19.0ms 13.4ms 22.4ms 16.1ms
Reserve + Release 14.0ms 16.0ms 18.3ms 10.5ms 25.6ms 14.1ms

Write-path analysis: After fixing the emit condition and making emission async, write-path latencies are within noise of v0.1.24.3. Commit dropped from 13.4ms (pre-fix, when every commit triggered a synchronous emit with 6 Redis round-trips) to 5.6ms — now comparable to v0.1.24.3's 4.9ms. The remaining ~0.7ms delta is environmental variance (different container state between benchmark runs). Reserve (6.9ms vs 5.9ms) and Event (6.0ms vs 4.5ms) show small increases attributable to the additional Spring bean initialization overhead, not per-request cost. The buggy emit condition was the dominant cause of the initial 173% commit regression — with the fix, the event emission system adds near-zero overhead to normal (non-overage) operations. Extend remains the slowest operation (8.6ms p50) due to its Lua script complexity, unchanged from v0.1.24.3's 6.9ms (environmental delta).

Single-Threaded Read-Path Latency

Operation p50 p95 p99 min max mean
GET reservation 4.0ms 4.6ms 4.9ms 2.7ms 7.4ms 4.0ms
GET balances 4.1ms 4.9ms 5.2ms 2.7ms 6.0ms 4.0ms
LIST reservations 5.0ms 5.8ms 6.2ms 4.2ms 9.8ms 5.0ms
Decide (pipelined) 5.6ms 6.3ms 6.5ms 4.4ms 7.0ms 5.6ms

Read-path analysis: Read operations show a slight increase from v0.1.24.3 (GET reservation 4.0ms vs 2.8ms, GET balances 4.1ms vs 2.1ms). Since no read-path code was changed in v0.1.25, this delta is environmental: the additional Spring beans (EventEmitterService, CryptoService, RetentionCleanupService) increase the application footprint and may cause slightly more GC pressure. The relative ordering is preserved — GET balances remains the fastest read, Decide (pipelined) remains the slowest due to multi-scope HGETALL batching. These numbers remain well within acceptable latency bounds for a budget authority API.

Concurrent Throughput (Reserve+Commit lifecycle)

Threads Total Ops Ops/sec p50 p95 p99 min max Errors
8 3,922 784.4 10.0ms 12.1ms 18.1ms 7.0ms 34.7ms 0
16 5,447 1,089.4 14.5ms 20.1ms 23.7ms 7.1ms 32.8ms 0
32 12,922 2,584.4 11.7ms 18.2ms 27.2ms 7.0ms 53.0ms 0

Concurrency analysis: Throughput at 32 threads is 2,584 ops/s — fully recovered from the pre-fix 1,801 ops/s and matching v0.1.24.3's 2,534 ops/s. The 50 ops/s improvement over v0.1.24.3 is within noise but confirms the async emit + pipelined Redis commands add no measurable overhead to the hot path. The scaling ratio is 3.3x from 8→32 threads (784 → 2,584 ops/s), consistent with prior versions. Zero errors at all concurrency levels. Max latency at 32 threads (53.0ms) is a single outlier — p99 (27.2ms) is comparable to v0.1.24.3's 22.7ms. The async emit thread pool (daemon threads, availableProcessors/4 size) does not compete with request threads for Redis connections since emission only fires on actual deny/overage events, which don't occur during the benchmark's happy-path lifecycle.


v0.1.24.3 — Performance Optimizations

Date: 2026-03-30 Branch: feature/performance-optimizations Base commit: 0555529 Environment: Windows 11 Pro, AMD Ryzen Threadripper 3990X 64-Core, Java 21.0.5, Docker 29.2.1, Redis 7 (Testcontainers)

Changes from v0.1.24.2:

  • Lua HMGET consolidation (expire, extend, commit, event scripts)
  • Caffeine caches replacing ConcurrentHashMap (ApiKeyRepository, tenant config)
  • ThreadLocal MessageDigest in ApiKeyRepository
  • Pipelined idempotency reads/writes (dry_run, decide)
  • Connection pool tuning (128/32/16, testWhileIdle, idle eviction)
  • Script loading via readAllBytes
  • expire.lua guard condition bugfix

Single-Threaded Write-Path Latency

Operation p50 p95 p99 min max mean
Reserve 5.9ms 7.1ms 7.4ms 4.4ms 7.6ms 5.9ms
Commit 4.9ms 6.1ms 12.2ms 3.4ms 20.9ms 5.1ms
Release 4.8ms 6.2ms 6.9ms 3.5ms 13.9ms 4.9ms
Extend 6.9ms 8.7ms 9.3ms 5.3ms 10.8ms 7.1ms
Decide 5.8ms 6.8ms 7.1ms 4.0ms 7.1ms 5.7ms
Event 4.5ms 5.6ms 6.0ms 3.5ms 12.0ms 4.6ms
Reserve + Commit 14.3ms 17.2ms 18.3ms 11.3ms 22.0ms 14.4ms
Reserve + Release 13.2ms 15.6ms 18.4ms 9.0ms 21.6ms 13.0ms

Write-path analysis: Median latencies are within noise of v0.1.24.2 when accounting for test execution order (this run had warm-container advantage). Extend improved slightly (6.9ms vs 7.1ms) due to HMGET consolidation reducing 7 separate HGET calls to 2 batched HMGET calls inside the Lua script. Event is flat at 4.5ms — the scope budget cache eliminates redundant HGET calls across validation/capping/mutation phases, but the dominant cost is the Lua script execution itself which is unchanged. Commit p99 tail (12.2ms) is a known pattern from the PEXPIRE command on terminal hashes occasionally coinciding with Redis background save or container GC pauses.

Single-Threaded Read-Path Latency

Operation p50 p95 p99 min max mean
GET reservation 2.8ms 3.8ms 4.3ms 1.5ms 5.0ms 2.8ms
GET balances 2.1ms 2.9ms 3.6ms 1.5ms 3.8ms 2.2ms
LIST reservations 2.6ms 3.6ms 4.2ms 1.8ms 4.6ms 2.7ms
Decide (pipelined) 3.2ms 4.5ms 5.0ms 2.2ms 5.4ms 3.2ms

Read-path analysis: Read operations are the fastest since they bypass Lua scripts entirely — just direct Redis hash reads (pipelined for multi-scope queries). GET balances at 2.1ms p50 is identical to v0.1.24.2, confirming no regression. Decide at 3.2ms p50 benefits from the pipelined idempotency check (2 GETs in 1 round-trip instead of 2 sequential calls). These operations were not the focus of the Lua optimizations since they don't execute Lua scripts.

Concurrent Throughput (Reserve+Commit lifecycle)

Threads Total Ops Ops/sec p50 p95 p99 min max Errors
8 3,844 768.8 10.2ms 12.6ms 17.6ms 7.4ms 30.0ms 0
16 5,496 1,099.2 14.3ms 19.9ms 24.1ms 6.9ms 42.3ms 0
32 12,668 2,533.6 12.1ms 17.9ms 22.7ms 6.8ms 45.2ms 0

Concurrency analysis: Throughput scales near-linearly from 8 to 32 threads (769 to 2,534 ops/s — 3.3x at 4x threads). Zero errors at all levels. The pool increase from 50 to 128 max connections prevents exhaustion under load. testWhileIdle validates idle connections in the background (every 30s) which avoids stale-connection spikes without adding per-request PING overhead. Max latency at 32 threads (45.2ms) improved from 51.0ms in v0.1.24.0 — fewer extreme outliers due to better connection pool health. The p50 at 32 threads (12.1ms) being lower than 16 threads (14.3ms) reflects Redis Lua script serialization: more threads means more pipeline overlap while waiting for Lua execution.


v0.1.24.2 — Baseline

Date: 2026-03-30 Branch: main Commit: 0555529 Environment: Windows 11 Pro, AMD Ryzen Threadripper 3990X 64-Core, Java 21.0.5, Docker 29.2.1, Redis 7 (Testcontainers)

Note: This run executed first (cold container) in the comparison session. v0.1.24.3 ran second (warm). Single-threaded numbers may be ~10% pessimistic due to cold JVM and container OS page cache.

Single-Threaded Write-Path Latency

Operation p50 p95 p99 min max mean
Reserve 5.3ms 6.7ms 7.9ms 4.2ms 14.1ms 5.5ms
Commit 4.1ms 5.2ms 5.6ms 3.2ms 10.6ms 4.2ms
Release 4.3ms 5.1ms 5.7ms 3.4ms 6.3ms 4.3ms
Extend 7.1ms 8.6ms 9.9ms 5.4ms 17.0ms 7.2ms
Decide 5.5ms 6.6ms 7.6ms 4.5ms 7.8ms 5.6ms
Event 4.5ms 5.5ms 5.8ms 3.5ms 9.4ms 4.5ms
Reserve + Commit 12.9ms 15.5ms 16.3ms 10.3ms 23.6ms 12.9ms
Reserve + Release 10.4ms 12.7ms 14.3ms 8.4ms 22.8ms 10.6ms

Write-path analysis: This is the pre-optimization baseline. Each Lua script uses individual HGET calls (e.g., commit.lua makes 9 separate HGET calls plus 2 more for expires_at/grace_ms). The connection pool is sized at 50/10/5 (max/maxIdle/minIdle) with no health checks. Extend is the slowest operation (7.1ms p50) because it executes the Lua script, then makes 3 additional HGET calls for balance collection — these are now consolidated into a single HMGET in v0.1.24.3. Reserve max of 14.1ms shows occasional cold-connection latency spikes that the pool health checks in v0.1.24.3 prevent.

Single-Threaded Read-Path Latency

Operation p50 p95 p99 min max mean
GET reservation 2.1ms 2.9ms 3.4ms 1.4ms 5.0ms 2.2ms
GET balances 2.1ms 3.2ms 3.9ms 1.5ms 4.2ms 2.2ms
LIST reservations 2.3ms 3.2ms 3.6ms 1.8ms 3.9ms 2.4ms
Decide (pipelined) 3.1ms 4.1ms 4.7ms 2.4ms 7.1ms 3.2ms

Read-path analysis: Read operations were already pipelined in v0.1.24.2 (added in Phase 3 of that release). These are pure Redis hash reads — no Lua scripts involved. Decide uses a pipeline to batch budget HGETALL calls across all scopes, but its idempotency check still makes 2 sequential GET calls (optimized to a single pipeline in v0.1.24.3). The low min values (1.4ms for GET) represent the best case when the JVM is warm and Redis response is immediate.

Concurrent Throughput (Reserve+Commit lifecycle)

Threads Total Ops Ops/sec p50 p95 p99 min max Errors
8 4,199 839.8 9.3ms 11.4ms 16.3ms 6.5ms 31.9ms 0
16 6,772 1,354.4 12.0ms 17.9ms 21.8ms 5.4ms 31.5ms 0
32 13,687 2,737.4 11.2ms 16.6ms 21.0ms 6.5ms 41.4ms 0

Concurrency analysis: Good throughput scaling at 2,737 ops/s with 32 threads. The connection pool (max 50) is sufficient for this test but leaves limited headroom — with 32 threads each needing a connection for Reserve then Commit (2 connections per lifecycle), pool contention is possible. The ConcurrentHashMap-based caches (API key, tenant config) use O(n) lazy eviction when size exceeds 10,000 entries, which could block request threads under sustained load — replaced with Caffeine's O(1) eviction in v0.1.24.3. Zero errors across all concurrency levels confirms Redis Lua atomicity prevents race conditions even under contention.


v0.1.24.0 — Initial Benchmarks (from AUDIT.md)

Date: 2026-03-24 Environment: Prior machine/environment (not directly comparable to v0.1.24.2+)

Note: These numbers were captured on different hardware than v0.1.24.2/v0.1.24.3. They represent the state after the initial performance optimization pass (EVALSHA, BCrypt cache, pipelined balance fetch, Lua-returned balances). Use only for trend analysis, not direct comparison.

Single-Threaded Write-Path Latency

Operation p50 p95 p99 min max mean
Reserve 5.1ms 6.1ms 6.8ms 4.1ms 12.9ms 5.1ms
Commit 4.3ms 5.5ms 12.1ms 2.8ms 24.1ms 4.5ms
Release 4.4ms 5.3ms 5.8ms 3.2ms 6.2ms 4.5ms
Extend 7.4ms 9.1ms 10.7ms 5.9ms 17.7ms 7.6ms
Decide 5.4ms 6.2ms 6.7ms 4.2ms 6.9ms 5.5ms
Event 4.6ms 5.7ms 6.4ms 3.4ms 8.3ms 4.7ms
Reserve + Commit 12.9ms 15.6ms 17.9ms 10.2ms 21.8ms 13.2ms
Reserve + Release 10.4ms 12.1ms 13.9ms 9.0ms 20.3ms 10.6ms

Write-path analysis: Baseline after the first round of optimizations: EVALSHA (saves 1-5KB network per Lua call), BCrypt API key cache (eliminates ~100ms+ BCrypt on cache hit), and Lua-returned balance snapshots (eliminates post-operation Java balance fetch). Commit p99 of 12.1ms and max of 24.1ms show significant tail latency — the PEXPIRE on terminal reservation hashes occasionally coincides with Redis background saves. Extend at 7.4ms is the slowest write due to its Lua script making 7 individual HGET calls (3 for validation + 3 for balance scope data + 1 more for estimate_unit).

Single-Threaded Read-Path Latency

Operation p50 p95 p99 min max mean
GET reservation 2.5ms 3.3ms 3.7ms 1.5ms 9.2ms 2.5ms
GET balances 2.8ms 4.0ms 4.6ms 1.6ms 4.8ms 2.8ms
LIST reservations 3.2ms 4.2ms 4.6ms 2.0ms 5.3ms 3.2ms
Decide (pipelined) 4.6ms 5.8ms 6.5ms 2.7ms 7.7ms 4.7ms

Read-path analysis: Read-path pipelining was introduced in this version (Phase 3). Decide at 4.6ms p50 uses a pipeline for budget HGETALL calls but still has 2 sequential GET calls for idempotency. GET reservation max of 9.2ms is an outlier — likely a cold connection or container GC pause. LIST at 3.2ms uses Redis SCAN with pipelined HGETALL for each batch.

Concurrent Throughput (Reserve+Commit lifecycle)

Threads Total Ops Ops/sec p50 p95 p99 min max Errors
8 3,993 798.6 9.8ms 11.9ms 16.9ms 6.7ms 30.9ms 0
16 5,737 1,147.4 13.9ms 19.1ms 22.6ms 6.8ms 32.1ms 0
32 12,775 2,555.0 11.7ms 18.9ms 27.9ms 6.8ms 51.0ms 0

Concurrency analysis: Near-linear scaling from 8 to 32 threads (799 to 2,555 ops/s — 3.2x at 4x threads). The max latency at 32 threads (51.0ms) is notable — this is the highest observed across all versions and likely reflects connection pool contention at the max-50 pool size combined with ConcurrentHashMap eviction pauses. The 27.9ms p99 at 32 threads shows the tail cost of Redis Lua script serialization: only one script can execute at a time on the Redis server, so under high concurrency, scripts queue behind each other. Zero errors confirms the system is functionally correct under load.