Can a fleet of WAL-mode SQLite files, one per customer, replicated through S3-compatible object storage, carry a multi-region application? The question decomposes into four smaller ones: whether each tenant’s write volume fits under SQLite’s single-writer constraint, whether the tenant count fits under operating-system file-handle limits, whether reads must come from local disk or may cross a network and whether the team can absorb the roadmap risk of the replication tool it selects. This survey lays out the evidence on each axis for four approaches (Litestream, LiteFS, libSQL/Turso and, as the managed comparison point, Cloudflare D1) and takes no position on a winner.
One disclosure governs everything that follows. Every finding this piece draws on emerged from adversarial fact-checking with a weakened verdict: each claim survived falsification only after specific dates, quantifiers or scope were corrected. No claim below is settled. Each appears together with the qualification that checking produced, and where a disconfirming or qualifying source exists it is cited next to the claim it adjusts.
Problem / Context: What a Multi-Region, Database-per-Customer Bet Requires
Write-ahead logging (WAL) is the SQLite journaling mode in which committed changes append to a separate log file while readers continue against the main database. The design permits any number of concurrent readers alongside exactly one writer per database file; readers do not block the writer and the writer does not block readers, per the official WAL documentation. That single-writer rule is the constraint every replication approach in this survey inherits, because none of them alters SQLite’s locking model. They move bytes; they do not change semantics.
Database-per-customer is the topology that makes the constraint tolerable. Turso markets the arrangement as database per tenant, and the checked finding behind this section describes it as an emerging third pattern in 2025-2026 multi-tenant design, positioned between shared-schema with row-level security (RLS), which remains the 2026 default, and a dedicated Postgres instance per tenant. Emerging is the load-bearing word. The adversarial pass surfaced active skepticism, including an argument that most teams do not need a database per tenant and a field report on the operational weight of running thousands of tenant shards. The pattern is recognized; it is not dominant.
Four decision drivers organize everything else in this piece, and the comparison table later maps every option against them:
- Per-tenant write volume. One writer per file means one tenant’s peak write concurrency must fit through one serialized write path. The topology confines the limit to each tenant’s own file; it does not remove it.
- Tenant count. SQLite imposes no cap of its own on how many database files one server process may hold open; the widely quoted hard limit of 125 applies only to
ATTACH, the mechanism for querying multiple databases through a single connection, per a discussion on the SQLite user forum. The practical ceiling is the operating system’s per-process open-file limit, which a fleet of thousands of tenants will reach without deliberate tuning. Checking weakened this finding only on its vendor-pricing periphery, not on the file-descriptor core. - Read topology. The four approaches split cleanly between serving reads from a genuine local disk file and serving reads over a network, and the latency difference spans several orders of magnitude.
- Operational appetite. Two of the four options changed roadmap direction between 2024 and 2026. A team adopting any of them is adopting a trajectory, not a snapshot.
The figure below places all four approaches on a single path from a tenant’s write to a remote read, so the option descriptions that follow have a shared map to attach to. One term appears in the figure before its option does: LTX is the transaction-file format that Litestream and LiteFS both use to package committed changes for shipment, keyed by transaction identifier.
flowchart TB
W["Tenant write"] --> WAL["Local WAL-mode SQLite file (self-hosted primary)"]
W --> SYNC["libSQL sync client"]
W --> WK["Cloudflare Worker"]
WAL --> LS["Litestream process: checkpoint takeover"]
LS --> S3["S3-compatible bucket (LTX files)"]
S3 --> VFS["Remote reader via Litestream VFS (network page reads)"]
WAL --> LF["LiteFS FUSE layer: commit interception"]
LF --> LFR["Replica nodes over HTTP (full local on-disk copies)"]
SYNC --> TP["Turso remote primary"]
TP --> EMB["Embedded replica (real local SQLite file)"]
WK --> D1P["D1 primary (managed storage layer)"]
D1P --> D1R["D1 global read replicas (WAL shipping)"]
Options Considered: Four Ways to Get WAL Pages Out of One SQLite File
Litestream is a separate background process that requires no application changes. Per its own architecture documentation, it takes over SQLite’s checkpointing by holding a long-running read transaction, watches the WAL and ships changed pages to object storage on a default one-second interval set in the configuration file. Since the 2025 v0.5 release those pages travel as LTX files keyed by transaction identifier, with tiered compaction consolidating them over time. Two qualifications from the adversarial pass belong in this description rather than a footnote. First, Litestream is routinely summarized as S3-only; its configuration reference lists eight replica backend types, including Google Cloud Storage, Azure Blob Storage, SFTP and NATS. Second, Litestream historically offered no live reads at all (a replica was something to restore, not something to query); the v0.5 virtual file system (VFS) read replica changes that by letting a reader pull pages from object storage over the network, with latency consequences the Trade-offs section quantifies.
LiteFS, from Fly.io, takes the interception one layer down. It is a FUSE (Filesystem in Userspace) pass-through filesystem that observes SQLite’s transaction boundaries at the file layer, packages each transaction as an LTX file and distributes those files over HTTP to replica nodes, each of which maintains a complete local on-disk copy of the database, per the launch announcement. Primary election uses a Consul time-based lease rather than a Raft-style consensus protocol, a deliberate simplification suited to ephemeral cloud instances. The qualification that most changes the picture: Fly.io’s how-it-works documentation describes replication as asynchronous, so a commit acknowledged on the primary has not necessarily reached any replica, and a failover inside that window can lose acknowledged writes. Descriptions of LiteFS as the strongest-consistency option in this field overstate what the documentation supports.
libSQL/Turso approaches the problem from the client side. libSQL is Turso’s open-contribution fork of SQLite, and its embedded replica is a real local SQLite file (not a cache and not a network VFS) kept in sync with a remote primary, per Turso’s embedded replica documentation. Reads come from local disk; writes are forwarded to the remote primary; the writing process observes its own writes immediately; the background sync interval is configurable through the sync documentation. Turso’s published benchmark reports local replica read latency below 200 nanoseconds. The figure is vendor-published and unaudited, so it is best read as an order-of-magnitude signal (memory-speed local reads) rather than a precise number. This mechanical description was the strongest survivor of the adversarial pass; the qualification that matters attaches to the platform around it and appears under Trade-offs.
Cloudflare D1 is the managed comparison point. D1 runs SQLite databases inside Cloudflare’s infrastructure, accessible from Workers; there is no standalone wire protocol, so access from outside the Workers runtime requires a Worker proxy, and each database carries a 10 GB ceiling. D1 reached general availability in April 2024. Global read replication entered public beta in April 2025, shipping WAL to replicas in multiple regions at no additional per-region cost, with a Sessions API that provides sequential consistency through a Lamport-timestamp-style bookmark token, per Cloudflare’s engineering write-up. Hardening has continued since, including automatic read retries in September 2025 per the release notes. Checking corroborated four of the five claims here across independent sources; the accurate framing is a maturing beta under active repair, not a finished system.
For completeness, the market’s non-SQLite answers to the same problem are multi-tenant Postgres in its several forms and sharded MySQL through Vitess. One qualification from the landscape check belongs here: an earlier analysis of these alternatives materially omitted that Turso already ships, as a product, the exact combination this survey evaluates (database-per-tenant SQLite with embedded replicas). The pattern under discussion is in production, not hypothetical.
The landscape finding describes the three self-hosted patterns as occupying distinct, non-overlapping positions on a plane of operational cost against read latency, rather than forming a best-to-worst ranking. The chart below draws that claim, and it carries the same weakened verdict as every other figure in this piece.
quadrantChart
title Operational burden versus read latency
x-axis Local-disk reads --> Network reads
y-axis Lower operational burden --> Higher operational burden
quadrant-1 Costly and remote
quadrant-2 Costly but local
quadrant-3 Cheap and local
quadrant-4 Cheap but remote
Litestream VFS: [0.78, 0.22]
LiteFS: [0.2, 0.8]
libSQL Turso embedded: [0.15, 0.4]
Trade-offs: Comparing the Options Against the Decision Drivers
The table below maps the four options against the four decision drivers from the Problem section. Every cell reflects the finding’s qualified verdict, not the vendor’s framing; the paragraphs after the table supply the citations and dates for the roadmap row, which is the row most likely to be stale by the time anyone acts on it.
| Decision driver | Litestream | LiteFS | libSQL/Turso | Cloudflare D1 |
|---|---|---|---|---|
| Single-writer fit | One local process writes; Litestream adds no second writer, but its checkpoint takeover holds a read transaction on the write path. | One elected primary per cluster (Consul lease); replicas are read-only. | One remote primary per database; embedded replicas forward writes to it. | Managed single primary per database; all writes route to it. |
| Read latency | VFS replicas read over the network: 5-50 ms per read even same-region; the docs scope the feature to moderate query loads. | Local-disk reads from a full on-disk copy on every replica. | Local-disk reads from a real local file; vendor benchmark reports sub-200 ns. | Reads served from a replica near the user, inside Workers only. |
| Replica lag | Default 1 s sync interval, plus eventual consistency once S3 cross-region replication is layered on (no default time guarantee). | Asynchronous; a commit acknowledged on the primary may not have reached any replica. | Configurable sync interval; the writing replica reads its own writes immediately. | Asynchronous replication with per-session sequential consistency via a bookmark token. |
| Roadmap and maturity | Active reinvestment: v0.5 (2025) brought the LTX format and VFS replicas; v0.5.1 followed. | Maintenance mode per Fly.io staff; LiteFS Cloud sunset October 2024; README still says actively maintained, beta. | Mid-pivot: edge replicas discontinued for new users; Rust rewrite in beta with a proprietary server component. | GA April 2024; read replication beta April 2025; hardening through 2025-2026. |
Roadmap risk deserves dates rather than adjectives, because each of these cells cites a moving target.
LiteFS sits at one extreme. Fly.io staff confirmed in a community thread that the project receives limited updates, has no significant work planned and carries no formal support; LiteFS Cloud, the managed offering, was sunset in October 2024 as engineering attention moved to Fly Managed Postgres. The GitHub README still describes the project as actively maintained and in beta. The two statements conflict. A deployer should weight the direct staff statement over the README while recording that the contradiction exists, because it means the project’s public surface does not reflect its actual investment level.
Litestream moved in the opposite direction over the same period. The 2025 revamp announcement described the v0.5 rework (LTX format, VFS read replicas) as a deliberate reinvestment that widens Litestream’s role beyond backup and disaster recovery, and v0.5.1 shipped shortly after. Simon Willison’s independent analysis of v0.5.0 corroborates the scope of the change from outside the vendor. The adversarial pass found one mischaracterization in the periphery of this finding, so the safe reading is the narrow one: the release activity is real and recent; broader conclusions about where Litestream ends up should wait.
Turso is mid-transition. The company’s roadmap post announces the discontinuation of geographic edge replicas for new users, citing that 70 percent of its users never create one, and refocuses the company on a from-scratch Rust rewrite of SQLite (tursodatabase/turso) that pairs an open database engine with a proprietary server component and remains in beta. The embedded-replica mechanics described earlier survived checking; the platform carrying them is changing shape underneath.
The replica-lag row hides one more correction worth stating plainly. Amazon S3 (Amazon Simple Storage Service) is strongly consistent for reads after writes within a single region and bucket; that guarantee does not extend across regions, and the Implementation Notes section carries the corrected numbers, because a widely repeated sub-second figure for cross-region replication lag did not survive checking.
What the single-writer constraint means inside this topology is easier drawn than described. The per-file limit becomes a per-tenant isolation boundary: each tenant’s file has its own single writer and its own replicas, and no tenant’s write load queues behind another’s.
flowchart TB
subgraph TA["Tenant A"]
WA["Single writer for tenant-a.db"] --> FA["tenant-a.db (WAL mode)"]
FA --> RA1["Local reader"]
FA --> RA2["Local reader"]
end
subgraph TB2["Tenant B"]
WB["Single writer for tenant-b.db"] --> FB["tenant-b.db (WAL mode)"]
FB --> RB1["Local reader"]
end
FA --> REPA["Replication layer"] --> RRA["Read replica: local copy of tenant-a.db"]
FB --> REPB["Replication layer"] --> RRB["Read replica: local copy of tenant-b.db"]
Decision: Which Evidence Fits Which Deployment Context
This section makes no recommendation, by design. The evidence base is uniformly qualified; the options occupy different points on the cost-latency plane rather than a ranking; the deciding variables (write volume, tenant count, read topology, operational appetite) belong to the deployer, not the survey. What the evidence does support is a mapping from context to the findings that bear on it.
Backup and disaster recovery first, reads served from the primary region. The Litestream evidence fits this context and, by its own documentation, only this context for reads: the VFS replica docs scope the feature to moderate query loads and 5-50 ms per-read latency, which excludes low-latency read serving. A team whose multi-region requirement is durability rather than read locality finds the strongest and least-encumbered evidence here.
Low-latency reads in every region, self-hosted, with tolerance for a deprioritized dependency. The LiteFS evidence supports the read topology (full local copies on every replica) and undercuts the dependency: asynchronous replication with possible lost writes on failover, plus a maintenance-mode status confirmed by staff. Both halves are part of the same record; a deployer takes them together or not at all.
Low-latency reads with a managed primary and acceptance of vendor coupling. The embedded-replica evidence is the most mechanically verified in this survey, and the strongest production precedent in the entire corpus sits here: Turso reports that Adaptive’s AI builder platform crossed 2 million databases, one per generated application version. That figure is a single vendor-and-customer self-reported number with no independent audit; Adaptive itself is independently confirmed as a real, seed-funded company, which supports the existence of the deployment without verifying its scale. Turso has also shipped operational improvements aimed specifically at database-per-tenant fleets. Read this as one data point about one context: massive counts of small, mostly idle databases under one vendor’s management.
Already on Cloudflare Workers, tenants comfortably under 10 GB. The D1 evidence fits and only fits this context, because the platform constraints are structural: Workers-only access and a fixed per-database ceiling. Within it, the replication design provides the only managed sequential-consistency contract in this survey, at the cost of adopting a beta feature still being hardened.
High per-tenant write concurrency. No option in this survey changes the single-writer arithmetic. BEGIN CONCURRENT, the SQLite feature that would permit limited concurrent writers, exists on a development branch, not in trunk SQLite, so the evidence supports planning around one serialized write path per tenant for the foreseeable future. A tenant whose write load exceeds that path has outgrown this pattern regardless of replication approach.
Implementation Notes: Dependencies, Migration and Operational Risk
Each option carries named dependencies a deployer inherits on day one. Litestream requires an object storage bucket (S3-compatible or one of the other seven backends) and a supervised background process per host. LiteFS requires the FUSE kernel module and, for automatic primary election, a Consul endpoint, per the FAQ; both are infrastructure commitments beyond the application. libSQL/Turso requires adopting the libSQL client libraries and a Turso-managed (or self-hosted libSQL) primary, plus a deliberate sync-interval choice per database. D1 requires the Workers runtime as the sole access path.
A staged migration reduces the number of qualified claims a team has to trust at once. The sequence the evidence supports: first, enable WAL mode and run Litestream in pure backup mode against a single region, because that configuration exercises the smallest verified surface (checkpoint takeover plus one-second shipping); second, rehearse restore per tenant, since restore is the half of backup that pages people; third, introduce read replicas only after measuring actual replica lag under production write load, whichever approach provides them; fourth, introduce a second region only with monitoring already in place for the lag windows described below.
Three operational concerns from the findings deserve explicit planning rather than discovery in production.
Checkpoint contention sits on the write path. Litestream’s takeover of checkpointing works by holding a long-running read transaction that prevents other processes from checkpointing the WAL. The mechanism is load-bearing and verified; its consequence is that WAL growth and checkpoint timing become Litestream’s responsibility, and a deployer should monitor WAL file size per tenant rather than assume SQLite’s default checkpoint behavior still applies.
File-descriptor limits arrive before SQLite limits. With no SQLite-imposed cap on open databases and the 125 limit applying only to ATTACH, the binding constraint for a thousands-of-tenants fleet is the per-process open-file limit, tuned through ulimit or the service manager’s equivalent. Pooling and lazily closing idle tenant databases is an application-level obligation, not something any of the four options provides. On the managed side, note that per-database pricing has shifted repeatedly; Turso’s pricing changed model again effective July 2026, so cost math from any dated blog post needs re-verification.
S3’s strong consistency stops at the region boundary. Amazon documents strong read-after-write consistency automatically within a single region. Cross-Region Replication (CRR) of those objects is eventually consistent, and the adversarial pass falsified the widely repeated claim of typical sub-second CRR lag; the sub-second figure in the AWS architecture guidance belongs to a different service. The strongest guarantee available is S3 Replication Time Control, a paid feature with a service-level agreement of 15 minutes for 99.9 percent of objects. A multi-region design that assumes sub-second object replication is designing against a number that does not exist; monitor observed lag and design read paths for staleness measured in minutes at the tail.
The sequence below walks one write through the full path and marks the windows a deployer has to instrument.
sequenceDiagram
participant App as Tenant application
participant DB as Primary SQLite (WAL)
participant LS as Litestream
participant S3A as S3 region A
participant S3B as S3 region B
participant RD as Remote VFS reader
App->>DB: COMMIT (acknowledged locally)
Note over DB,LS: Window 1: sync interval (default 1 s)
LS->>S3A: ship LTX pages
Note over S3A,S3B: Window 2: CRR is eventually consistent, no default time bound (RTC SLA is 15 min at 99.9 percent)
S3A-->>S3B: replicate objects
RD->>S3B: page reads over network (5-50 ms each)
Note over RD: Reader may observe a stale transaction until both windows close
Consequences: What Gets Easier, What Gets Harder, What to Revisit
The architecture makes three things structurally easier. Tenant isolation stops being a query-discipline problem and becomes a filesystem fact; one tenant’s data lives in one file, and the blast radius of a corrupted database, a bad migration or an oversized tenant is that tenant. Per-tenant operations (backup, restore, export, deletion for a compliance request) become file operations. The shared-schema migration, the single change that must succeed simultaneously for every customer, disappears; each tenant’s schema migrates on its own schedule, which converts one high-stakes event into many low-stakes ones.
Three things get harder in exchange. Cross-tenant queries lose their natural home: with ATTACH capped at 125 databases per connection, fleet-wide analytics requires an external pipeline rather than a JOIN. Fleet-wide operational tooling (the migration runner, the backup verifier, the lag monitor) must be built to iterate over thousands of databases, and the field report from a large deployment documents that this tooling, not the databases, is where the operational weight accumulates. Finally, the architecture couples the deployer to a replication project’s trajectory, and this survey’s own record shows those trajectories reversing inside two years in both directions.
That record defines the revisit list. Revisit LiteFS if its staff-confirmed maintenance-mode status changes or the README contradiction resolves in either direction. Revisit Litestream’s role at each release, because the v0.5 line is actively absorbing read-replica capability that previously required LiteFS, and the boundary between the two projects is still moving. Revisit Turso when the Rust rewrite leaves beta, because the platform’s edge-replica discontinuation and proprietary server component change both the technical and the lock-in calculus mid-adoption. Revisit D1’s limits page before assuming the 10 GB ceiling still binds.
The question this survey opened with has a qualified answer rather than a verdict. The evidence supports the pattern’s mechanics: WAL shipping works; local-disk replicas are real; one production deployment claims seven figures of databases; the single-writer constraint shrinks to per-tenant scope in this topology. The same evidence base carries a qualification on every load-bearing claim, from misquoted lag figures to conflicting maintenance statements to vendor-reported benchmarks with no audit. A deployer who proceeds should proceed the way this survey was assembled: claim by claim, with the disconfirming source open in the next tab, and with dates checked against the calendar rather than the blog post.