This page describes how we measure Brume, so you can judge any number we publish — and reproduce the measurement yourself. We do not publish numbers we cannot stand behind. The harness ships with the repository (brume-bench); the first measured results are below.
The unit under test is one POST /v1/ratelimit/limit evaluation: auth lookup, rule resolution from the in-process cache, daily-budget check, and the atomic Lua evaluation against Redis — the full path your application exercises.
We measure:
degraded: true, zero denials.| Name | Concurrency | Identifier cardinality | Rule shape | Models |
|---|---|---|---|---|
steady-api | 100 | 10K users | 100 req/min sliding counter | A typical REST API |
bursty-login | 50 | 1K IPs | 5 req/min fixed window + blocklist | Credential-stuffing defense |
hot-key | 500 | 1 user | token bucket with burst | A single viral identifier |
multi-rule | 100 | 10K users | per-minute + per-day in one call | Budget + burst together |
Measured 2026-08-29 on the production artifact — plain release build cfb6b48 — socket-activated on 127.0.0.1:8422 against the bench database, 30-second runs after a 5-second warmup, zero errors (no HTTP failures, no decode failures, no degraded responses) across all four workloads. Hardware: see below.
| Workload | Concurrency | Checks | Checks/s | p50 | p90 | p99 | p999 | Outcomes |
|---|---|---|---|---|---|---|---|---|
steady-api | 100 | 174,873 | 5,827 | 16.6 ms | 22.1 ms | 29.0 ms | 35.1 ms | all allowed |
bursty-login | 50 | 156,222 | 5,206 | 9.3 ms | 12.7 ms | 16.8 ms | 22.4 ms | all denied (by design) |
hot-key | 500 | 214,568 | 7,138 | 67.9 ms | 86.8 ms | 114.2 ms | 136.3 ms | all allowed |
multi-rule | 100 | 118,883 | 3,960 | 22.2 ms | 33.2 ms | 73.0 ms | 86.5 ms | all allowed |
Notes on reading these:
bursty-login is denial-heavy by design. At ~5.2K checks/s against 1,000 IPs, every IP blows through its 5-per-minute window within the first second, so the measured phase is a pure deny-path exercise (the blocklist is in the rule set and was behaviorally verified). Per-identifier isolation was verified separately: two fresh IPs each start with a full bucket.hot-key at concurrency 500 is host saturation, not service latency. The 4-vCPU host is shared by client and service; 500 in-flight workers on 4 cores means queueing dominates — that is what a saturated single machine looks like, and it is the honest number for that shape. The low-concurrency rows below show the service latency underneath.multi-rule counts calls, not evaluations. Each call evaluates two rules (per-minute + per-day) dispatched together on one Redis connection, sharing one round trip: 3,960 calls/s ≈ 7,920 evaluations/s at concurrency 100.Single-tenant-shaped load: four in-flight requests, same host, loopback.
| Workload | Checks | Checks/s | p50 | p99 | p999 |
|---|---|---|---|---|---|
steady-api | 24,590 | 1,639 | 2.3 ms | 4.9 ms | 6.5 ms |
bursty-login | 27,864 | 1,857 | 2.1 ms | 4.1 ms | 5.7 ms |
hot-key | 25,509 | 1,700 | 2.2 ms | 4.8 ms | 8.4 ms |
multi-rule (2 rules/call) | 12,555 | 837 | 4.1 ms | 12.1 ms | 19.5 ms |
| State | Checks/s | p50 | p99 | p999 |
|---|---|---|---|---|
| Warm caches | 1,639 | 2.3 ms | 4.9 ms | 6.5 ms |
| Post-restart cold | 1,495 | 2.5 ms | 5.8 ms | 9.1 ms |
Cold start costs ~0.2 ms at p50: auth and rule caches load per-project on first use, not per-request. The gateway was restarted and hit immediately with 15 seconds of traffic at concurrency 4, no warmup.
The gateway points at an unreachable Redis endpoint; every check takes the fail-open path.
| Concurrency | Checks | Checks/s | p50 | p99 | p999 | Outcomes |
|---|---|---|---|---|---|---|
| 4 | 170,361 | 11,357 | 0.3 ms | 1.3 ms | 3.0 ms | all degraded-allowed |
| 100 | 707,773 | 23,587 | 3.9 ms | 10.6 ms | 14.7 ms | all degraded-allowed |
Fail-open is faster than the enforcing path (0.3 ms vs 2.3 ms at p50, c=4) because the Redis round trip is the dominant cost; when Redis is unreachable the check resolves from the auth and rule caches alone. Every response carried degraded: true and allowed the request — zero denials, zero errors. The tradeoff is stated plainly: during a Redis outage Brume stops enforcing limits and keeps serving; enforcement resumes automatically on reconnect.
The service runs on a single VPS in Frankfurt (see Deployment). These figures were measured on that host itself, over loopback, with the bench client sharing the machine with the service:
brume-bench, 4 worker tasks maximum in the service-latency rows, HDR histogram per workerWe publish the machine class with every result — a number without its hardware is marketing, not measurement. Same-host loopback is disclosed here because it is what was measured; numbers from a separate client machine in the same region will replace these when measured, with both the client and server cost visible.
The harness lives in the repository at crates/core/src/bin/bench.rs and builds as brume-bench:
Every result above was produced by exactly those two commands with the flags shown in the table rows. New numbers land here and in the changelog — with the machine, the workload, and the build.