Skip to content

Performance and footprint

Measured by the project on a laptop:

  • HP Pavilion Gaming 15-ec0xxx, AMD Ryzen 5 3550H (4 cores, 8 threads), 13 GiB RAM visible to the OS, Ubuntu 24.04, on mains power.
  • Default configuration: 2 workers, access log on and written to a file, a sealed file of 64 secrets.
  • Rate limiter lifted, so the serving path is measured.
  • Server on logical CPUs 0-3, load generator on 4-7 of the same machine.
  • Every figure is the median of 3 runs.
StateRequests per secondRAM (RSS)CPU, % of one logical CPUp50 / p99
Idle08.4 MiB1.3%-
Stress39,24711.8 MiB132%1.35 ms / 3.22 ms
Saturated63,64816.7 MiB216%4.04 ms / 6.04 ms

Stress is 40,000 requests per second offered, the most the default configuration will serve: 20,000 per worker, 2 workers. Saturated is wrk with 256 connections sending as fast as the server answers.

Offered rateServedRAM (RSS)CPUCPU per requestp50p99
1,00098211.6 MiB14%143 µs1.02 ms2.62 ms
5,0004,98112.2 MiB47%95 µs1.07 ms2.34 ms
10,0009,96011.9 MiB84%85 µs1.44 ms3.02 ms
20,00019,62311.5 MiB113%58 µs1.67 ms3.82 ms
30,00029,43411.8 MiB118%40 µs1.32 ms4.13 ms
40,00039,24711.8 MiB132%34 µs1.35 ms3.22 ms
50,00049,79311.9 MiB159%32 µs1.27 ms4.48 ms
60,00059,75212.0 MiB185%31 µs1.42 ms6.61 ms

Every request in every run answered 200.

What the numbers show:

  • RAM stays flat under load, near 12 MiB from 1,000 to 60,000 requests per second. The step to 16.7 MiB at saturation comes with 256 open connections instead of 100.
  • The ceiling is the server’s. At saturation both workers are fully busy while the load generator used about half its CPUs. The two workers were pinned to the two hardware threads of one physical core, so the default configuration served about 63,000 requests per second from a single core.
  • CPU per request falls as load rises, from 143 µs to 31 µs. The likely cause is that one wake-up of a worker and the log writer serves many requests under load. This has not been measured separately.
  • Idle is near zero. The access log writer checks for work at most every millisecond, which avoids putting a mutex on the read path.

With 4 workers on an 8-core laptop, the project measured about 99,000 requests per second at saturation, and p50 1.3 ms / p99 3.5 ms at a fixed 30,000 requests per second.

ProtectionCost
In-memory barrier (each secret sealed in RAM between requests)350-430 ns per request
Access logabout 6% of saturation throughput

Both were measured by interleaving two builds in one loop, after measuring them one after the other gave a result explained by the laptop warming up.

BinarySize
kallisto-server7.5 MiB, 5.7 MiB stripped
kallisto-ctl2.4 MiB

Neither links anything beyond libc and libgcc_s. The container image uses static musl builds.

Server and load generator shared one machine with a desktop session running. Treat the figures as an order of magnitude and measure your own hardware before sizing anything. See run the benchmarks.