Plate 28
sqlite3 vs shelve Local KV: Localhost Lab
Hands-on sqlite3 vs shelve local KV store lab: real insert/get ops/s plus file sizes, measured on Linux localhost today in this hands-on lab for SREs.
Aditya Challa4 min read
Intro — what this post promises
Local key-value persistence with sqlite3 vs shelve. This lab reports insert/get ops/s and on-disk bytes on Linux localhost for 5000 JSON-like records.
Related links:
- fractions vs float localhost lab
- math fsum vs sum localhost lab
- itertools batched vs chunk localhost lab
- exitstack vs nested with localhost lab
- xml etree vs json localhost lab
- logging formatter vs fstring localhost lab
- copy copy vs dict copy localhost lab
- html parser vs regex localhost lab
Lab honesty (1 Oct 2026 IST): Python 3.13.5. Affiliates: 0. Differentiates from shelve-vs-pickle-dict (lab 94) and sqlite WAL modes (lab 34) — here the peer is sqlite table KV vs shelve, default sqlite journal.
Verdict up front: sqlite insert ~217559 ops/s vs shelve insert ~13249; get: sqlite ~148945, shelve ~169920. Files: sqlite 532480 B, shelve 606208 B.
Arms
| Arm | Pattern |
|---|---|
| sqlite INSERT + commit | TEXT key, JSON TEXT value |
| shelve assign + sync | pickle values via dbm |
| sqlite SELECT by key | parse JSON |
| shelve read by key | native object |
Seven rounds, p50. Temp dir on local disk. Values equal across stores (equal=True).
Lab topology
Script: lab-evidence/128-sqlite3-vs-shelve/results/run_lab.py.
Lead table (p50 ops/s)
| Arm | ops/s |
|---|---|
| sqlite insert | 217559 |
| shelve insert | 13249 |
| sqlite get | 148945 |
| shelve get | 169920 |
Insert is the headline gap: sqlite led by about 16.4×. Gets were close; shelve edged slightly on this run.
Disk footprint
After 5000 keys: sqlite 532480 bytes vs shelve aggregate 606208 bytes. Shelve pays pickle + dbm overhead; sqlite stores compact JSON text in one file.
Reading it for SRE work
- Bulk load / agent checkpoint writes → sqlite3 (insert throughput).
- Occasional Python-object cache on disk → shelve can be fine if write volume is low.
- Need SQL, indexes, concurrent readers → sqlite wins on features, not just speed.
- Lab 94 compared shelve to an in-process pickle dict; lab 34 compared WAL — this post is cross-API KV.
Document which store owns the file path in the runbook so on-call does not “migrate to shelve” during an incident without measuring inserts.
Write amplification note
Shelve insert sat near ~13249 ops/s because each assignment pickles through dbm. Sqlite batched inserts in one transaction hit ~217559. If your workload is read-heavy and already shelve-shaped, the get numbers (~169920 vs ~148945) matter more than insert bragging rights.
API shape vs speed
Shelve returns native dict-like objects without a JSON decode step — that helps explain get parity (~169920 ops/s vs sqlite ~148945). Insert still paid pickle+dbm. If your values are already JSON strings for a wire format, sqlite avoids a double encode; if values are rich Python graphs, shelve avoids a schema. Measure the operation you actually ship, not only the friendlier API.
Pitfalls
- Comparing shelve without
syncto sqlite withcommit. - Assuming shelve is multi-process safe (it is not).
- Ignoring JSON encode cost on the sqlite path (included here on purpose).
- Treating lab 34 WAL timings as interchangeable with this default-journal run.
Reproduce
Evidence: summary.json, summary.txt.
Limits
One Linux box, tempfile disk, single writer. Not networked DB, not SQLCipher.
Takeaway
For 5000 local KV writes, sqlite3 insert ~217559 ops/s crushed shelve ~13249; gets were similar. Prefer sqlite for write-heavy local state; keep shelve for small Python-native caches.
Lab evidence
What I found running this
Ran the sqlite3 and shelve benchmark on Linux localhost with Python 3.13.5: 5,000 JSON-like records, seven rounds, p50 timings. sqlite3 insert measured about 217559 ops/s versus shelve about 13249; gets were 148945 versus 169920. Disk totals were 532480 versus 606208 bytes. The write gap surprised me.
Related links
Plate 68
gc.collect Cost Empty vs Cycles: Localhost Lab
Hands-on gc.collect cost empty vs cyclic garbage lab: real collect latency plus reclaim counts, measured on Linux localhost today in this lab for SREs.
Observability & SRE · 1 Oct 2026
Plate 16
ExitStack vs Nested with Resources: Localhost Lab
Hands-on contextlib.ExitStack vs nested with and manual close: real cycles/s for N resources, measured on Linux localhost in this hands-on lab for SREs.
Observability & SRE · 1 Oct 2026
Plate 40
itertools.batched vs Manual Chunking: Localhost Lab
Hands-on itertools.batched vs manual list-slice chunking: real items/s batching sequences, measured on Linux localhost today in this hands-on lab for SREs.
Observability & SRE · 1 Oct 2026