sqlitend · benchmark report · 2026-09-06

The Speedtest Replication

SQLite’s own speedtest1 benchmark, run as the official C tool for the citation-grade number, then ported statement-for-statement to run two ways on the same box — directly embedded, and through sqld over a Hrana WebSocket — so the cost of the “serverless” layer is measured, not assumed.

Machine1 vCPU · 1 GB RAM
PlatformLinux aarch64 (Debian 13)
SQLite3.53.4 · -O3 · RTREE
sqlitend0.1.0 · bind 0.0.0.0
sqld0.24.32 (pinned, SHA-256)
6.494 s
Official speedtest1
C tool · default size 100 · full testset suite
483 ms
Port, embedded
same 15-step suite · bun:sqlite, local file
8,385 ms
Port, through sqld
identical statements · Hrana over loopback
17.4×
Serverless overhead
single-row insert overhead 20.4× → 3.4× at 1,000 rows/stmt
1The official baselineSQLite 3.53.4 amalgamation · default --size 100

First, the number people actually quote: SQLite’s own benchmark tool compiled with -O3 and RTREE enabled, run at its default scale on this single-core machine. It builds and tears down a sequence of 50,000-row tables across the full default testset list — unindexed and indexed inserts, clustered updates, deletes, joins, LIKE, VACUUM, JSON, floating point, a star-cube and a Fossil-style schema — and reports a 6.494 s total. This is the citation-grade ceiling for raw SQLite on this exact box.

StepTime
50,000 INSERTs into a table with no index0.035 s
50,000 unordered INSERTs with one index/PK0.074 s
50,000 INSERTs with three indexes0.155 s
10,000 SELECTs, text-BETWEEN, indexed0.143 s
10,000 four-way joins0.362 s
50,000 UPDATEs of individual rows0.105 s
50,000 DELETEs of individual rows0.121 s
70000 REPLACE ops on an IPK0.143 s
Star query over a 5,000-entry fact table0.028 s
Open and use the database 300 times0.124 s
TOTAL (all testsets)6.494 s
2The same suite, two runtimesone clean sqld database · loopback · single client

A faithful port of speedtest1’s statement shape — batched inserts in growing group sizes, indexed point and range lookups, a LIKE prefix scan, a full-table scan, updates, deletes, and 100 KB blobs — run twice on the same machine with byte-identical SQL. Embedded, the same engine answers in 483 ms. Through sqlitend’s sqld over a loopback Hrana WebSocket it takes 8,385 ms. There is no network here — the entire delta is one round trip per statement plus sqld’s replication-lite machinery. That is the measured cost of turning a library into a server.

embedded · bun:sqlite
483 ms

15 steps, zero errors. The base layer. 203 ms alone is the 5,000 individual one-row inserts.

serverless · sqlitend → sqld
8,385 ms

identical statements, zero errors. 4,134 ms is those same 5,000 single-row inserts — each one a Hrana round trip.

StepembeddedsqldRatio
create schema + indexes16 ms67 ms 4.2×
insert 5,000 rows · 1 row/statement203 ms4,134 ms 20.4×
insert 5,000 rows · 10 rows/statement50 ms495 ms 9.9×
insert 5,000 rows · 100 rows/statement21 ms148 ms 7.0×
insert 5,000 rows · 1,000 rows/statement28 ms95 ms 3.4×
insert 2,000 rows · one statement9 ms39 ms4.3×
update 1,000 rows via PK1 ms2 ms2.0×
update 2,000 rows via index13 ms30 ms2.3×
delete 500 rows via PK0 ms5 ms—
full-table scan (count + sum)2 ms6 ms3.0×
2,000 PK point lookups11 ms1,278 ms 116×
2,000 indexed range lookups62 ms1,655 ms 26.7×
LIKE prefix scan7 ms55 ms7.9×
insert 100 × 100 KB blobs55 ms342 ms6.2×
read back 50 blobs5 ms34 ms6.8×
Total483 ms8,385 ms17.4×

Read it once and the shape of the answer is obvious. The pain is entirely per-statement: single-row inserts pay 20.4× and one-at-a-time point lookups a brutal 116× (0.64 ms per lookup — that is the round trip itself). The moment rows travel together, sqld’s own SQLite does the exact same work — 1,000 rows per statement lands at 3.4×, which includes the insert logic. The takeaway for real workloads: batch your writes, dashboard your point queries, and the serverless tax mostly disappears. And at no point — embedded or serverless — did a statement fail.

3The machine under ithost sampled every 0.3 s during the sqld run

Peaks while sqld ran the suite

Host memory used
332 / 969 MB (peak)
Load average (1 vCPU)
peak 0.84
sqld resident set (this DB)
31 MB
CPU busy explaining it
mostly idle

What that means

A single sqld database serving this whole suite peaked under 35 MB RSS and never pushed the 1-core box past a load of 0.84. The 17.4× is not the server working hard — it is the server being asked one tiny thing at a time. Each statement could be answered in a blink; the blink is the wire. A faster core, a real network, or 64 of them at once would shift the absolute numbers, not the structure of the answer.

MMethod & honesty

How it ran

The official tool was built from the stale-free sqlite-amalgamation-3530400 with gcc -O3 plus -DSQLITE_ENABLE_RTREE (without it speedtest1 hard-fails at the rtree testset rather than skipping — 3 runs confirmed), and run at its default size 100. The port is a statement-level re-implementation of that shape — it is not bit-identical to the C tool, and the two totals (6.494 s vs 483 ms) are different workloads, so they are presented separately, never averaged. For the serverless leg, one database was created through the sqlitend API, one token minted, and the port run against ws://127.0.0.1 so no network noise beyond loopback entered the numbers. Both legs used the same Bun 1.4.2 runtime.

Caveats

Single node, single CPU, deliberately small RAM — absolute seconds are not portable; the ratios and the per-statement shape are the general lesson. Real deployments add a genuine network, TLS, and possibly many concurrent clients, which shift the numbers and are worth a follow-up run. The control plane has no login in v1: put it behind a firewall or an authenticated reverse proxy. This measured what it claims — one engine, two execution paths, the exact same SQL in front of each.