database · tutorial · sqlite
0.1ms vs 35ms: The Latency Math That Picks SQLite
Three cloud queries at 35ms each is 105ms of pure wait. Local SQLite answers in 0.3ms total. The math plus when it flips. Inside.

The page waited 100ms before thinking
Timing added to a page: three queries (user, orders, settings), then render. Total server time: 118ms.
Query logic: 4ms. The other 114ms was network — three round trips from serverless functions to a managed database in another availability zone, each 35ms of pure waiting for electrons.
Same page on SQLite: 3.4ms total. Not optimized, not tuned — just local. The database was 30x faster because it was 30 meters (same NVMe) instead of 30 miles away.
Sqlite latency truth lands as a feeling first: pages that snap instead of load. Then as math: every cloud query pays a latency tax no index can deduct.
Latency budgets per page
| Page (3 queries) | SQLite local | Same-region cloud | Cross-region cloud |
|---|---|---|---|
| Per query | ~0.1ms | ~5–15ms | ~35–100ms |
| 3 queries total | ~0.3ms | ~15–45ms | ~105–300ms |
| Plus connection | $0 (file open) | Pool wait 0–50ms | Pool + TLS + travel |
| User feels | Instant | Fine-ish | Sluggish |
| At 10 queries deep | ~1ms | ~100ms+ | Seconds (death) |
Three details make network latency dominate everything else:
- Indexes can't deduct travel. A perfect 0.01ms index lookup still rides 35ms of network each way — optimization has a floor set by physics, and the floor is 350x the query. Teams tune queries for weeks to save milliseconds while milliseconds-times-hundred sit in transit, untouchable by EXPLAIN.
- Serverless multiplies hops. Edge → function → pooler → database → back, each leg adding its slice: cold starts, TLS handshakes, pool acquisition. My 114ms wasn't three queries; it was three journeys with layovers. Local SQLite has no journey — the data lives where the code runs.
- Deep pages die by multiplication. Dashboards with ten queries (user, stats, lists, counts, prefs...) turn 35ms legs into 350ms+ waterfalls — before rendering, before JS, before paint. N+1 is a latency multiplier, and multipliers need small bases: 0.1ms × 10 is 1ms (fine), 35ms × 10 is a loading spinner (churn).
The SQLite production guide tuned the pragmas. This one prices the distance.
Sqlite latency truth applied
Measure legs, colocate data, know the flip triggers — latency as architecture, not accident.
Step 1 — Time your legs, not just your queries.
# instrument one hot route (5 min, reveals everything):
console.time("q-user"); await getUser(id); console.timeEnd("q-user");
console.time("q-orders"); await listOrders(id); console.timeEnd("q-orders");
# compare: sum(query logic) vs total route time — the gap is pure travelMost teams discover travel dominates 10-to-1 over logic on cloud setups — the optimization roadmap inverts instantly (colocate first, index second). Measure in prod-like topology (serverless-to-managed, not laptop-to-localhost); local measurements lie about deployed legs. Numbers first, opinions retired.
Step 2 — Colocate data with compute (SQLite on the box).
Choose SQLite as the database and keep its file on the same disk as the Node process. BoringPush applies the production settings for you: write-ahead logging, a busy timeout and sensible memory settings. Result: the network legs collapse to local reads, so a page with a few queries stops waiting on the network at all.
Colocation deletes the journey instead of optimizing it — no poolers, no TLS-per-query, no AZ roulette. WAL mode keeps readers unblocked during writes; busy_timeout absorbs contention spikes gracefully.
The entire "database performance" discipline compresses to one file on fast disk plus seven pragmas. Distance zero beats tuning heroics, every page load, forever.
Step 3 — Monitor the two flip triggers honestly.
There are two triggers that end the SQLite era. First, write contention, with lock waits climbing past about 100ms as writers queue.
Second, geography, with users spanning continents who need local reads from one file in one place. If either fires with a trend, switch the database setting, migrate the rows and keep the app code.
Latency honesty cuts both ways: SQLite wins single-box reads by orders of magnitude AND loses multi-region reads equally decisively. Monitor lock-waits and latency percentiles (alert on thresholds you choose), flip on trend not spike. The tri-DB parity exists precisely so triggers fire as plans, not panics.
Simple as that — data at arm's length, always.
Page timings, both homes
| Hot route (user+orders+settings) | Managed cloud | SQLite local |
|---|---|---|
| Query logic sum | 4ms | 0.3ms |
| Network travel sum | 114ms | ~0ms |
| Route total | 118ms | 3.4ms |
| Feel | Loads | Snaps |
| Monthly cost of speed | $25–50 + meters | $0 (in the $5 box) |
BoringPush pages read local before reaching remote.
To be straight about it: local speed concentrates risk (one box, one file — snapshots and off-box copies mandatory) and caps geography (distant users feel distance again, just HTTP-shaped). Priced against 115ms per page-load forever: backups are cheap, latency is forever. Fast-and-backed-up beats distributed-and-sluggish for every workload the triggers don't cover — which, at solo scale, is all of them.
FAQ
Is sqlite latency truth just benchmarks, or does users feel 100ms?
Users feel it cumulatively: 100ms per navigation × dozens daily = perceptible sluggishness, higher bounce, lower trust — especially on mobile networks where device latency stacks atop server latency. Sub-10ms server times make pages feel instant; 100ms+ makes them feel loaded.
Feelings compound into metrics: bounce, conversion, retention. Milliseconds are UX wearing infrastructure clothes.
What about connection pooling — doesn't it fix cloud latency?
Pooling fixes acquisition (no per-query handshake), never travel (bytes still cross zones). Pooled cloud reads run ~5–35ms versus SQLite's 0.1ms — better, still 50–350x slower.
Pool always (free performance), but recognize the floor: pooled travel is still travel. Poolers optimize the waiting room; colocation removes the trip.
Do read replicas solve the geography trigger?
They mitigate (regional reads, local legs) at real cost (replication lag, split-brain reads, operational surface, vendor bills). Replica topology is genuinely complex — lag-induced stale reads create bug classes single-box SQLite never has.
For global scale it's correct and worth it; for continental scale, one central box plus edge caching covers most. Replicas on evidence, never aspiration.
How does caching change the latency math?
Caching (edge, CDN, memoization) helps both homes equally — cached SQLite responses and cached cloud responses both answer in ~1ms from edge. Caching compresses repeated reads; latency truth governs uncached ones (personalized, fresh, user-specific).
Cache aggressively AND colocate: layered speed beats either alone. Uncached paths reveal the engine; make sure yours is local.
Can I measure legs without code changes (APM)?
APM waterfall views show legs directly (database spans vs total trace) — useful for inherited codebases where instrumentation feels invasive. But five lines of console.time teach more (you choose what to compare) and cost nothing.
APM for teams with budgets, console.time for everyone always. Measure somehow; unmeasured latency is just vibes with dashboards.
When does SQLite lose on writes (not just geography)?
Sustained concurrent writes past single-writer comfort (lock-waits climbing, busy_timeout firing routinely): high-frequency event ingestion, multi-writer counters, chatty real-time state. Reads scale beautifully (WAL + mmap); writes serialize honestly.
Watch lock-wait p99 (telemetry screams past thresholds); flip on trend. Writes are SQLite's honest ceiling — respect it early, exit on schedule.
Keep reading
- Next.js SQLite SaaS Boilerplate (Prod WAL Guide) — the setup this math justifies
- SQLite in Prod Is Fine. You're Just Missing 3 Settings — operating the fast engine
- SQLite vs Mongo vs Supabase: 2 Triggers End the Debate — the flip triggers, expanded
+ all the boring stuff (analytics, auth, SQLite ↔ Supabase, full marketplace, webhooks)
Command from your phone.
Built so autonomous AI agents build what you want without breaking what's already paying you.
- Built-in analytics — cookie-free mode, $0 forever, no PostHog bill
- SQLite ↔ Supabase: one setting, same field names
- Full digital marketplace & feedback boards included
- 3 payment rails: Stripe, LemonSqueezy & Dodo Payments
- Agent fences: lint gate, read-only data access, auto-rollback
- Ship from your phone via SSH & WireGuard/Tailscale in under a minute
$199
Get BoringPushOne-time payment · Lifetime updates · Unlimited projects.
Keep reading


