Multi-Tenant Database-Per-User SQLite: Unlimited Horizontal Write Scaling on One VPS
SQLiteEdge multi-tenant blueprint: assigning each customer workspace an isolated .sqlite3 WAL file with an LRU handle pool and lazy PRAGMA user_version migrations.
- MODELED DESIGN TARGET
- 100,000+ Aggregate
- READ PATH / RPO TARGET
- Zero Cross-Tenant Leak
- REFERENCE NVME PROFILE
- $48–$96 Multi-Core NVMe
SQLite's "single writer" lock applies per file—not per server! When your B2B SaaS assigns each organization (`tenant_8492.sqlite3`) its own file on a 12-core NVMe VPS, 12 different tenants write simultaneously across all 12 CPU cores with zero lock contention, cryptographic-grade tenant isolation, zero row-level-security bugs, and instant 1-file customer data exports.
In our SQLiteEdge BP-08 reference guide, every configuration rule below follows official SQLite 3.46 documentation and public vendor specifications retrieved 2026-11-12.
SQLiteEdge Architecture Rules for Database-Per-Tenant Multi-File SQLite Sharding
- Manage open SQLite handles through an in-memory LRU Connection Pool (e.g., max 512 open tenant databases) that cleanly runs `PRAGMA wal_checkpoint(TRUNCATE)` and closes idle handles after 5 minutes.
- Cap per-tenant `cache_size = -16384` (16 MiB) and rely on the Linux kernel Page Cache to dynamically prioritize hot active workspaces across 32 GB – 64 GB RAM.
- Run schema migrations lazily on tenant database open (`PRAGMA user_version` check)—active tenants migrate in <5ms on first request without multi-hour global table locks!
- Back up cold/idle tenant files via incremental sha256 + ZSTD snapshots to a $6 InterServer 1TB vault, streaming only dirty `.sqlite3` files modified in the last 5 minutes.
SQLiteEdge Reference Snippet: LRU Tenant Handle Manager + Lazy user_version Migrator
const MAX_OPEN_TENANTS = 512;
const pool = new Map(); // tenantId -> { db, lastUsed }
export function getTenantDb(tenantId) {
const safeId = tenantId.replace(/[^a-zA-Z0-9_-]/g, '');
if (pool.has(safeId)) {
const entry = pool.get(safeId);
entry.lastUsed = Date.now();
pool.delete(safeId);
pool.set(safeId, entry); // Refresh LRU tail
return entry.db;
}
if (pool.size >= MAX_OPEN_TENANTS) {
const [oldestKey, oldestVal] = pool.entries().next().value;
oldestVal.db.pragma('wal_checkpoint(TRUNCATE)');
oldestVal.db.close();
pool.delete(oldestKey);
}
const db = new Database(`/var/lib/tenants/${safeId}.sqlite3`);
db.pragma('journal_mode = WAL');
db.pragma('synchronous = NORMAL');
db.pragma('cache_size = -16384'); // 16 MiB per active tenant
runLazyMigrations(db); // Instant check via PRAGMA user_version
pool.set(safeId, { db, lastUsed: Date.now() });
return db;
}
SQLiteEdge Modeled Profile & Tariff Table (BP-08)
Public vendor prices retrieved 2026-11-12 (promotional and renewal rates may change at checkout); throughput figures reflect theoretical single-queue sizing models rather than unarchived lab claims.
| Architecture Pattern | Aggregate Server Write TPS | Tenant Data Export / Wipe | Noisy-Neighbor Blast Radius |
|---|---|---|---|
| Shared Multi-Tenant Postgres (RLS) | ~9,000 TPS (Shared WAL) | Slow DELETE WHERE tenant_id=... | 1 runaway query slows all tenants |
| Single Giant Monolithic SQLite File | ~22,000 TPS (1 Core Lock) | Slow filtered table dump | Heavy writer queues other writers |
| DB-Per-Tenant SQLite on 8–12 vCPU NVMe | 95,000+ TPS (All Cores) | Instant cp / rm tenant_id.db! | Zero—isolated B-tree & lock per org |
SQLiteEdge Spec-Matched NVMe & S3 Targets for BP-08
-
Vultr 3.5GHz+ High Frequency Local NVMe ComputeInspect Vultr Specs for BP-08 via SQLiteEdge →
3.7GHz+ Single-Core IPC + Direct PCIe NVMe • ~0.045ms fsync for Peak SQLite Single-Writer TPS (https://www.vultr.com/pricing/) -
InterServer $6 Price-Lock S3 / MinIO Litestream Backup SliceInspect InterServer S3 Slice for BP-08 via SQLiteEdge →
$6/mo Lifetime Price-Lock 1TB Slice • $0 S3 PUT API Fees for 1-Second Litestream WAL Streaming (https://www.interserver.net/vps/)
Open this BP-08 workload preset inside the SQLiteEdge PRAGMA Studio →
SQLiteEdge Engineering FAQ for BP-08
-
How many `.sqlite3` files can a single Linux ext4 NVMe filesystem hold comfortably?
ext4 easily handles 100,000 to 1,000,000+ files when sharded into 2-char hex subdirectories (`/var/lib/tenants/a4/tenant_a49f.sqlite3`) so no single directory exceeds a few thousand entries.
-
Why is lazy migration via PRAGMA user_version a superpower for B2B SaaS?
In shared Postgres, an `ALTER TABLE` on a 400M-row table risks locking every paying customer at once. With DB-per-tenant SQLite, each 50 MB workspace migrates atomically in ~3ms the moment that team logs in.