sqlitend · benchmark report · 2026-09-06

The Million-Statement Run

One million statements, four concurrent clients, one isolated sqld database on a single-core virtual machine — and not one failure. Every statement is a live network round trip over loopback Hrana: 1,000,000 total, 0 failures, p95 6 ms.

Machine1 vCPU · 1 GB RAM
PlatformLinux aarch64 (Debian 13)
sqld0.24.32 (pinned, SHA-256)
Driver@libsql/client over Hrana WS
Mix80% reads · 20% writes
1,000,000
Statements
4 clients · loopback · one sqld database
0
Failures
26 transient BUSY retries · zero dropped ops
1,500/s
Sustained rate
p50 2 ms · p95 6 ms · p99 11 ms
2,250/s
Single-writer ceiling
more clients queue — they don’t add speed
1The concurrency curvewrite-heavy 30/70 mix · 35 s per level

Before the million, a sweep: 1, 4, 8, then 16 simultaneous clients, each running an identical mixed workload against the same seeded database. The headline is a wall, not a curve — sqld has a single writer thread, so aggregate throughput is flat at roughly 2,250 statements per second from one client to four. Concurrency buys queueing, not speed: p95 climbs ~5× from four clients to sixteen (6→28 ms) and ~28× from one (1→28 ms), and the box stops cooperating past that point (32 and 64 client runs collapsed — not captured in the evidence file; the separate wedge finding documented load ~56). Zero errors at every level — the server said no politely, by returning SQLITE_BUSY, and the client retried with backoff exactly as a production driver would.

ClientsOps / 35 sRatep50p95p99BUSY retriesFailures
178,0182,229 /s0 ms1 ms3 ms00
478,8822,254 /s1 ms6 ms 8 ms70
868,7901,965 /s2 ms14 ms 22 ms270
1647,4081,354 /s4 ms28 ms 80 ms440
≥ 32collapse — thrash, not captured in the evidence file—
2The million-statement runread-heavy 80/20 mix · fresh DB · 4 clients · 666.8 s

A brand-new database, seeded with 100,000 rows, then four clients hammering it with a realistic serverless OLTP mix — 12% single-row inserts, 8% point updates, 35% indexed point reads, 25% range counts, 20% top-N reads — until exactly 1,000,000 statements had been served. The wall from the sweep was the write-heavy part; at this mix the same wall stands far away. Throughput held a mean of 1,500 /s for eleven minutes, and p99 held at 11 ms for the entire run.

throughput
1,500 /s mean

870 – 1,872 /s per second; flat until the very end.

p99 latency
11 ms

99% of a million statements answered in 11 ms or less.

failures
0 / 1M

26 BUSY retries absorbed by the client; none surfaced.

sqld footprint
42 MB

RSS at peak; host never above 357 MB of 969 MB.

Throughput, second by second

Statements completed per second across the 666-second run — 4 clients, one sqld process, one core. The dashed amber line is the mean, 1,500/s.

2000/s 1000/s 0 mean 1,500/s 0 s666 s
Latencyp50p95p99max
milliseconds2611269
3The wall is real — and it isn’t the network

Single-writer, by design

sqld serializes writes through one writer thread — exactly as SQLite itself does, only across a socket. The sweep’s flat ceiling (~2,250/s at 1–4 clients) is that serialization on one core. The practical law it implies: per-statement round trips are the currency, so batch your writes. In the companion speedtest replication, 1,000-row batches cut serverless overhead from 20× to 3.4×.

One real bug found

A sustained write-heavy run (30% writes, retry storms, growing table) left the benchmark sqld process busy-spinning at 100% CPU with zero clients attached — load 56 on one core, machine otherwise idle. Killing the process ended it instantly; the read-heavy million ran eleven minutes without a trace of it. This is a sqld behavior worth knowing before you run heavy write concurrency against a solo node: monitor per-database CPU and batch aggressively.

MMethod & honesty

How it ran

The sweep and the million used one sqlitend-created database each, one minted full-access token, @libsql/client 0.18.0 over Hrana WebSocket to ws://127.0.0.1 — loopback, so no real network in the numbers. Both databases were seeded with 100,000 rows. The sweep mix was write-heavy (30%); the million mix read-heavy (20% writes) and is labeled as such — rates across the two are not directly comparable. Latency was bucketed at 50µs resolution; client-visible latency includes retry backoff. Each run was a fixed-quota batch, so the million is exactly 1,000,000 statements — every one counted.

Caveats

One core, one gigabyte, loopback — absolute rates are box-relative; the shape (flat writer ceiling, calm read tail) is the portable lesson. The write-heavy sweep thrashes at ≥32 clients and is reported as such, not hidden. The sqld busy-spin was observed once and mitigated; it deserves a dedicated investigation before claiming write-heavy HA. A real network, TLS, and more cores would all move these numbers and are the obvious follow-up. The control plane has no login in v1: firewall or reverse-proxy it in production.