A complete guide to advanced SQL, runtime behavior, search, vectors, agent memory, GraphRAG, OpenLineage, concurrency, limits, security, containers, cloud providers, and migration strategy.
Primary audienceSolutions architects and senior engineers
Operating modelLocal-first through cloud transition
Evidence36 linked sources
Document version1.0.0
No sections matched.
Try a broader term such as “WAL”, “vector”, “cloud”, “memory”, or “security”.
01 · Orientation
What this guide is for
A decision-oriented reference for architects and engineers evaluating SQLite with Bun for local applications, AI memory, search, vector retrieval, containers, and cloud transition.
Central conclusion
SQLite is strongest when one authoritative owner controls each database file.
Bun makes that model unusually productive for TypeScript. It does not remove SQLite’s single-writer serialization, filesystem assumptions, or absence of native distributed availability.
Best default
bun:sqlite + WAL + short transactions + rebuildable search indexes
Use a repository boundary and a deliberate writer coordinator. Add FTS5 first. Add vectors when semantic recall is measured and justified.
Cloud boundary
Do not horizontally scale a writable SQLite file by mounting it into generic stateless replicas.
Scale by ownership: one stateful owner, tenant/file sharding, immutable replicas, single-primary replication, or a managed SQLite-compatible service.
How to use the document
1
Start with the decision framework.
Determine whether the workload fits SQLite before selecting APIs or extensions.
2
Choose a Bun surface.
bun:sqlite, Bun.SQL, and node:sqlite solve different problems.
3
Adopt the operational model.
Writer ownership, WAL lifecycle, backups, and storage topology are architectural requirements.
4
Add derived capabilities.
FTS, vectors, graph projections, and lineage should be reconstructible from canonical records.
02 · Decision
When SQLite is the right database
SQLite is not “small PostgreSQL.” It is an embedded transactional engine optimized for local ownership, low operational overhead, and high read performance.
Strong fit
CLI, desktop, mobile, local agent, or developer tool.
Single-node API or MCP server with bounded writes.
Per-user, per-tenant, per-workspace, or per-repository databases.
Read-heavy workloads with one coordinated write stream.
Portable knowledge snapshots and embedded catalogs.
Local FTS, vector retrieval, graph projections, and memory.
Edge or offline-first systems with explicit synchronization.
Poor fit
Many independent hosts must write the same logical database.
Active-active multi-region writes are mandatory.
Stateless replicas expect one shared writable file.
Database-native users, roles, network authentication, and centralized policy are required.
Transparent HA, managed PITR, and near-zero failover are non-negotiable.
Write throughput must scale by adding replicas.
Can one service or shard own the file?
→
Are writes short and serializable?
→
Can HA be external or provider-managed?
→
SQLite is credible
If any answer is “no,” introduce a service boundary, shard ownership, use a distributed SQLite derivative, or move the authoritative store to a client/server database. SQLite’s own guidance identifies high write concurrency and network-separated storage as key client/server boundaries. S02S05
Decision matrix
Workload
Recommended store
Why
Primary control
Local AI agent memory
SQLite per agent/workspace
Fast local reads, portable state, no service dependency
Revisioned memory and rebuildable indexes
Single-node internal API
SQLite on local/block storage
Operational simplicity and low latency
One writer coordinator and tested recovery
Many tenant-isolated workloads
Database-per-tenant
Independent writer locks and isolation
Shard catalog and migration fleet tooling
Global read distribution
Immutable snapshots or single-primary replicas
Local reads without multi-writer ambiguity
Version routing and freshness contracts
Shared enterprise platform
PostgreSQL/distributed SQL; SQLite at edge
Many writers, HA, RBAC, central operations
Repository abstraction and cache invalidation
03 · Foundations
The engine model you must preserve
SQLite is an in-process C library operating on pages and files. Transactions rely on filesystem locking, journals, and storage durability—not a network database daemon.
Filesystem and block storageLock correctness, atomic writes, fsync semantics, available space
Concurrent readersMany
Concurrent writers per fileOne
Network protocolNone
Built-in replicationNone
Why the ownership model matters
WAL mode allows readers to retain snapshots while one writer appends changes. It does not create independent row-level writers. All writes to unrelated tables and tenants still serialize through one database file. S04S05
Rollback journal and WAL
Mode
Reader/writer behavior
Advantages
Constraints
Rollback journal
Writer eventually excludes readers while pages are committed
Simple file model; works without WAL shared-memory index
Choosing between bun:sqlite, Bun.SQL, and node:sqlite
Bun exposes three useful interfaces. The right choice depends on performance, portability, governance, and how much SQLite-specific behavior the application needs.
For CLIs, local agents, MCP servers, desktop tools, embedded indexes, and single-node services.
Use Bun.SQL
For tagged templates and repository code that may target PostgreSQL/MySQL. Portability stops at API shape.
Use node:sqlite
For Node compatibility, authorizers, defensive mode, runtime limits, custom SQL functions, changesets, and backup APIs.
Bun documents bun:sqlite as synchronous and exposes statement caching, transactions, bigint handling, serialization, extension loading, and file controls. Its unified SQL client supports SQLite, PostgreSQL, and MySQL through a tagged-template API. S14S15S17
05 · Implementation
A production baseline in TypeScript
Start with explicit connection policy, schema migrations, strict data contracts, and a repository boundary before adding search or AI capabilities.
Strict parameter mode catches missing named bindings. Safe integers avoid silent precision loss when SQLite 64-bit integers exceed JavaScript’s safe-number range. S14
Migration runner
migrations.ts
import type { Database } from "bun:sqlite";
interface Migration {
version: number;
name: string;
sql: string;
}
const migrations: readonly Migration[] = [
{
version: 1,
name: "canonical-schema",
sql: `
CREATE TABLE document (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
metadata TEXT NOT NULL DEFAULT '{}'
CHECK (json_valid(metadata)),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
) STRICT;
CREATE INDEX document_tenant_updated_idx
ON document(tenant_id, updated_at DESC);
`,
},
];
export function migrate(db: Database): void {
db.exec(`
CREATE TABLE IF NOT EXISTS schema_migration (
version INTEGER PRIMARY KEY,
name TEXT NOT NULL,
applied_at INTEGER NOT NULL
) STRICT;
`);
const applied = new Set(
db.query<{ version: number }, []>(
`SELECT version FROM schema_migration`
).all().map((row) => row.version),
);
const apply = db.transaction((migration: Migration) => {
db.exec(migration.sql);
db.query(`
INSERT INTO schema_migration(version, name, applied_at)
VALUES ($version, $name, $appliedAt)
`).run({
version: migration.version,
name: migration.name,
appliedAt: Date.now(),
});
});
for (const migration of migrations) {
if (!applied.has(migration.version)) {
apply.immediate(migration);
}
}
}
06 · Core usage
Prepared statements, transactions, and write coordination
Bun’s direct SQLite API is strongest when SQL is prepared once, reused with bounded parameters, and executed inside short synchronous transactions.
Statement lifecycle
Method
Use
Gotcha
db.query(sql)
Reusable SQL; cached prepared statement by SQL text
Unbounded generated SQL can grow the cache
db.prepare(sql)
One-off or deliberately managed statement
Finalize when lifecycle is explicit
.get()
First row
TypeScript generics do not validate runtime schema
.all()
Materialize all rows
Large result sets allocate memory
.iterate()
Process rows without one large array
Still synchronous; long iterators hold read snapshots
.values()
Array-of-arrays result
Column order becomes part of the contract
.run()
Mutation metadata
Use RETURNING when rows are needed
Atomic queue claim
Atomic read-modify-write
interface ClaimedTask {
id: string;
payload: string;
attempts: bigint;
leaseUntil: bigint;
}
const claimStatement = db.query<
ClaimedTask,
{ workerId: string; now: bigint; leaseUntil: bigint }
>(`
UPDATE task
SET
status = 'running',
lease_owner = $workerId,
lease_until = $leaseUntil,
attempts = attempts + 1,
updated_at = $now
WHERE id = (
SELECT id
FROM task
WHERE status = 'pending'
OR (status = 'running' AND lease_until < $now)
ORDER BY priority DESC, created_at
LIMIT 1
)
RETURNING
id,
payload,
attempts,
lease_until AS leaseUntil
`);
const claimTask = db.transaction((workerId: string) => {
const now = BigInt(Date.now());
return claimStatement.get({
workerId,
now,
leaseUntil: now + 60_000n,
});
});
const task = claimTask.immediate(crypto.randomUUID());
SQLite supports sophisticated relational, analytical, JSON, graph, temporal, and virtual-table patterns. Bun passes these capabilities through the loaded SQLite engine.
Recursive CTEs
Trees, graph traversal, dependency expansion, date series, and hierarchical rollups.
Native SQL
Window functions
Rank, running totals, lead/lag, partitioned analytics, and deduplication.
Native SQL
JSON
Flexible metadata with indexed generated projections and table-valued traversal.
Built in
Virtual tables
FTS5, R*Tree, vectors, CSV, geospatial, and extension-defined data sources.
Extensible
Partial/expression indexes
Smaller indexes aligned to active states, sparse attributes, or computed keys.
Planner-aware
WITHOUT ROWID
Efficient composite-key and mapping tables when the natural primary key is the storage key.
Schema choice
Recursive graph traversal
Bounded cycle-safe traversal
WITH RECURSIVE walk(entity_id, depth, path) AS (
SELECT
:start_id,
0,
',' || :start_id || ','
UNION ALL
SELECT
relation.target_id,
walk.depth + 1,
walk.path || relation.target_id || ','
FROM walk
JOIN relation
ON relation.source_id = walk.entity_id
WHERE relation.tenant_id = :tenant_id
AND walk.depth < :max_depth
AND instr(
walk.path,
',' || relation.target_id || ','
) = 0
)
SELECT
entity_id,
MIN(depth) AS distance
FROM walk
GROUP BY entity_id
ORDER BY distance
LIMIT :limit;
Recursive CTEs are appropriate for bounded, shallow graph expansion. Deep graph algorithms and corpus-wide community detection should run in an indexing worker or specialized graph engine. S12
Windowed retrieval analysis
SELECT
query_id,
memory_id,
fused_score,
row_number() OVER (
PARTITION BY query_id
ORDER BY fused_score DESC
) AS rank_within_query,
fused_score - lead(fused_score) OVER (
PARTITION BY query_id
ORDER BY fused_score DESC
) AS margin_to_next
FROM retrieval_candidate;
JSON with generated projections
CREATE TABLE document (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
metadata TEXT NOT NULL
CHECK (json_valid(metadata)),
source_uri TEXT GENERATED ALWAYS AS (
json_extract(metadata, '$.source.uri')
) VIRTUAL,
media_type TEXT GENERATED ALWAYS AS (
json_extract(metadata, '$.mediaType')
) STORED
) STRICT;
CREATE INDEX document_source_uri_idx
ON document(tenant_id, source_uri)
WHERE source_uri IS NOT NULL;
Use JSON for optional, low-frequency metadata. Promote security, tenant, status, time, and frequently filtered values to relational columns or generated/indexed projections. S10S31S32
Data structure selection
Need
SQLite representation
Trade-off
Canonical entities
STRICT table
Strong constraints; conventional B-tree indexes
Many-to-many mapping
Composite primary key, often WITHOUT ROWID
Efficient natural-key lookup; different rowid semantics
Optional metadata
JSON + generated columns
Flexibility without sacrificing common access paths
Full-text lookup
FTS5 virtual table
Derived index must remain synchronized
Semantic lookup
Vector virtual table or BLOB extension
Native packaging and model-version lifecycle
Relationships
Adjacency-list edge table
Good for bounded traversal; not graph-native analytics
Hierarchy
Closure table or materialized path
Fast reads versus more expensive moves/writes
Temporal/geospatial intervals
R*Tree
Specialized multidimensional range index
08 · Query engineering
Indexes, plans, and performance validation
SQLite’s planner is capable, but the application must align indexes to equality filters, ranges, ordering, and the actual projection.
CREATE INDEX memory_active_lookup_idx
ON memory(
tenant_id,
kind,
updated_at DESC,
confidence,
importance
)
WHERE status = 'active';
Plan inspection
EXPLAIN QUERY PLAN
SELECT
id,
content,
confidence,
importance,
updated_at
FROM memory
WHERE tenant_id = :tenant_id
AND status = 'active'
AND kind = :kind
ORDER BY updated_at DESC
LIMIT 25;
Plan smells
SCAN on a table expected to grow.
USE TEMP B-TREE FOR ORDER BY on a latency-sensitive query.
An index used for filtering but not sorting.
Functions applied to indexed columns without a matching expression index.
JSON predicates that differ syntactically from the expression index.
Automatic indexes appearing repeatedly—often evidence of a missing deliberate index.
Bun statement cache discipline
db.query(sql) caches the compiled statement by SQL text. Reuse stable statements. Use prepare() for unique generated statements, and avoid embedding values in SQL strings. S14
09 · Search
FTS5 and lexical retrieval
FTS5 should usually precede vector search. Technical corpora depend heavily on exact identifiers, phrases, acronyms, paths, versions, and error codes.
Canonical table and external-content FTS
CREATE TABLE document_chunk (
id INTEGER PRIMARY KEY,
tenant_id TEXT NOT NULL,
document_id TEXT NOT NULL,
heading TEXT,
content TEXT NOT NULL,
created_at INTEGER NOT NULL
) STRICT;
CREATE VIRTUAL TABLE document_chunk_fts USING fts5(
heading,
content,
content='document_chunk',
content_rowid='id',
tokenize='unicode61 remove_diacritics 2',
prefix='2 3'
);
CREATE TRIGGER document_chunk_fts_ai
AFTER INSERT ON document_chunk BEGIN
INSERT INTO document_chunk_fts(rowid, heading, content)
VALUES (new.id, new.heading, new.content);
END;
CREATE TRIGGER document_chunk_fts_ad
AFTER DELETE ON document_chunk BEGIN
INSERT INTO document_chunk_fts(
document_chunk_fts, rowid, heading, content
) VALUES (
'delete', old.id, old.heading, old.content
);
END;
Ranked search with snippets
interface LexicalResult {
id: number;
documentId: string;
heading: string | null;
snippet: string;
rank: number;
}
const lexicalSearch = db.query<
LexicalResult,
{ query: string; tenantId: string; limit: number }
>(`
SELECT
chunk.id,
chunk.document_id AS documentId,
chunk.heading,
snippet(
document_chunk_fts,
1,
'<mark>',
'</mark>',
' … ',
24
) AS snippet,
bm25(document_chunk_fts, 2.0, 1.0) AS rank
FROM document_chunk_fts
JOIN document_chunk AS chunk
ON chunk.id = document_chunk_fts.rowid
WHERE document_chunk_fts MATCH $query
AND chunk.tenant_id = $tenantId
ORDER BY rank
LIMIT $limit
`);
Use separate tokenization strategies
Index
Best for
Examples
unicode61
Language and document search
“authorization policy”, phrases, proximity
trigram
Substring and identifier lookup
reqId, routes, filenames, partial product names
B-tree
Exact and normalized keys
IDs, hashes, canonical names, prefixes
Maintenance
-- Validate consistency with an external-content table.
INSERT INTO document_chunk_fts(document_chunk_fts)
VALUES ('integrity-check');
-- Reconstruct the FTS projection from canonical content.
INSERT INTO document_chunk_fts(document_chunk_fts)
VALUES ('rebuild');
-- Merge index segments.
INSERT INTO document_chunk_fts(document_chunk_fts)
VALUES ('optimize');
FTS5 supports MATCH syntax, phrases, prefixes, proximity, BM25, snippets, custom tokenizers, and maintenance commands. S09
10 · Vector retrieval
Exact KNN, ANN, and hybrid search
SQLite can host vector representations beside canonical records and lexical indexes. The extension choice determines search algorithm, filtering, packaging, and maturity.
Option
Model
Strength
Primary caveat
sqlite-vec
Stable exact KNN through vec0; experimental ANN work
Portable JavaScript integrations, metadata and partition columns
Pre-1.0 lifecycle and native extension packaging
SQLite Vec1 0.7
IVFADC + OPQ ANN; exact/flat options
Official SQLite project, C implementation, cosine/L2
Documentation states testing and optimization remain incomplete
SQLite-Vector
Normal BLOB columns and quantized scans
Representation flexibility and reconstruction
Compressed scan is not the same as graph ANN
Turso/libSQL vector
Provider-integrated vector index
Managed/synchronized environment
Provider semantics rather than vanilla SQLite
Exact versus approximate
Exact KNN
Deterministic top-K baseline.
No training.
Linear work over eligible vectors.
Excellent evaluation ground truth.
ANN
Lower latency at larger scale.
Recall becomes a tunable metric.
Index build, training, and rebuild lifecycle.
Oversample and exact-rerank candidates.
sqlite-vec with Bun
Install
bun add sqlite-vec
Initialize vector storage
import { Database } from "bun:sqlite";
import * as sqliteVec from "sqlite-vec";
const DIMENSIONS = 384;
if (process.platform === "darwin") {
Database.setCustomSQLite(
process.env.SQLITE_LIBRARY_PATH ??
"/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib"
);
}
const db = new Database("./data/knowledge.sqlite", {
create: true,
strict: true,
safeIntegers: true,
});
sqliteVec.load(db);
db.exec(`
CREATE TABLE chunk (
id INTEGER PRIMARY KEY,
tenant_id TEXT NOT NULL,
document_id TEXT NOT NULL,
content TEXT NOT NULL,
embedding_model TEXT NOT NULL,
created_at INTEGER NOT NULL
) STRICT;
CREATE VIRTUAL TABLE chunk_vec USING vec0(
chunk_id INTEGER PRIMARY KEY,
tenant_id TEXT PARTITION KEY,
embedding FLOAT[${DIMENSIONS}]
DISTANCE_METRIC=cosine
);
`);
WITH nearest AS (
SELECT chunk_id, distance
FROM chunk_vec
WHERE embedding MATCH :query_embedding
AND tenant_id = :tenant_id
AND k = :candidate_count
ORDER BY distance
)
SELECT
chunk.id,
chunk.document_id,
chunk.content,
nearest.distance
FROM nearest
JOIN chunk ON chunk.id = nearest.chunk_id
WHERE chunk.tenant_id = :tenant_id
ORDER BY nearest.distance
LIMIT :final_count;
sqlite-vec documents JavaScript bindings, vector virtual tables, KNN MATCH queries, metadata filtering, and partition keys. On macOS, Bun requires a non-Apple SQLite library before loading extensions. S14S19
Hybrid rank fusion
interface RankedCandidate { id: number; rank: number }
export function reciprocalRankFusion(
lexical: readonly RankedCandidate[],
semantic: readonly RankedCandidate[],
constant = 60,
): Array<{ id: number; score: number }> {
const scores = new Map<number, number>();
for (const list of [lexical, semantic]) {
for (const candidate of list) {
scores.set(
candidate.id,
(scores.get(candidate.id) ?? 0) +
1 / (constant + candidate.rank),
);
}
}
return [...scores]
.map(([id, score]) => ({ id, score }))
.sort((a, b) => b.score - a.score);
}
Rank fusion avoids pretending BM25 and cosine distance share a calibrated numeric scale. Add structured exact matches and graph proximity as additional channels when evaluation shows value.
Vec1 maturity
SQLite Vec1 version 0.7 provides ANN through IVFADC and OPQ with cosine and L2 support. Its roadmap explicitly says testing is insufficient and optimization remains. Treat it as an evaluation candidate behind a replaceable adapter. S18
11 · AI knowledge
Agent memory, graphs, and GraphRAG
A reliable memory system separates evidence, derived assertions, revisions, retrieval indexes, and feedback. Embeddings alone are not memory governance.
Memory classes
Class
Meaning
Lifecycle
Episodic
A specific event or interaction
Time-bound; may compact into summaries
Semantic
Durable concept or fact
Revisioned and evidence-backed
Profile
Stable preference or operating constraint
Explicitly superseded or retracted
Procedural
How to perform a task
Versioned with tools and environment
Working
Short-lived active-session state
Expires aggressively
Summary
Compacted historical context
Retains source references
Task
Goal, status, and next action
State-machine lifecycle
Canonical memory schema
CREATE TABLE source_event (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
source_type TEXT NOT NULL,
payload TEXT NOT NULL CHECK (json_valid(payload)),
content_hash TEXT NOT NULL,
observed_at INTEGER NOT NULL,
ingested_at INTEGER NOT NULL,
UNIQUE (tenant_id, content_hash)
) STRICT;
CREATE TABLE memory (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
kind TEXT NOT NULL,
content TEXT NOT NULL,
metadata TEXT NOT NULL DEFAULT '{}'
CHECK (json_valid(metadata)),
confidence REAL NOT NULL CHECK (confidence BETWEEN 0 AND 1),
importance REAL NOT NULL CHECK (importance BETWEEN 0 AND 1),
status TEXT NOT NULL CHECK (
status IN ('candidate','active','superseded','retracted','expired')
),
observed_at INTEGER NOT NULL,
valid_from INTEGER,
valid_until INTEGER,
expires_at INTEGER,
supersedes_id TEXT REFERENCES memory(id),
source_event_id TEXT REFERENCES source_event(id),
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
) STRICT;
CREATE TABLE memory_revision (
memory_id TEXT NOT NULL REFERENCES memory(id),
revision INTEGER NOT NULL,
content TEXT NOT NULL,
metadata TEXT NOT NULL CHECK (json_valid(metadata)),
confidence REAL NOT NULL,
source_event_id TEXT REFERENCES source_event(id),
created_at INTEGER NOT NULL,
PRIMARY KEY (memory_id, revision)
) WITHOUT ROWID, STRICT;
CREATE TABLE entity (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
entity_type TEXT NOT NULL,
canonical_name TEXT NOT NULL,
description TEXT,
metadata TEXT NOT NULL DEFAULT '{}'
CHECK (json_valid(metadata)),
UNIQUE (tenant_id, entity_type, canonical_name)
) STRICT;
CREATE TABLE relation (
id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
source_id TEXT NOT NULL REFERENCES entity(id),
target_id TEXT NOT NULL REFERENCES entity(id),
relation_type TEXT NOT NULL,
weight REAL NOT NULL DEFAULT 1,
confidence REAL NOT NULL DEFAULT 1,
valid_from INTEGER,
valid_until INTEGER,
evidence_id TEXT REFERENCES source_event(id)
) STRICT;
CREATE INDEX relation_outgoing_idx
ON relation(tenant_id, source_id, relation_type, target_id);
CREATE INDEX relation_incoming_idx
ON relation(tenant_id, target_id, relation_type, source_id);
GraphRAG boundary
GraphRAG adds entity extraction, relationship extraction, entity resolution, community detection, hierarchical summaries, and local/global query routing. SQLite is well suited to store the canonical chunks, graph edges, community assignments, summaries, and retrieval indexes. Corpus-scale clustering should generally run outside online SQL.
Question type
Retrieval mode
Exact identifier or route
B-tree and FTS
“What is X?”
Hybrid chunk retrieval
“How is X related to Y?”
Entity resolution + bounded graph expansion
“What are the major themes?”
Community/global summaries
“What changed over time?”
Temporal filters + revisions + graph evidence
12 · Provenance
SQLite with OpenLineage and OpenTelemetry
SQLite can participate in OpenLineage even without a native connector. Instrument the TypeScript job that reads and writes SQLite datasets.
OpenLineage
Explains durable provenance: which job transformed which datasets, under which model and configuration.
OpenLineage models datasets, jobs, runs, and events abstractly. A SQLite table or derived index can be represented as a dataset when the emitting application supplies a stable namespace and name. S20S21
CREATE TABLE lineage_outbox (
id TEXT PRIMARY KEY,
event_json TEXT NOT NULL CHECK (json_valid(event_json)),
status TEXT NOT NULL DEFAULT 'pending'
CHECK (status IN ('pending','sending','sent','failed')),
attempts INTEGER NOT NULL DEFAULT 0,
next_attempt_at INTEGER NOT NULL,
created_at INTEGER NOT NULL,
sent_at INTEGER
) STRICT;
CREATE INDEX lineage_outbox_pending_idx
ON lineage_outbox(status, next_attempt_at)
WHERE status IN ('pending', 'failed');
Commit the output records and an OpenLineage event into the outbox in one SQLite transaction. Publish outside the transaction, retry transient failures, and mark delivery state. This prevents a metadata-backend outage from rolling back the business transformation.
SQLite’s compiled limits are generally high. Operational limits—writer contention, recovery time, maintenance space, storage topology, and latency—usually arrive first.
Resource
Typical default
Upper bound or constraint
Practical meaning
String/BLOB or encoded row
1,000,000,000 bytes
About 2.147 GB implementation ceiling
Use object storage for large media and archives
Database file
Page-size dependent
~281 TB at 64 KiB pages
Restore, backup, and maintenance windows dominate first
Columns
2,000
Compile-time up to 32,767
Very wide schemas create planning/code-generation cost
Tables in a join
64
Hard planner limit
Generated mega-joins need staging or redesign
SQL statement length
1,000,000,000 bytes
Compile configurable
Bind data and batch work instead
Bound parameters
32,766 in modern SQLite
Compile configurable
Provider limits can be dramatically lower
Compound SELECT terms
500
Compile configurable
Stage large generated unions
Attached databases
10
125 compile-time maximum
ATTACH is not a distributed sharding layer
SQLite documents a maximum file size of approximately 281 TB with the largest page size, but the filesystem, disk capacity, backup process, and operational blast radius are the real constraints. S03
Capacity model
Provisioned capacity must include:
main database
+ WAL or rollback journal
+ temporary sort and index files
+ table-rebuild migration copy
+ backup or snapshot staging
+ vector/FTS rebuild space
+ filesystem safety reserve
Provider limits are not vanilla limits
Cloudflare D1, for example, currently documents 10 GB per paid database, 2 MB maximum row/string/BLOB, 100 columns, 100 bound parameters, 100 KB SQL statements, and one-at-a-time query processing per individual database. These are provider contracts—not SQLite core limits. S22
14 · Concurrency
Writer serialization, locks, snapshots, and WAL checkpoints
Most production SQLite incidents are caused by transaction duration, filesystem assumptions, or unmonitored WAL lifecycle—not incorrect SQL syntax.
One application-level writer queue. Multiple pooled connections do not create write throughput.
No remote I/O in transactions. Compute before BEGIN IMMEDIATE.
Bound every batch. Limit rows, bytes, and elapsed transaction time.
Treat SQLITE_BUSY as expected backpressure. Retry only idempotent work with jitter and a deadline.
Close readers promptly. Long snapshots delay checkpoints.
Monitor WAL size and checkpoint progress. Do not rely on defaults without visibility.
Checkpoint starvation
A checkpoint cannot advance past pages needed by active readers. Continuous overlapping readers can prevent a full reset, grow the WAL, consume disk, and increase recovery work. S04
Workers improve event-loop responsiveness and parallel read CPU use. They do not create parallel writers. Each worker should own its connection and close readers promptly.
Horizontal write scaling
tenant-a.sqliteWriter Atenant-b.sqliteWriter Btenant-c.sqliteWriter C
Separate files have independent writer locks. Sharding is credible when transactions remain inside a tenant, workspace, repository, device, region, or agent boundary. It introduces fleet migrations, backup inventory, routing, and cross-shard query complexity.
16 · Containers
Container and Kubernetes deployment patterns
Container orchestration assumes replaceable replicas. Writable SQLite assumes an authoritative storage owner. The deployment must make that ownership explicit.
Anti-patterns
Database in image layer
State disappears when the container is replaced.
One copy per replica
Replicas silently diverge.
Shared NFS/RWX WAL
Cross-host lock and shared-memory assumptions fail.
Horizontal autoscaling of writers
Creates contention or divergent files—not write scale.
Concurrent rolling-update writers
Migration and ownership races.
Raw file copy during writes
Main database may not include committed WAL state.
ReadWriteOncePod constrains a supported CSI volume to one pod and is preferable when single-pod ownership is critical. It does not provide failover, backups, or replication by itself. S29
Pattern B: database-owner service
Stateless replicasScale horizontally
→
SQLite owner APIAuth + one writer
→
Local/block volumeSQLite + WAL
This is the safest generic cloud pattern. Upstream clients use a network API while SQLite remains local to the owner. The service boundary also makes a later PostgreSQL migration easier.
Open read-only and atomically route to the new generation.
Litestream and LiteFS
Tool
Purpose
Does not provide
Litestream
Continuous SQLite-aware replication and restore, often to object storage
Parallel writers or a general multi-primary database
LiteFS
Single-primary replicated filesystem with local read replicas
Active-active writes; synchronous replication by default
LiteFS routes writes to one primary and asynchronously distributes changes. Litestream’s container guidance recommends local storage and warns about incompatible cross-OS or network lock models. S26S27
17 · Cloud
Cloud transition and provider models
“Cloud SQLite” can mean hosted SQL, synchronized embedded replicas, actor-owned databases, single-primary replication, or a Raft service. Compatibility and consistency vary materially.
Product-specific: remote primary, local sync, or concurrent modes
Local-first and many-database architectures
Validate current SDK and semantic model
SQLite Cloud
Managed distributed service built on SQLite
Provider-managed cluster
Managed operations and SQLite-oriented APIs
Plan limits and vendor compatibility surface
LiteFS
Replicated SQLite filesystem
One lease-elected primary
Local reads across replicas
Write routing, lag, and failover operations
rqlite
Raft database service built on SQLite
Leader/consensus serializes writes
Moderate control-plane workloads requiring HA
HTTP service semantics and lower write throughput
PostgreSQL
Client/server relational DB
Many concurrent writers
Shared enterprise authoritative store
More infrastructure and migration work
Current provider observations
D1: current paid limits include 10 GB per database, 100 columns, 2 MB row/string/BLOB, 100 parameters, and 100 KB SQL. Cloudflare explicitly positions horizontal scale around many smaller databases. S22
Durable Objects: each SQLite-backed object has an ownership boundary well aligned to per-tenant or per-agent state, with current per-object storage limits documented by Cloudflare. S23
Turso: embedded replicas keep local reads and delegate writes to a cloud primary; newer synchronization products introduce explicit local-write and sync semantics. Verify the current product and SDK rather than assuming legacy libSQL behavior. S24
SQLite Cloud: offers managed tiers with published storage and connection limits. Validate extension support and distributed transaction semantics. S25
rqlite: provides Raft-backed availability, but consensus serializes writes and adds network/log overhead relative to standalone SQLite. S28
Serverless rule
Ephemeral function/container filesystems are suitable for request-local computation, downloaded read-only snapshots, or caches. They are not a shared authoritative SQLite store because each instance owns a separate disposable file.
18 · Security
Threat model and controls
SQLite security is primarily application and filesystem security. Vanilla SQLite does not provide database users, network authentication, or GRANT/REVOKE.
SQL injection
Bind values. Do not interpolate query text. Constrain identifiers through allowlists or tagged-template helpers.
Malicious database file
Open read-only in an isolated worker, disable trusted schema, enable defensive controls, reduce limits, and inspect integrity.
Native extension
Treat as arbitrary executable code. Pin, checksum, sign, scan, and load only from an allowlisted path.
File theft
Use encrypted disks, SQLCipher/SEE where appropriate, restricted service users, protected backups, and managed keys.
Cross-tenant access
Apply tenant authorization before search. Duplicate scope filters in canonical joins and vector partitions.
Resource exhaustion
Bound SQL length, expression depth, output rows, recursion, vector candidates, query time, and memory.
SQLite’s security guidance recommends trusted-schema controls, defensive mode, authorizers, and reduced runtime limits for hostile SQL or files. Bun’s node:sqlite compatibility is the stronger high-level surface for these controls. S07S17
Extension supply chain
No runtime downloads.
Pin extension and SQLite versions together.
Build/test per OS and CPU architecture.
Verify checksums and signatures before startup.
Include native artifacts in the SBOM.
Disable or avoid extension loading after initialization.
Use a separate process for untrusted extensions.
Logging gotcha
A statement’s expanded SQL may contain bound values. Avoid logging expanded SQL where secrets, personal data, tokens, or document content can appear. Log statement identifiers, plan hashes, normalized query shapes, and redacted parameters instead.
19 · Portability
Runtime, dialect, data, and operational portability
Portability is multi-dimensional. A common API can reduce rewrites while still leaving SQL syntax, extension behavior, data representation, and availability semantics coupled to SQLite.
Layer
Portable?
Control
Repository contract
High
Domain operations rather than generic query strings
Bun.SQL call-site
Moderate
Shared tagged templates and result API
SQL dialect
Low for advanced features
Adapter-specific queries and tests
FTS/vector extensions
Low
Search interfaces and reconstructible indexes
Data types
Moderate
Explicit timestamp, decimal, JSON, and bigint conventions
Transactions/concurrency
Low
Design for the target engine’s semantics
Backup/HA
Low
Provider-specific operational adapter and runbooks
BunSqliteKnowledgeRepository
NodeSqliteKnowledgeRepository
TursoKnowledgeRepository
PostgresKnowledgeRepository
InMemoryKnowledgeRepository # tests only
Use Bun.SQL for conventional CRUD when migration is likely, but keep FTS, vector, PRAGMA, JSON, and graph queries inside engine-specific modules. A unified API does not make the dialect or operational model universal. S15
20 · Extensions
What plugins can add—and what they cost
SQLite’s virtual-table and extension mechanisms can add new indexes, functions, tokenizers, data sources, encryption, and geospatial behavior inside Bun’s process.
Capability
Extension route
Operational cost
Vector retrieval
sqlite-vec, Vec1, SQLite-Vector
Native binaries, index lifecycle, model versioning
import { Database } from "bun:sqlite";
if (process.platform === "darwin") {
Database.setCustomSQLite(
process.env.SQLITE_LIBRARY_PATH ??
"/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib"
);
}
const db = new Database("./data/application.sqlite");
db.loadExtension("./native/approved-extension");
Bun documents that Apple’s default macOS SQLite build does not support loadable extensions. A custom SQLite dynamic library must be selected before opening any connection. S14
JavaScript functions before native code
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("./data/application.sqlite");
db.function(
"normalize_identifier",
{ deterministic: true, directOnly: true },
(value: unknown): string | null => {
if (typeof value !== "string") return null;
return value
.normalize("NFKC")
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "");
},
);
Use JavaScript-defined functions for lightweight deterministic logic. Heavy per-row callbacks block the SQLite statement and the JavaScript thread; implement CPU-intensive virtual tables or tokenizers in native code or a separate worker.
21 · Operations
Backups, recovery, maintenance, and observability
A production SQLite system needs a tested recovery objective, WAL monitoring, integrity checks, planner maintenance, and explicit storage capacity controls.
Safe backup mechanisms
Method
Use
Notes
Online backup API
Consistent live copy
Source remains usable; available through node:sqlite
VACUUM INTO
Compact consistent snapshot
Requires destination space and substantial I/O
Litestream
Continuous replication and point-in-time recovery workflow
Does not create multi-writer HA
Block/filesystem snapshot
Infrastructure backup
Must capture main and transactional sidecars consistently
-- Fast routine validation.
PRAGMA quick_check;
PRAGMA foreign_key_check;
-- More expensive full validation.
PRAGMA integrity_check;
-- Planner statistics maintenance.
PRAGMA optimize;
-- Controlled compact snapshot.
VACUUM INTO 'knowledge-snapshot.sqlite';
The SQLite shell includes diagnostics, backup, recovery, import/export, hashing, and experimental index recommendations. These dot-commands are not SQL and cannot be sent through db.exec(). S36
23 · Gaps
Known Bun and SQLite gotchas
These are the issues most likely to produce subtle production failures or misleading portability assumptions.
Gotcha
Failure
Control
SQLite calls are synchronous
HTTP/MCP event-loop stalls
Read workers, bounded queries, separate maintenance process
Bun.SQL returns Promises
Team assumes SQLite is non-blocking
Document synchronous execution and test tail latency
Default unsafe integer conversion
Silent precision loss
safeIntegers: true and bigint boundary normalization
Non-strict parameter binding
Missing parameter may become NULL
strict: true
Statement cache by SQL text
Dynamic SQL grows cache
Stable templates; prepare() for one-offs
Long iterate()
Old snapshot and WAL growth
Bound iteration and release statements promptly
.as(Class) skips constructors
Uninitialized invariants/private fields
Use for presentation behavior only
macOS system SQLite
Version/feature drift and no extension loading
Inspect runtime; configure custom SQLite before connections
Embedded DB in compiled executable
Writes disappear on exit
Use as seed/snapshot; copy to persistent data location
Relative database path
Different DB based on working directory
Resolve an explicit absolute application-data path
:memory: per connection
Workers see independent databases
Use a file or explicit serialize/deserialize transfer
Native extension mismatch
Load failure, crash, or duplicate SQLite runtime
Pin ABI, OS, architecture, SQLite, and extension together
ORM abstraction
False portability for FTS/vector/PRAGMA
Engine-specific query modules and repository contracts
Bun supports SQLite in standalone executables, but imported file databases resolve relative to the process working directory by default, while an embedded database is an in-memory writable asset whose changes do not persist after process exit. S16
24 · Evolution
Scaling and migration triggers
Move components only when a measured requirement crosses SQLite’s ownership or operational boundary. SQLite can remain a local cache or agent store after the central authority moves.
Shard ownership, single-primary replication, managed provider, or authoritative server DB.
Migration triggers
Multiple active writer hosts
The defining SQLite boundary.
Write lock wait dominates P95
After transaction and batching fixes.
Failover exceeds SLO
Restore or volume attachment is too slow.
Central RBAC/PITR required
Database-service governance becomes mandatory.
One tenant monopolizes writer
Shard or migrate central authority.
Index rebuild exceeds window
Split search/index infrastructure.
Deep graph/ANN dominates
Scale specialized retrieval independently.
Active-active regions
Choose distributed storage with explicit semantics.
Target hybrid architecture
Authoritative shared records → PostgreSQL / distributed SQL
Vector retrieval → specialized vector service if required
Graph analytics → graph engine or offline job
Agent-local memory → SQLite
Edge/read cache → SQLite snapshot or embedded replica
Portable export → SQLite
25 · Blueprints
Reference deployment profiles
Use these profiles as starting architectures. Each preserves a clear owner, a recovery model, and a future migration boundary.
Profile A Local AI agent or developer tool
Architecture
One database per workspace or repository.
bun:sqlite in-process.
FTS5 + exact vector search.
Revisioned memory and provenance.
Periodic VACUUM INTO or file backup while quiescent.
Controls
Absolute application-data path.
Safe integers and strict bindings.
Derived indexes rebuildable.
Schema version on startup.
Export/delete tooling.
Profile B Single-node Bun cloud service
Architecture
One Bun owner service.
Local or block-backed persistent volume.
Read workers and one writer coordinator.
Litestream or online-backup workflow.
Stateless frontends may scale separately.
Controls
WAL and lock metrics.
ReadWriteOncePod if Kubernetes.
Restore drill and explicit RTO/RPO.
No shared NFS/RWX live database.
Maintenance process for index rebuilds.
Profile C Per-tenant cloud databases
Architecture
Shard catalog maps tenant to database owner.
Independent files or provider databases.
Tenant-scoped search and memory.
Asynchronous central analytics export.
Controls
Fleet migration orchestration.
Per-shard size and write quotas.
Backup inventory and tenant relocation.
No cross-tenant transactions.
Profile D Enterprise shared platform
Architecture
PostgreSQL or distributed SQL is authoritative.
SQLite remains local cache, agent memory, snapshot, or offline store.
OpenLineage tracks derived indexes and migrations.
Repository adapters separate stores.
Controls
Cache invalidation and generation IDs.
Central RBAC and audit.
Independent retrieval SLOs.
Explicit consistency contract.
26 · Validation
Red-team and production-readiness checklist
The implementation is credible only when concurrency, recovery, security, portability, and retrieval quality have been tested under realistic failure and scale conditions.
Correctness
Concurrency
Recovery
Security
Scale
AI retrieval
27 · Evidence
Sources, provenance, and methodology
Primary documentation was favored for runtime behavior, engine semantics, provider limits, and standards. The guide distinguishes fact, architectural analysis, and recommendation.
Method
Synthesized the complete conversation research, reconciled duplicated findings, separated engine/runtime/provider layers, and revalidated time-sensitive claims on July 20, 2026.
Evidence hierarchy
Official SQLite and Bun documentation first; official provider and standards documentation second; extension project documentation for extension-specific behavior.
Uncertainty
Cloud prices, quotas, product maturity, bundled SQLite versions, and extension releases can change. Version-sensitive claims are dated and linked.
Interpretation
Recommendations are architectural analysis. They are not claims that a provider or engine prohibits every alternative implementation.