benchmarks

KvSQL Against SQLite on the Same Machine: Key Reads, SQL Reads and Synced Commits

KvSQL’s claim is a fast path by key that skips the SQL machinery, on the same records SQL reads. A claim like that needs a measurement, and the obvious yardstick is SQLite. It’s the most used embedded database there is, and it’s very good. So the project’s test script runs a benchmark against SQLite every time it records results, and saves it next to the test output.

The figures here are from the recorded run of October 22, 2026, on a Linux container with two virtual CPUs. The full table, with every condition, is bench.md in the repository. A container like this is noisy: between runs on the same day, single figures moved by 20 to 30 percent. Treat the gaps below as rough sizes.

KvSQL and SQLite on the same machine. Point read by primary key: KvSQL get on a plain keyspace 1.13 µs, KvSQL get_row 1.56 µs, KvSQL SQL point select 3.59 µs, SQLite KV table prepared 3.54 µs, SQLite point select prepared 2.66 µs, SQLite point select parsed each time 5.04 µs. Synced commit: KvSQL put 509 µs, KvSQL INSERT 438 µs, SQLite KV insert 237 µs, SQLite INSERT 226 µs.

Point reads in microseconds, and one small write per synced commit, from one recorded run.

The Set-Up

Both databases get the same 100,000 rows in a table t (id INTEGER PRIMARY KEY, name TEXT, score INTEGER), plus 100,000 short keys in a plain keyspace. SQLite gets the plain keys as a WITHOUT ROWID table of keys and values. Both run on real files with an 8 MiB page cache. SQLite uses its WAL journal with synchronous=FULL, so its commits are synced to disk like KvSQL’s. Reads are warm, every key once per round in shuffled order, and each figure is the median of five rounds.

Reads by Key

This is where the design pays. Reading a row by primary key took 1.56 µs through get_row and 2.66 µs through SQLite’s prepared statement. A raw key read, get on a plain keyspace, took 1.13 µs against 3.54 µs for SQLite’s prepared lookup in a key-value table.

KvSQL’s own SQL door took 3.59 µs for the same row. KvSQL has no prepared statements yet, so every call goes through the statement cache and the planner. SQLite with a prepared statement beats that, and SQLite parsing the text every time doesn’t, at 5.04 µs. With two threads reading at once, get_row reached 786,972 reads a second in total and SQLite 180,134.

Where SQLite Wins

Writes. One small write per transaction, synced on every commit, cost KvSQL about 0.5 ms and SQLite about 0.23 ms. A KvSQL commit syncs twice: once for the new pages and once for the meta page that points at them. SQLite’s WAL gets by with one. Loading 100,000 table rows in one transaction took KvSQL 425 ms and SQLite 87 ms. A GROUP BY over the whole table took 39.9 ms against 27.3 ms, and a 1,000-row range scan through SQL took 606 µs against 347 µs. KvSQL’s file was larger after the run too, 7.0 MiB against 5.5 MiB.

So KvSQL fits work that reads by key far more often than it writes, with SQL for the questions. A bulk loader or a write-heavy log would be better off with SQLite today. A single-sync commit, which would close most of the commit gap, is on the list for later. Faster SQL execution is too.

Run It Yourself

git clone https://github.com/softdevstudio/kvsql
cd kvsql
cargo run --release -p kvsql-engine --example bench

KVSQL_BENCH_ROWS changes the row count. The program prints its own conditions at the top: the machine, both versions, and how the reads and writes were done.