Chapter 20 · Production Capstone: Build and Operate a Complete SQLite Application Database

Operate, Troubleshoot, Document, and Defend the Final Architecture

Finish the course with a production runbook, incident diagnosis playbooks, runtime capability policy, measurable migration-away triggers, extension exercises, and a reliability checklist spanning the entire SQLite course.

Beginner150–210 minutesRunbook + eight incident drills + final architecture defenseSQLite 3.53.4 baselineCurrent patched SQLite target; optional extensions are capability-detectedLast reviewed: August 2026

The final deliverable is an operated system

The capstone ends where production work begins. The schema and repository are only part of the database system. Operations also owns startup capability checks, migrations, connection initialization, WAL/checkpoint observation, backup/restore, integrity monitoring, maintenance, incident response, version qualification, and the decision to keep or replace SQLite as requirements evolve.

01

Write a startup/runbook sequence that establishes version, migration, connection and journal assumptions before serving work.

02

Diagnose common SQLite incidents from evidence instead of changing random PRAGMAs.

03

Document minimum SQLite feature version, qualified current baseline, and required/optional compile capabilities.

04

Defend why SQLite fits the final FieldNotes architecture and define measurable migration-away triggers.

05

Plan extension exercises for sync, cloud backup, GUI, search, and alternate engines without breaking the data-access contract.

06

Connect every major course concept to one production reliability responsibility.

Startup/opening runbook

Order matters. A startup that begins serving requests before it verifies the schema/runtime can turn a recoverable deployment error into inconsistent application behavior.

text · FieldNotes startup sequence
1. Resolve approved local database path; reject network-share deployment.2. Record application version + sqlite_version() + sqlite_source_id().3. Verify minimum feature version and required JSON capability.4. Open administrative/migration connection.5. Enable and verify PRAGMA foreign_keys=ON.6. Set trusted_schema=OFF for application policy.7. Refuse user_version newer than this application understands.8. Acquire release/migration serialization; create verified pre-migration backup.9. Apply ordered migrations to target user_version.10. Run quick_check + foreign_key_check + schema/domain assertions.11. Open normal application connections.12. On each connection: foreign_keys=ON; trusted_schema=OFF; busy timeout policy.13. Establish/verify WAL and synchronous=FULL policy for this deployment.14. Record journal_mode/synchronous/compile capability snapshot.15. Run a lightweight representative read; only then mark database READY.16. Schedule/observe backup, WAL/checkpoint and maintenance according to workload evidence.

Runtime compatibility policy

The database file format is exceptionally stable, but SQL features and compiled modules still vary by library build. FieldNotes therefore documents both a minimum and a current qualified target.

ItemFieldNotes policyReason
Minimum SQLite feature version3.38.0Covers STRICT/RETURNING and JSON built-in-by-default era; capability probe still required.
Qualified current baseline at course generation3.53.4 (2026-07-24)Current patched upstream release; Chapter 9 also established avoiding vulnerable WAL-reset versions.
JSONRequiredSchema has json_valid/json_extract generated expressions; fail startup if unavailable.
Foreign keys/triggersRequiredCore invariants depend on them; foreign_keys must be enabled per connection.
WALRequired for this deployment decisionMust be a local suitable filesystem; one writer still applies.
FTS5/RTree/dbstat/sessionOptionalDo not fail core startup because optional extensions are absent.
Loadable native extensionsDisabled/not requiredReduce supply-chain and native-code attack surface.
Threading modeRecord and honor driver/runtime rulesConnection/thread sharing policy is driver + SQLite-build dependent.

Capability probe

sql · startup diagnostics
SELECT sqlite_version();SELECT sqlite_source_id();PRAGMA user_version;PRAGMA foreign_keys;PRAGMA journal_mode;PRAGMA synchronous;PRAGMA trusted_schema;PRAGMA compile_options;-- Required JSON capability: should return 1.SELECT json_valid('{"probe":1}');-- Optional module inventory where supported by current build.PRAGMA module_list;

Do not infer availability from the filename extension or from the programming-language version alone. Host operating systems, mobile frameworks, Python/Node runtimes, and application packages may bundle different SQLite libraries.

Incident 1: “database is locked” / SQLITE_BUSY

Start with the transaction timeline, not with an ever-larger timeout. Ask which connection owns the writer, how long it has held the transaction, whether it is doing network/UI/file work while the transaction remains open, and whether a read transaction is preventing checkpoint progress.

text · busy incident checklist
[ ] capture timestamp, operation/request_id, primary + extended SQLite code[ ] identify active/long transactions and connection ownership[ ] verify busy_timeout policy on the failing connection[ ] reproduce with two controlled file-backed connections[ ] inspect whether writes are short and idempotent/retry-safe[ ] remove network/user interaction from DB transaction[ ] measure retry count / exhausted busy budget[ ] only then consider workload-specific queueing or architecture change

Incident 2: slow query

Record the actual SQL shape with non-sensitive parameter categories, data volume/distribution, runtime version, statistics state, and EXPLAIN QUERY PLAN. A query that changed from SEARCH to SCAN after a release may indicate an index/migration/statistics problem; a SEARCH can still be slow if it returns huge payloads or performs application N+1 loops.

sql · slow-query evidence pack
SELECT sqlite_version();PRAGMA optimize;EXPLAIN QUERY PLANSELECT inspection_id, started_at, outcome, summaryFROM inspectionWHERE device_id=?ORDER BY started_at DESCLIMIT 20;-- Compare current schema/index manifest, row counts and realistic parameter distribution.SELECT count(*) FROM inspection;SELECT * FROM pragma_index_list('inspection');

Incident 3: WAL growth

WAL growth is a symptom to explain. A long-lived reader may pin an old end mark so a checkpoint cannot advance fully. A burst of writes may simply produce a temporarily larger log. Observe checkpoint results and active readers before forcing TRUNCATE/RESTART modes in a busy system.

sql · observe WAL checkpoint state
PRAGMA journal_mode;PRAGMA wal_autocheckpoint;PRAGMA wal_checkpoint(PASSIVE);

The checkpoint result reports whether it was busy plus WAL/checkpointed frame counts. Current SQLite defaults new connections to an autocheckpoint threshold of 1000 pages unless the build changes SQLITE_DEFAULT_WAL_AUTOCHECKPOINT; that default is a starting point to measure, not a tuning commandment.

Incident 4: failed migration

If a migration transaction fails before COMMIT, roll it back, preserve logs/error codes, and leave the pre-migration backup untouched. Do not “finish the schema manually” under pressure. Determine whether the release encountered unsupported syntax, unexpected old data, schema drift, missing capability, storage error, or a concurrent connection.

text · failed migration response
1. Stop serving writes from the incompatible release.2. Record sqlite_version/source_id/user_version and exact migration number.3. ROLLBACK if transaction remains active.4. Keep failed DB + logs for diagnosis; do not overwrite last good backup.5. Run quick_check / foreign_key_check if safe to inspect.6. Compare schema manifest with expected pre/post states.7. Fix migration or data precondition in source control.8. Rehearse on a restored copy of production-like data.9. Redeploy only after migration + rollback/recovery acceptance passes.

Incident 5: foreign keys unexpectedly not enforced

PRAGMA foreign_keys is connection-specific and changing it while a transaction is active is a no-op. If an orphan write slipped through a misconfigured connection, enabling enforcement later does not erase the bad row. Fix initialization, run foreign_key_check, and repair/recover data using a controlled procedure.

sql · foreign-key incident evidence
PRAGMA foreign_keys;PRAGMA foreign_key_check;SELECT * FROM pragma_foreign_key_list('inspection');

Incident 6: unexpected type

STRICT tables reduce dynamic-typing surprises but do not eliminate semantic mistakes such as a date string in the wrong format or valid JSON with the wrong business shape. Inspect typeof(), declared schema, validation code, and migration history. Do not “fix” production by editing bytes.

sql · type/representation diagnosis
SELECT typeof(device_id), typeof(started_at), typeof(measurements_json),       json_valid(measurements_json)FROM inspectionWHERE inspection_id=?;SELECT sql FROM sqlite_schema WHERE type='table' AND name='inspection';

Incident 7: disk full or read-only

SQLITE_FULL and SQLITE_READONLY are not concurrency errors. Retrying indefinitely cannot create disk space or permissions. Stop destructive churn, preserve diagnostics, inspect free space/quota/mount state/permissions, and make room or restore correct authority. Remember that WAL/journal/temp/backup operations may need writable directory space beyond the main database file.

text · storage incident questions
[ ] Is the database file writable by the service identity?[ ] Is the parent directory writable for WAL/journal/temp needs?[ ] Is the filesystem/device out of space, quota, or in read-only mode?[ ] Is a container using an ephemeral/read-only layer instead of the intended volume?[ ] Did a backup/restore destination fill the same filesystem?[ ] Are logs/core dumps consuming the application volume?[ ] After remediation, do integrity/domain checks still pass?

Incident 8: corrupt backup candidate

Never promote a backup that fails to open or does not return ok from integrity checks. Keep the last known-good backup, investigate the backup job/storage, and create a fresh candidate from the live source if the source is healthy. If the source is corrupt, Chapter 16's recovery path applies: restore known-good backup first; .recover is last-resort salvage, not a substitute for recovery media.

Maintenance runbook

text · observe first, act second
Daily/normal startup:  - version/capability + user_version checks  - lightweight application smoke query  - capture failed BUSY/storage/corruption signalsAfter schema/index changes:  - run PRAGMA optimize under current documented workflow  - record critical EQP plansLong-running WAL application:  - monitor WAL size + checkpoint progress  - investigate long readers before aggressive checkpointsBackup schedule:  - SQLite-aware candidate -> verify -> off-device copy -> retention  - scheduled restore drill with documented RPO/RTOPeriodic/deployment health:  - quick_check routinely as policy permits  - integrity_check on maintenance/backup validation schedule  - foreign_key_check + domain consistency checksVACUUM:  - only when measured free-space/locality/privacy requirement justifies rebuild cost

Why SQLite—and measurable triggers to migrate away

FieldNotes keeps SQLite because the SQL-issuing application and file are co-located, writes are short and queueable, offline operation matters, no server-role or multi-node HA requirement exists, and one portable local database simplifies deployment/recovery. Revisit that decision using product metrics, not fear of a large row count.

TriggerExample measurable evidenceLikely architectural response
Writer contention exceeds product SLOBounded retry exhaustion or write p99 misses persist across representative releases after shortening transactions/index fixes.Serialize/queue writes if product allows; otherwise evaluate client/server OLTP.
Direct multi-host database access becomes requirementMultiple machines must issue authoritative SQL against same dataset.Put engine with data: application server/client-server DB; do not move file to a network share.
Server-owned authorization/audit requiredIndependent services/users require GRANT/roles/central policies SQLite file permissions cannot supply.Client/server database/security architecture.
HA/RTO requires automatic failoverProduct commits to multi-node failover/replicas/PITR beyond application-managed sync design.Evaluate PostgreSQL/MySQL-class operational database or specialized replicated architecture.
Backup/restore/storage windows failSingle-file operational window repeatedly misses product RPO/RTO despite retention/design changes.Partition/shard by domain if valid or move to engine/storage architecture built for the scale.
Workload becomes analyticalDominant requirement becomes large scans/Parquet/OLAP rather than local transactional state.Complement/replace analytical path with DuckDB/warehouse/analytical DB while preserving OLTP contract where useful.

Extension exercises

The course ends with optional directions that preserve the core contract rather than rewriting the application around every new feature.

ExerciseConstraint
Application-layer sync/replicationTreat remote sync as an explicit protocol with idempotency/conflict semantics; never claim SQLite itself became distributed.
Cloud/off-device backup uploaderUpload only after local SQLite-aware backup verification; protect credentials and encryption/retention.
GUI/desktop viewerOpen read-only where possible; do not bypass migrations/invariants with direct edit controls.
Optional FTS5 note searchCapability-detect FTS5, define synchronization strategy, and retain a non-FTS core startup path if search is optional.
Alternate PostgreSQL backendPreserve repository/service contract; re-test types, transactions, concurrency, SQL dialect, migrations, errors and operational semantics.
DuckDB analytical companionExport/copy analytical datasets; keep SQLite responsible for local transactional state if that remains its strength.

Course-wide production reliability checklist

Course conceptCapstone responsibility
Foundations / CLIKnow which library/CLI you are actually running; inspect files safely.
Schema / ROWID / typesChoose keys/table organization intentionally; use STRICT/affinity knowledge.
Constraints / DMLPut durable invariants in constraints; use UPSERT/RETURNING intentionally.
Expressions / JSONKeep semantics explicit; validate semi-structured data and promote stable paths.
Transactions / savepointsMap business invariants to short atomic boundaries; recover deliberately.
Concurrency / WALExpect one writer; bound busy handling; observe readers/checkpoints.
Indexes / plannerMeasure access patterns with EQP; maintain only justified indexes/statistics.
Storage internalsUnderstand pages/freelist/WAL/temp files when diagnosing size/I/O.
Views / triggers / ATTACHUse higher-level schema features sparingly and document side effects/boundaries.
Virtual tables/extensionsCapability-detect specialized modules and treat native extension loading as a trust decision.
Application drivers / C mental modelBind values, own connection/statement/transaction lifecycle, map error codes.
Backup/recoveryUse SQLite-aware copies, verify, restore drill, and keep salvage as last resort.
Migrations/testingVersion schema in source control; test constraints, failures and multi-connection semantics.
Performance/maintenanceMeasure before tuning; batch transactions; use current optimize/VACUUM/checkpoint guidance.
Security/deploymentOS file authority + app authorization; local filesystem assumptions; explicit encryption/secret design.
Architecture fitKeep SQLite while requirements match; migrate from measured product needs, not folklore.

Final architecture defense

Defend FieldNotes in a design review

Answer as the owner who will operate this database.

  1. Why does FieldNotes require SQLite 3.38.0+ features but still qualify 3.53.4 as its current target?
  2. What incident would make increasing busy_timeout the wrong first action?
  3. Why can a verified backup still fail the product RTO requirement?
  4. What evidence separates a slow query from an application N+1 problem?
  5. Why is WAL growth not automatically solved by TRUNCATE checkpoints?
  6. Name three measurable requirements that would justify moving from SQLite to a client/server database.
Review the answers

The minimum version describes required SQL capabilities; current qualification also captures reliability/security fixes and tested builds. A long transaction or network call holding the writer must be fixed rather than hidden behind longer waiting. A technically valid backup may still restore too slowly or be stored too remotely to meet RTO, so restore drills measure the whole recovery path. Query plans/timings plus application statement counts reveal whether one SQL statement is slow or the application is issuing too many. Long readers can block checkpoint progress; forced truncation does not remove the root cause. Sustained writer-SLO failure, direct multi-host access, server-role requirements, automatic HA/replication/PITR, or unmanageable single-file operational windows are examples of measurable migration drivers.

Course completion

You now have the full production loop: model the problem, implement constrained schema, own transactional application access, test concurrency and failures, measure plans/performance, back up and restore, secure the file/runtime boundary, operate incidents, and re-evaluate whether SQLite still matches the system. That loop—not a list of PRAGMAs—is the transferable SQLite skill.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only assets compatible with the Ethereum/ERC-20 network. Do not send TRC-20/TRON assets.