Benchmarks
Measured, not promised. The Rust engine (0.5) is faster than SQLite and PostgreSQL at every size measured, 16× faster than SQLite at p50 on 1M events and 36× on 10M.
Summary
- 16× faster than SQLite at p50 on 1M events (0.107 ms vs 1.75 ms), and 36× on 10M. Against PostgreSQL: 38× and 124×.
- Prunes 96.6% of segments at 1M events: a query scores 19 of the 459 events in its window.
- 8.5x to 19.4x faster than the earlier pure-Python engine, with identical results.
- Better answers than recency: hit@1 of the true culprit 0.562 vs 0.367 on a simulated week of code changes.
The Rust engine carries most of the speed, and segment pruning adds more as the data grows: brute force over the same Rust store is 1.10x slower at 1M events and 1.54x slower at 10M, while at 10K and 100K events a plain scan is faster.
Query latency
Default kernel (log-normal, median 3h, sigma 0.6), 15 min buckets. Latency in milliseconds.
| N | impl | p50 | p95 | p99 | mean |
|---|---|---|---|---|---|
| 10K | heapdb | 0.011 | 0.021 | 0.049 | 0.012 |
| 10K | bruteforce-rust | 0.005 | 0.008 | 0.012 | 0.005 |
| 10K | sqlite-mem | 0.025 | 0.043 | 0.064 | 0.026 |
| 10K | postgres | 0.120 | 0.178 | 0.199 | 0.126 |
| 100K | heapdb | 0.026 | 0.035 | 0.056 | 0.028 |
| 100K | bruteforce-rust | 0.021 | 0.029 | 0.052 | 0.022 |
| 100K | sqlite-mem | 0.187 | 0.269 | 0.347 | 0.195 |
| 100K | postgres | 0.537 | 0.982 | 1.13 | 0.621 |
| 1M | heapdb | 0.107 | 0.167 | 0.200 | 0.120 |
| 1M | bruteforce-rust | 0.119 | 0.157 | 0.173 | 0.125 |
| 1M | sqlite-mem | 1.75 | 2.71 | 2.92 | 1.85 |
| 1M | postgres | 4.04 | 4.70 | 6.20 | 4.11 |
| 10M | heapdb | 0.514 | 0.602 | 0.634 | 0.517 |
| 10M | bruteforce-rust | 0.789 | 1.15 | 1.21 | 0.830 |
| 10M | sqlite-mem | 18.5 | 29.0 | 35.1 | 20.2 |
| 10M | postgres | 63.5 | 129 | 174 | 72.0 |
HeapDB p50 relative to the others (other p50 / heapdb p50; above 1, HeapDB is faster; below 1, slower):
| N | heapdb p50 (ms) | vs bruteforce-rust | vs bruteforce | vs sqlite-mem | vs sqlite-disk | vs postgres |
|---|---|---|---|---|---|---|
| 10K | 0.011 | 0.43x | 0.49x | 2.28x | 2.51x | 11.00x |
| 100K | 0.026 | 0.81x | 1.85x | 7.09x | 7.05x | 20.33x |
| 1M | 0.107 | 1.10x | 4.12x | 16.30x | 16.71x | 37.58x |
| 10M | 0.514 | 1.54x | not measured | 35.93x | 35.89x | 123.58x |
The Python-loop bruteforce was not measured at 10M: its estimated memory (10.6 GiB) exceeded the VM.
Pruning
Means over the timed queries. Events in window is what brute force scores.
| N | events in window | heapdb events scored | scored p99 | segments in window | segments pruned | pruned % |
|---|---|---|---|---|---|---|
| 1M | 459.0 | 19.2 | 30 | 129.2 | 124.7 | 96.6% |
| 10M | - | 149.6 | 250 | 133.3 | 129.0 | 96.8% |
How the pruning works: Core algorithms.
Python engine vs Rust engine
heapdb rows of the v0.4 pure-Python engine re-run on the same CPU, next to the v0.5 Rust engine. Both score exactly the same number of events per query; the gain is the cost per unit of work, not a different algorithm.
| N | Python p50 ms | Rust p50 ms | p50 speed-up | ingest speed-up | Python RSS B/event | Rust RSS B/event |
|---|---|---|---|---|---|---|
| 10K | 0.092 | 0.011 | 8.5x | 4.3x | 2,415 | 808 |
| 100K | 0.377 | 0.026 | 14.3x | 6.6x | 2,228 | 607 |
| 1M | 2.08 | 0.107 | 19.4x | 8.3x | 1,502 | 298 |
| 10M | not measured | 0.514 | - | - | not measured | 178 |
Where the index pays off and where it doesn't
- Small N, against brute force. At 10K the window holds 4.6 events and only 13% of segments can be pruned; HeapDB's p50 is 0.011 ms against 0.005 ms for both brute-force implementations (0.43x vs
bruteforce-rust, 0.49x vsbruteforce). At 100K it is still slower thanbruteforce-rust(0.026 vs 0.021 ms, 0.81x). - Pruning pays off as the data grows. With the same scoring code, brute force is 1.10x slower at 1M and 1.54x slower at 10M. For top-k over a short window on small data, a scan gets close.
- Short or uninformative kernels. With a 10-minute exponential kernel HeapDB is slower than
bruteforce-rust(0.72x); with a narrow log-normal and with flat links in a 24h window it roughly ties (0.94x, 0.98x). Table below. - Memory against SQLite. HeapDB needs 298 B/event at 1M and 178 B/event at 10M; SQLite in memory needs 139 and 140.
Memory (RSS delta):
| N | impl | ingest events/s | RSS delta MiB |
|---|---|---|---|
| 1M | heapdb | 357,197 | 284 (298 B/event) |
| 1M | sqlite-mem | 185,164 | 132 (139 B/event) |
| 10M | heapdb | 293,950 | 1,701 (178 B/event) |
| 10M | sqlite-mem | 173,499 | 1,331 (140 B/event) |
Kernels at 1M events (latency in ms):
| kernel | heapdb scored | pruned % | heapdb p50 | bruteforce-rust p50 | bruteforce-rust / heapdb |
|---|---|---|---|---|---|
exp_10m | 9.4 | 63.8% | 0.015 | 0.011 | 0.72x |
lognormal_narrow | 18.4 | 89.1% | 0.041 | 0.039 | 0.94x |
window_24h, flat links | 117.9 | 70.6% | 0.073 | 0.072 | 0.98x |
Ranking quality
hit@1 of the true culprit on a simulated week of code changes (20 seeds), identical for both engines:
| method | days 0-3.5 | days 3.5-7 | all |
|---|---|---|---|
recency | 0.335 ± 0.097 | 0.411 ± 0.117 | 0.367 ± 0.070 |
heapdb_proportional | 0.536 ± 0.098 | 0.600 ± 0.111 | 0.562 ± 0.076 |
Before any feedback, the kernel, dose and noisy-OR over feature sets take whole-week hit@1 from 0.367 (recency) to 0.536 (priors only); revert feedback raises it to 0.562.
Methodology
We implemented the same ranking problem (top-5 causes of one effect for one entity) six ways and checked that all six return identical answers before timing them: HeapDB, brute force with the same Rust scoring code (bruteforce-rust), a Python brute-force loop over the Rust score function (bruteforce), SQLite in memory and on disk, and PostgreSQL. Every latency includes the Python call.
One 2-vCPU VM (Intel Xeon @ 2.80GHz, 7.8 GiB RAM), Python 3.13, heapdb 0.5.0, SQLite 3.45.1, PostgreSQL 16. Single-threaded, synthetic data with uniform event times; real logs are bursty, which changes pruning. HeapDB ran in-process; SQLite on disk and PostgreSQL were queried warm. Differences of 10% or less between implementations should be treated as ties (run-to-run noise). Read the "Reading these results" section of RESULTS.md before quoting any number. What comes next is on the roadmap.