Chapter 20 · Production Capstone: Build and Operate a Complete SQLite Application Database
Test, Benchmark, Back Up, Restore, and Secure the Database
Turn the FieldNotes design into an operationally testable artifact with constraint, transaction, migration and concurrency tests, evidence-backed benchmarks, SQLite-aware backup/restore, integrity checks, and a security review.
Production readiness is a report of evidence
A database that survives a demo is not yet an operated system. This lesson builds one acceptance suite that covers schema, constraints, transactions, migrations, concurrency, hot query plans, backup/restore, integrity, and security. The output is a pass/fail operational report with enough environment metadata to reproduce failures.
Automate tests for schema shape, constraints, migrations, transaction atomicity, expected queries, and two-connection contention.
Benchmark representative reads and writes while recording SQLite/runtime/configuration/dataset and query plans.
Improve only evidence-backed bottlenecks rather than applying a PRAGMA recipe.
Create a live SQLite-aware backup, open it independently, run checks, and perform a restore drill.
Combine quick_check, integrity_check, foreign_key_check, and domain-specific consistency queries.
Review file permissions, bound parameters, extension loading, sensitive fields, backups, and logs against explicit pass/fail criteria.
Test matrix: each layer can fail differently
| Test layer | Example assertion | Why it matters |
|---|---|---|
| Schema | user_version=2; required tables/indexes/view/trigger exist. | Catches missing/partial migrations. |
| Constraint | Orphan device and malformed JSON are rejected. | Proves invariants across every writer. |
| Transaction | Injected failure leaves inspection/outbox counts unchanged. | Proves atomicity. |
| Idempotency | Same request_id twice yields one inspection + one event. | Makes retry safe after uncertain client outcomes. |
| Query | Device history returns expected rows/order. | Prevents “fast but wrong” optimization. |
| Concurrency | Second writer gets BUSY while first holds write lock; succeeds after release. | Tests real file semantics. |
| Migration | v1 fixture migrates to v2 once; newer DB is refused. | Prevents release drift. |
| Backup/restore | Backup independently opens/checks and restored domain counts match. | Proves recovery, not just backup creation. |
Representative Python tests
The course test suite should use temporary files for migration, WAL, locking, backup and restore tests. :memory: remains useful for isolated SQL logic that does not depend on file lifetime or multiple independent connections.
import sqlite3import tempfilefrom pathlib import Pathwith tempfile.TemporaryDirectory() as td: db = Path(td) / "fieldnotes.sqlite" con = open_db(db) migrate(con) con.execute("INSERT INTO site(site_code,site_name) VALUES('PLANT-A','Plant A')") con.execute("INSERT INTO device(site_id,device_code,device_name) VALUES(1,'P-007','Pump 007')") # Invalid JSON must be rejected by the database, not only the UI. try: con.execute("UPDATE device SET metadata_json=? WHERE device_id=1", ("{bad",)) raise AssertionError("invalid JSON was accepted") except sqlite3.IntegrityError: pass before = con.execute("SELECT count(*) FROM inspection").fetchone()[0] con.execute("BEGIN IMMEDIATE") try: con.execute(""" INSERT INTO inspection(device_id,request_id,technician_name, started_at,outcome,summary) VALUES(1,'inject-1','Tech A','2026-08-12T08:00:00Z','pass','demo') """) raise RuntimeError("injected") except RuntimeError: con.execute("ROLLBACK") after = con.execute("SELECT count(*) FROM inspection").fetchone()[0] assert after == before con.close()Idempotency test
kwargs = dict( request_id="req-0001", device_id=1, technician_name="Tech A", started_at="2026-08-12T08:00:00Z", finished_at="2026-08-12T08:10:00Z", outcome="follow_up", summary="seal temperature elevated", measurements={"temperature_c": 83.2}, severity="warning",)first, created1 = record_inspection(con, **kwargs)second, created2 = record_inspection(con, **kwargs)assert created1 is Trueassert created2 is Falseassert first["inspection_id"] == second["inspection_id"]assert con.execute( "SELECT count(*) FROM inspection WHERE request_id='req-0001'").fetchone()[0] == 1assert con.execute( "SELECT count(*) FROM sync_outbox WHERE event_id='inspection:req-0001'").fetchone()[0] == 1Benchmark record: no naked timing number
Before a timing result can support a change, record the environment and plan. Repeat trials, separate setup from the measured operation, and report distributions rather than one lucky run.
SELECT sqlite_version();SELECT sqlite_source_id();PRAGMA journal_mode;PRAGMA synchronous;PRAGMA page_size;PRAGMA cache_size;PRAGMA mmap_size;PRAGMA compile_options;SELECT count(*) AS devices FROM device;SELECT count(*) AS inspections FROM inspection;SELECT count(*) AS notes FROM maintenance_note;EXPLAIN QUERY PLANSELECT inspection_id, started_at, outcome, summaryFROM inspectionWHERE device_id=?ORDER BY started_at DESCLIMIT 20;from statistics import medianfrom time import perf_counterdef time_recent_history(con, device_id, trials=25): samples = [] for _ in range(trials): t0 = perf_counter() rows = con.execute( """SELECT inspection_id, started_at, outcome, summary FROM inspection WHERE device_id=? ORDER BY started_at DESC LIMIT 20""", (device_id,), ).fetchall() samples.append(perf_counter() - t0) return {"rows": len(rows), "median_s": median(samples), "min_s": min(samples), "max_s": max(samples)}Evidence-backed improvement example
Temporarily drop idx_inspection_device_started on a disposable benchmark copy, verify that the plan/timing worsens on a representative distribution, recreate it, run PRAGMA optimize when appropriate, and verify the plan/timing again. Do not run destructive benchmark DDL against the only production copy.
DROP INDEX idx_inspection_device_started;EXPLAIN QUERY PLANSELECT inspection_id, started_at, outcomeFROM inspectionWHERE device_id=17ORDER BY started_at DESC LIMIT 20;CREATE INDEX idx_inspection_device_startedON inspection(device_id, started_at DESC);PRAGMA optimize;EXPLAIN QUERY PLANSELECT inspection_id, started_at, outcomeFROM inspectionWHERE device_id=17ORDER BY started_at DESC LIMIT 20;SQLite-aware live backup
Python's Connection.backup() wraps SQLite's Online Backup API. It produces a transactionally consistent destination while the source can remain live. Use a unique temporary destination, verify it, then atomically promote/rename according to the host filesystem's supported semantics. Do not overwrite the last known-good backup before the new candidate passes checks.
from datetime import datetime, timezonefrom pathlib import Pathimport sqlite3stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")backup_dir = Path("backups")backup_dir.mkdir(exist_ok=True)candidate = backup_dir / f"fieldnotes-{stamp}.sqlite.partial"final = backup_dir / f"fieldnotes-{stamp}.sqlite"source = open_db("fieldnotes.sqlite")dest = sqlite3.connect(candidate, autocommit=True)source.backup(dest)dest.close(); source.close()check = sqlite3.connect(candidate, autocommit=True)assert check.execute("PRAGMA quick_check").fetchone()[0] == "ok"assert check.execute("PRAGMA integrity_check").fetchone()[0] == "ok"assert check.execute("PRAGMA foreign_key_check").fetchall() == []expected = check.execute("SELECT count(*) FROM inspection").fetchone()[0]check.close()candidate.replace(final)print(final, "verified inspections=", expected)Restore drill, not restore theory
A restore drill creates a separate target, opens it with the same runtime policy, checks migration/version compatibility, runs structural and domain checks, and exercises a representative read. Only then can the runbook claim a tested recovery path.
PRAGMA user_version;PRAGMA quick_check;PRAGMA integrity_check;PRAGMA foreign_key_check;SELECT count(*) FROM site;SELECT count(*) FROM device;SELECT count(*) FROM inspection;SELECT count(*) FROM maintenance_note;SELECT count(*) FROM sync_outbox WHERE delivered_at IS NULL;-- Domain consistency: every inspection device must be queryable through FK.SELECT count(*) AS orphan_inspectionsFROM inspection AS iLEFT JOIN device AS d ON d.device_id=i.device_idWHERE d.device_id IS NULL;-- Idempotency should remain unique after restore.SELECT request_id, count(*)FROM inspectionGROUP BY request_idHAVING count(*) > 1;Security acceptance review
| Control | Pass evidence |
|---|---|
| SQL injection | Repository tests store SQL-looking values as data; no value concatenation; dynamic identifiers are allowlisted. |
| File authority | DB directory, WAL/journal files and backup destination have reviewed OS permissions. |
| Extension loading | Core application does not enable arbitrary native extension loading. |
| trusted_schema | OFF established on application connections and tested with required schema. |
| Sensitive data | Logs avoid note bodies/measurement payloads unless explicitly redacted/approved. |
| Secrets | Remote sync credentials are outside database and backup files; rotation/recovery owner documented. |
| Backup confidentiality | Backup ACL/transport/retention match primary-data sensitivity. |
| Runtime patching | Qualified current SQLite build is inventoried; minimum feature version is not mistaken for patch policy. |
Operational acceptance report
FieldNotes Release Acceptance=============================Application version: __________________SQLite version/source id: _____________Compile/capability snapshot attached: [ ]DB user_version: ______________________Correctness[ ] schema manifest matches release[ ] constraint-negative tests pass[ ] idempotency replay test passes[ ] transaction failure injection leaves no partial state[ ] concurrency test reproduces + recovers from BUSYHealth[ ] quick_check = ok[ ] integrity_check = ok[ ] foreign_key_check = no rows[ ] domain consistency queries = expectedPerformance (target device)[ ] hot-query plans recorded[ ] read/write benchmark dataset recorded[ ] product latency/throughput SLOs met[ ] no unexplained busy/retry exhaustionRecovery[ ] online backup candidate verified[ ] restore drill completed[ ] restored counts/domain checks match policy[ ] off-device copy/retention verifiedSecurity[ ] parameter binding review pass[ ] file/backup permission review pass[ ] trusted_schema/extension policy pass[ ] logs/secrets review passRelease decision: PASS / FAILOwner: __________________ Date: _______Acceptance checkpoint
Would you release this database?
A green happy-path test is not enough.
- Why must a backup be opened and checked independently?
- Why is foreign_key_check separate from integrity_check?
- Why should benchmark DDL run on a disposable copy?
- What environment facts make a timing result reproducible?
- Why is a successful single-session test insufficient for concurrency?
- What is the difference between a minimum SQLite feature version and a patch qualification policy?
Review the answers
A created backup can still be incomplete, misplaced, incompatible, or logically wrong; independent validation and restore prove recovery. integrity_check does not report foreign-key violations. Benchmark experiments can alter schema/data and should not endanger the source of truth. Record SQLite/source ID, PRAGMAs, dataset/distribution, plan, hardware/filesystem/cache context and repeated metrics. Locking/WAL semantics require multiple file-backed connections. The minimum version states required features; patch policy says which currently supported builds are approved for security/reliability fixes.
Bridge to operations
Lesson 5 assumes the release has passed this report. The final task is to operate it predictably when reality breaks the happy path: busy writers, slow plans, WAL growth, failed migrations, misconfigured foreign keys, storage errors, and damaged backup candidates.