all posts

The best Turso alternatives in 2026

Ajay Kumar··9 min read

Turso took SQLite, made it a hosted service with libSQL, and pushed two ideas hard: replicas close to your users so reads are fast, and databases cheap enough that you can hand one to every tenant. Both ideas are good. They are also completely separate problems, and most of the confusion in this comparison comes from treating them as one product decision.

So the useful question is not which Turso alternative is best. It is which of the two things you came for, because the answer differs entirely.

Fair warning: I build PandaStack, which shows up below in the database-per-tenant half. It is not an edge-replica product and I will not pretend otherwise.

Which problem are you solving?

  • Read latency. Your users are spread across continents and a round trip to a single region is showing up in your p95. You want data physically near them, and you can tolerate replication lag on writes.
  • Tenant isolation and cost. You have thousands of small tenants and want each one's data in its own database — for isolation, for clean deletes, for per-tenant restore — without paying per-tenant server prices.
  • Local-first or embedded. You want the database in the application process, or on the user's device, with sync. This is a genuinely different architecture and very few products serve it.
If you cannot say which of the three you are optimising for, you are probably optimising for none of them, and a single well-placed Postgres will beat every option in this post on total effort. That is not a joke — it is the most common right answer.

If you came for edge reads

  • Cloudflare D1 — SQLite as a first-class Workers primitive, with read replication and the tightest integration if your compute already lives on Workers. The constraint is that your compute has to live on Workers for the story to work.
  • PlanetScale — MySQL rather than SQLite, with a mature global story, connection pooling that survives serverless, and genuinely good schema-change tooling. Choose it if you want a relational database that scales horizontally and you are not attached to Postgres.
  • Postgres with read replicas — the unglamorous answer. Any managed Postgres provider will give you a replica in another region. Point your read queries at it and your writes at the primary. Less magic, no new query language, and your ORM already knows how.
  • A cache in front of one database — before you distribute the database, distribute the answers. A CDN or a regional cache in front of a single-region Postgres solves a startling proportion of edge-latency problems for a fraction of the complexity, particularly for read-heavy content.

The honest note on edge databases: they trade write latency and consistency for read latency, and applications that were not designed for that trade discover it in the form of a user who updates a setting and sees the old value. Know which of your reads can be stale before you distribute anything.

If you came for a database per tenant

This is the stronger use case for SQLite-shaped products, and the alternatives are more interesting.

  • Postgres with schemas or row-level security — the default and, for most SaaS, correct. One database, one schema per tenant or a tenant_id column with row-level security enforced in the database rather than in your ORM. Cheap, operationally simple, and it fails in a specific way: a bad query or a runaway tenant affects everyone, and per-tenant restore is awkward.
  • Neon — one Postgres project per tenant, with compute that suspends when the tenant is idle. Closest thing to Turso's economics while remaining plain Postgres. Watch the per-project overheads at very large tenant counts.
  • SQLite files you host yourself — on a machine with a persistent disk, one file per tenant, backed up with Litestream or similar to object storage. Extremely cheap, extremely simple, and entirely your problem when the machine dies. Genuinely a good answer for the right team.
  • PlanetScale or Vitess-style sharding — for when tenants share infrastructure but you need horizontal scale rather than isolation. A different shape of answer to the same growth problem.
  • PandaStack — one Postgres 16 in its own Firecracker microVM per database, with a durable volume, which is a much harder isolation boundary than a schema or a row filter: a tenant gets its own kernel, its own memory, its own disk. Idle databases suspend and wake on connect, so a tenant nobody is using is close to free, and each one can be cloned or restored to a point in time independently. The trade is that a VM per tenant does not go to a hundred thousand tenants — this is the right shape at tens or hundreds of meaningful tenants, not at the scale where SQLite files win.

The number that decides it: how many tenants, and how big?

Rough shape of what works where. Your mileage will vary, but the
orders of magnitude hold:

  100,000 tiny tenants, KBs each
      -> SQLite files or a per-tenant embedded database.
         A VM or a Postgres cluster per tenant is absurd here.

  1,000 small tenants, MBs each
      -> One Postgres, schema or RLS per tenant. Boring and correct.
         Reach for per-tenant databases only if isolation is a
         contractual requirement rather than a preference.

  50 substantial tenants, GBs each, some noisy
      -> A real database per tenant. Now isolation buys you something:
         one tenant's terrible query cannot take down the others, and
         "restore just this customer to yesterday" is a normal operation.

  5 enterprise tenants with compliance requirements
      -> A database per tenant, probably per region, and the isolation
         boundary is going in the contract. Price stops being the axis.

Most teams asking this question are in the second row and have talked themselves into the third. Per-tenant databases have real operational weight — migrations now run N times, monitoring is N dashboards, and a schema change that fails on tenant 47 is a new class of incident. Do it because isolation is a requirement, not because it sounds tidy.

If you are migrating off SQLite

SQLite to Postgres is mostly mechanical, and the surprises are consistent enough to list.

  1. Types. SQLite is dynamically typed and will happily store a string in an integer column. Postgres will not. Expect to find data that was never valid and has been fine for two years.
  2. Autoincrement. SQLite's rowid behaviour and Postgres sequences differ; after any bulk load, reset your sequences or the first insert will collide.
  3. Booleans and dates. SQLite stores them as integers and text. Decide on the target representation before you migrate, not during.
  4. Concurrency. SQLite serialises writers. Code written against that can contain assumptions — read-then-write without a transaction — that were safe there and are races in Postgres.
  5. Full-text search. FTS5 has no drop-in Postgres equivalent; tsvector is better but different. Budget real time for this if you use it.
# pgloader handles the bulk of a SQLite -> Postgres move, including
# type coercion, in one command.
pgloader ./tenant-42.db "$DATABASE_URL"

# Then the two things it cannot know for you:
psql "$DATABASE_URL" <<'SQL'
-- 1. Reset sequences that a bulk load left behind.
SELECT setval(
  pg_get_serial_sequence('orders', 'id'),
  COALESCE((SELECT MAX(id) FROM orders), 1)
);
-- 2. Check the rows SQLite let through and Postgres would not have.
SELECT count(*) FROM orders WHERE created_at IS NULL;
SQL

Pick by situation

  • Global read latency, compute already on Cloudflare → D1.
  • Global read latency, compute anywhere else → Postgres read replicas, or a cache, before anything exotic.
  • Thousands of tiny tenants → SQLite files with streaming backup, or stay on a libSQL-shaped product.
  • Tens or hundreds of real tenants needing hard isolation → a database per tenant, on Neon or a microVM platform.
  • One application, one region, ordinary needs → a single Postgres. You will get more done.
  • Local-first with sync → this is a narrow field and worth evaluating on the sync protocol rather than the database.

The short version

Turso is two products wearing one name, and the alternatives only make sense once you have said which one you were buying. Edge reads and per-tenant isolation have almost no overlap in their shortlists.

And it is worth asking whether you need either. A great many applications adopt a distributed database to solve a latency problem that a cache would have solved, or a per-tenant architecture to solve an isolation problem they do not contractually have. Both mistakes are expensive in operational time rather than money, which is why they take so long to notice.

Frequently asked questions

Is SQLite good enough for production?

For a great many production workloads, yes — SQLite is one of the most thoroughly tested pieces of software in existence, and for read-heavy applications on a single machine it is faster than a client-server database because there is no network hop at all. The limits are architectural rather than about quality. Writers are serialised, so write-heavy concurrent workloads hit a wall that no amount of tuning removes. It lives on one machine's disk, so durability and availability become your problem via streaming replication to object storage. And horizontal scale means sharding by hand. If your application is read-dominated, fits on one machine, and you are prepared to own the backup story, SQLite in production is a legitimate and often excellent engineering choice rather than a compromise.

What is the difference between libSQL and SQLite?

libSQL is a fork of SQLite that adds capabilities SQLite deliberately does not have: a server mode so clients can connect over the network, replication between a primary and read replicas, and embedded replicas that keep a local copy in sync with a remote primary. The SQL dialect and file format remain compatible, so existing SQLite knowledge and most tooling carry over directly. The practical implication for a migration decision is that if you are using plain SQLite features, moving between SQLite, libSQL, and a hosted libSQL service is straightforward. If you have built on the replication or embedded-replica behaviour, that is the part that does not have an equivalent elsewhere, and it is what you would need to replace.

Should I give every tenant their own database?

Only if isolation is a requirement rather than an aesthetic preference, because the operational cost is real and arrives all at once. With a database per tenant, every schema migration runs N times and can fail partway through, leaving your fleet in mixed states. Monitoring, connection pooling, and backup verification all multiply. Onboarding a tenant becomes a provisioning operation that can fail. What you buy for that is genuine blast-radius containment, per-tenant restore as a normal operation rather than a heroic one, clean deletion for data-residency and right-to-erasure requests, and the ability to put an isolation guarantee in a contract. At tens of substantial tenants that trade is often clearly worth it. At thousands of small ones it usually is not, and row-level security in a single Postgres will serve you better.

Do edge databases actually make my app faster?

They reduce read latency for users far from your primary region, which helps only if database round trips were the dominant cost — and surprisingly often they are not. Before distributing anything, measure where the time actually goes: for many applications the answer is a slow query, an N+1 pattern, or a cold serverless function, none of which a replica in Frankfurt improves. Edge databases also introduce costs that are easy to underestimate. Writes still go to a primary, so write latency for distant users can get worse. Replication lag means a user can write a value and read back the old one. And your application now has consistency semantics it did not have before. Fix the query and add a cache first; distribute the database when you have evidence that geography is the bottleneck.

Can I use Postgres for a database-per-tenant architecture?

Yes, and there are three distinct ways to do it with very different characteristics. A schema per tenant inside one database is cheapest and gives logical separation, but everything shares one set of resources, so a runaway tenant affects all of them. A separate Postgres database per tenant on shared infrastructure adds real separation of data and connection limits while keeping one server. A separate Postgres instance per tenant — a container, a VM, or a microVM — gives you resource isolation as well, so one tenant's workload genuinely cannot starve another, and it makes per-tenant restore and deletion trivial. Cost and operational weight rise across those three in the same order. Pick the weakest one that satisfies your actual isolation requirement, and write that requirement down before choosing, because it is easy to buy more isolation than anyone asked for.

Keep reading

Run code in a microVM in one API call.

49ms p50 cold start. Fork, snapshot, and scale to zero.

Start free
Written by Ajay Kumar, Founder, PandaStack.