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).
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
ReservationExpiryServiceprefix 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.12 —
sort_by+sort_dironGET /v1/reservationslist 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
TraceContextFilteradds 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.
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.
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.
| 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.
| 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.
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.
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.
| 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 |
| 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 |
| 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.
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.
| 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 |
| 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 |
| 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.
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.reasonCoderetyped from free-formStringto typedEnums.ReasonCode(6 values). Compile-time safety against drift from the companion spec enum in runcycles/cycles-protocol#26.- 10
.reasonCode(...)call sites inRedisReservationRepository(5 inevaluateDryRun, 5 indecide) updated to pass enum constants; 2 controller boundaries that feedEventDataReservationDenied(which keeps its String-typedreasonCodeas that's its own webhook wire contract) now convert via.name()with a null guard. EventEmitterServiceTestde-flaked (#82): 13Thread.sleep(200)+verify()patterns replaced with Mockitotimeout(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.
| 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.
| 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.
| 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.
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— newunits_csvARGV slot; alternate-unit probe added to the#budgeted_scopes == 0error path (returns 400UNIT_MISMATCHwith{scope, requested_unit, expected_units}when a budget exists at the scope in a different unit, else falls through toBUDGET_NOT_FOUND)event.lua— defensive data-integrity branch aligned to the same response shapeRedisReservationRepository— sharedprobeAlternateUnits(jedis, scope, requestedUnit)helper used byevaluateDryRunanddecide(); symmetric Java-side probe throws 400UNIT_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
| 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.
| 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.
| 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).
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_remainingandpre_is_over_limitper 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
| 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.
| 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.
| 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.
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_incurredtracking 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
| 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.
| 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).
| 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.
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_VERSIONconstant: "0.1.24" → "0.1.25" - No changes to Lua scripts or core hot-path logic
| 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.
| 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.
| 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.
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
| 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).
| 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.
| 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.
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
| 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.
| 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.
| 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.
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.
| 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.
| 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.
| 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.
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.
| 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).
| 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.
| 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.