Turso vs Cloudflare D1: Choosing an Edge SQLite Database
Both put SQLite near your users, but they solve replication and write latency very differently. We ran the same schema on both for a month and picked one.
Key takeaways
- Turso is edge SQLite built on libSQL whose embedded replicas serve reads from a local file, while Cloudflare D1 is managed SQLite reached through a Workers binding.
- Pick D1 if you already run on Workers; pick Turso for sub-millisecond local reads, database-per-tenant, or portability.
- As of October 2026 both free tiers include 5 GB of storage.
On this page
Pick Cloudflare D1 if your app already runs on Workers; pick Turso if you want reads served from a local SQLite file, a database per tenant, or an exit from one vendor. In our four-week test, Turso embedded replicas read in under 1ms, D1 read replicas in 8-25ms, and writes cost both about the same.
The architectures diverge on where writes go#
D1 is Cloudflare's managed SQLite. A database has one primary where writes land, and read replication (opt-in through the Sessions API) serves reads from other regions while the primary replicates out asynchronously. Replicas carry no extra charge; you pay the same rows-read and rows-written rates. You talk to D1 through a Worker binding, not a wire protocol, so D1 assumes you live inside the Workers runtime.
export default {
async fetch(request, env) {
const { results } = await env.DB
.prepare("SELECT enabled FROM flags WHERE key = ?")
.bind("checkout_v2")
.all();
return Response.json(results);
},
};
Turso is built on libSQL, an open fork of SQLite. Its model is embedded replicas: your application holds a full local copy of the database on disk, reads hit that file at microsecond latency, and writes go to a remote primary. You connect with a normal client library from any server, container, or function.
import libsql_experimental as libsql
conn = libsql.connect(
"flags.db",
sync_url="libsql://flags-acme.turso.io",
auth_token=TOKEN,
)
conn.sync() # pull latest from primary
rows = conn.execute(
"SELECT enabled FROM flags WHERE key = ?", ("checkout_v2",)
).fetchall()
Once synced, reads never cross the network. For our read-heavy flag service, local reads measured under 1ms, against 8-25ms for D1 replica reads depending on region.
Write latency is where reality bites#
Neither makes writes free. Both have a single write primary, so a write from the far side of the world pays a round trip. Writes to D1 from a Worker in Europe hitting a US primary ran 60-110ms in our tests. Turso writes from an embedded replica were similar, 70-120ms, because the write still travels to the primary.
Consistency is where they differ in detail. Per Turso's docs, the replica that made a write sees it immediately, without calling sync(); every other replica stays stale until it syncs or hits its syncInterval. D1 replicas lag the primary too, and the Sessions API fixes that: withSession("first-primary") sends the first query to the primary, and passing a bookmark between requests keeps a user's reads ordered after their own writes. Both make you think about consistency; neither hides it.
Pricing and the shape of the bill#
Both bill on rows read, rows written, and storage, but the free tiers reset on different clocks: D1 per day, Turso per month.
| Plan (as of October 2026) | Price | Rows read | Rows written | Storage | Databases |
|---|---|---|---|---|---|
| D1 Free | $0 | 5M per day | 100K per day | 5 GB total, 500 MB per DB | 10 |
| D1 on Workers Paid | Workers Paid plan | 25B/month, then $0.001 per M | 50M/month, then $1.00 per M | 5 GB, then $0.75/GB-month; 10 GB per DB | 50,000 |
| Turso Free | $0 | 500M per month | 10M per month | 5 GB | 100 |
| Turso Developer | $4.99/month | 2.5B, then $1 per B | 25M, then $1 per M | 9 GB, then $0.75/GB | Unlimited |
At our volume D1 cost a few dollars a month. Watch rows-read billing on both: an unindexed lookup that scans a big table on every request multiplies "rows read", and we tripped one bill spike exactly that way. Turso's unlimited databases on paid plans make database-per-tenant cheap, which matters for multi-tenant SaaS where each customer wants isolation.
Lock-in and portability#
D1 only runs on Cloudflare. Your data and your access path are both tied to Workers. That's fine if you're all-in on Cloudflare, and painful if you ever want out.
Turso is libSQL, open source, and self-hostable. The embedded replica is a SQLite file you can copy. If you want the same database reachable from a Fly.io box, a Lambda, and a Worker, Turso is the less trapped choice.
Operational differences you notice in month two#
Migrations. D1 has a migrations system wired into Wrangler, so schema changes ride along with your Worker deploy. Turso expects you to bring your own migration tool pointed at a libSQL URL.
Backups. Both offer point-in-time restore; Turso's window is 1 day on Free and 10 days on Developer. A Turso replica is also an ordinary SQLite file, so "copy it and open it locally" is a real debugging option. With D1 that path runs through export tooling.
Observability. D1 surfaces query analytics in the Cloudflare dashboard, which catches the rows-read problem before the bill does. Turso's per-database metrics are thinner.
Local development. Turso wins: an embedded replica already is a local file. D1 local development goes through Wrangler's simulated environment, which is good and is still a simulation.
When neither is the right answer#
Both solve one shape of problem: read-heavy workloads that want low latency in many regions, with modest writes and tolerance for replica lag. If your users sit in one region, a single Postgres instance with a pooler is faster to build and cheaper. If writes are heavy or must be visible everywhere at once, the shared single-primary model becomes the constraint. The broader category, including Postgres options like Neon, is covered in edge databases for low-latency apps.
Questions people ask#
Is Turso SQLite at the edge?#
Yes. Turso runs libSQL, an open-source fork of SQLite, and gets its edge behavior from embedded replicas: each app instance keeps a full local copy of the database and reads it as a file, while writes go to one remote primary. Edge runtimes without a filesystem, such as Cloudflare Workers, can't host an embedded replica and connect to Turso remotely over HTTP instead.
Turso vs D1: which is cheaper?#
For small apps, both are free: D1 allows 5 million rows read and 100,000 rows written per day, Turso 500 million read and 10 million written per month, each with 5 GB of storage as of October 2026. Turso's paid tier starts at $4.99 a month; D1 paid usage rides on the Workers Paid plan. Query shape matters more than list price, since both bill per row scanned.
Can I use Turso from Cloudflare Workers?#
Yes, but not with embedded replicas. Turso's docs recommend the fetch-based @tursodatabase/serverless package for Workers, which queries the remote database on each request. You lose the local-file read path that makes Turso fast elsewhere, so on Workers D1 is usually the better fit unless you need Turso's portability or database-per-tenant model.
The decision, concretely#
- Already building on Cloudflare Workers? Use D1. The binding is the ergonomic win, and read replicas cost nothing extra.
- Read-dominated with a hard latency target, outside a single edge runtime? Use Turso. Embedded replicas take the network out of the read path entirely.
- Multi-tenant SaaS where each customer wants isolation? Turso, with unlimited databases on paid plans.
- New to either? Start with the Turso setup walkthrough, which covers the CLI, credentials, and a local embedded replica.
The call we'd make#
If your stack already lives in Cloudflare Workers, use D1: the binding just works and replication is free. If you're read-dominated and want true local-file reads, database-per-tenant, or a self-hosting exit, use Turso. We shipped the flag service on Turso for the sub-millisecond reads, but we'd have picked D1 without hesitation if we were already a Workers shop.
Sources#
Get the DevOps Troubleshooting Cheat Sheet
Subscribe and get our free one-page reference for the errors that eat an afternoon — CrashLoopBackOff, OOMKilled, Terraform state locks, and more — plus new guides as we publish them.
Cloud IAM Least-Privilege Without Breaking Everything
Least privilege fails when it's a one-time audit that locks things down until something breaks, then gets reverted. The iterative, log-driven approach that tightens permissions safely — and the policies we stopped writing by hand.
Flux vs Argo CD: Picking a GitOps Engine in 2026
After running both in production across a dozen clusters, here's where Flux and Argo CD actually differ and which one we'd reach for now.
More from Cloud
Explore more articles in this category
Azure OpenAI's Sweden Central Outage: A Health Check Postmortem
A slow database made a backend service look dead, and the health check that was supposed to protect it killed it faster. The lesson has nothing to do with AI.
GKE Pod Snapshots: Cold Starts Drop 89%, If Your Nodes Match
Google's benchmarks show a 70B model restoring in 37 seconds instead of minutes. The catch is a hash and a hardware match that silently refuses to restore when either is off.
AWS Lost a Region for Good: Multi-AZ Is Not Disaster Recovery
AWS says it cannot restore data held only in Bahrain (me-south-1) or in one UAE zone. Multi-AZ gave availability, not recovery, and only cross-region copies survived.
You might have missed
Evergreen posts worth revisiting.