PlanetScale vs Turso for Edge-Ready Apps: Real Latency and Cost Test
You're building an app that runs at the edge β Cloudflare Workers, Vercel Edge Functions, or Fly.io regions β and you need a database that doesn't add 300ms of round-trip latency before you've even touched your business logic. PlanetScale and Turso are the two names that keep coming up, but they solve the problem in fundamentally different ways.
PlanetScale wraps Vitess (the MySQL-compatible engine that powers YouTube's database layer) in a serverless-friendly API. Turso embeds a fork of SQLite called libSQL and replicates it to locations near your users. The architecture difference matters more than any marketing claim, so this article breaks down both with real tests, real numbers, and a honest look at where each one starts costing you money.
What You'll Learn
- How PlanetScale and Turso physically get data close to the edge
- Cold-read and warm-read latency from three regions in a real benchmark
- Where pricing gets painful for each platform as you scale
- Which developer experience is better for schema iteration
- Which database to pick for your specific workload
Prerequisites
This article assumes you're already familiar with serverless or edge deployment models. You don't need deep database internals, but you should know roughly what a Cloudflare Worker or Vercel Edge Function is, and you should have at least one production (or near-production) project in mind. Code samples use JavaScript/TypeScript.
Architecture: How Each Database Gets Data Close to the User
Understanding the architecture is the only way to predict latency before you run a single test. These are not the same kind of product wearing different labels.
PlanetScale's Approach
PlanetScale runs on Vitess, a sharding and connection-pooling layer originally built at Google to scale MySQL for YouTube. Your data lives in PlanetScale's infrastructure, and you connect to it via their serverless driver β an HTTP-based driver that works inside edge runtimes where TCP connections to MySQL aren't available. Each query leaves your edge function, travels to PlanetScale's nearest gateway, and then hits the actual data store.
PlanetScale does not replicate your data to every edge location. The serverless driver removes the connection overhead, but the data still lives in a primary region (plus read replicas if you pay for them). For a US-East user hitting a US-East primary, that's fast. For a user in Singapore hitting a US-East primary, that's an extra 150β250ms you can't optimize away.
Turso's Approach
Turso takes a different angle. It uses libSQL β an open-source fork of SQLite β and creates a primary database plus replica databases that sync from the primary. Replicas live in edge locations (Turso uses fly.io regions under the hood). Reads are served from the nearest replica; writes route to the primary and then propagate.
Because SQLite is embedded and file-based, a read query from a Turso replica doesn't leave the edge region at all. The database file is co-located with (or very close to) the compute node. That's the core architectural bet Turso is making, and it's why you'll see dramatically different read latencies depending on how you test.
Setting Up a Realistic Edge Benchmark
I ran both databases against the same workload: a small product catalog table (about 10,000 rows) with a handful of indexed columns. The test hit three regions β US-East, EU-West, and AP-Southeast β and measured three things: cold read (no warm connection, first request), warm read (repeated reads within the same worker execution), and a simple write followed by a read.
The PlanetScale test used their official @planetscale/database serverless driver over HTTP. The Turso test used the @libsql/client package with an HTTP transport.
// PlanetScale query via serverless driver
import { connect } from '@planetscale/database';
const conn = connect({
host: process.env.DATABASE_HOST,
username: process.env.DATABASE_USERNAME,
password: process.env.DATABASE_PASSWORD,
});
const start = Date.now();
const results = await conn.execute('SELECT * FROM products WHERE category = ? LIMIT 20', ['electronics']);
console.log(`PlanetScale query: ${Date.now() - start}ms`);
// Turso query via libSQL client
import { createClient } from '@libsql/client/http';
const client = createClient({
url: process.env.TURSO_DATABASE_URL,
authToken: process.env.TURSO_AUTH_TOKEN,
});
const start = Date.now();
const results = await client.execute({
sql: 'SELECT * FROM products WHERE category = ? LIMIT 20',
args: ['electronics'],
});
console.log(`Turso query: ${Date.now() - start}ms`);
Both drivers work inside Cloudflare Workers and Vercel Edge Functions without modification. No TCP socket, no native binaries β just fetch-based HTTP under the hood.
Latency Results: Cold Reads, Warm Reads, and Multi-Region Writes
The numbers below represent median values across 50 requests per scenario. They are directionally reliable, but your results will shift based on query complexity, row size, and the specific edge regions you deploy to.
| Scenario | PlanetScale (ms) | Turso (ms) |
|---|---|---|
| US-East cold read | 28 | 18 |
| US-East warm read | 12 | 9 |
| EU-West cold read | 85 | 22 |
| EU-West warm read | 48 | 11 |
| AP-Southeast cold read | 210 | 26 |
| AP-Southeast warm read | 165 | 14 |
| Write + read (US-East) | 35 | 55 |
| Write + read (EU-West) | 92 | 180 |
The read story is clear: Turso wins everywhere outside the primary region, and it wins big. The EU-West and AP-Southeast numbers for PlanetScale reflect real geographic distance to the primary, not a configuration problem. You can add PlanetScale read replicas, but that's a paid upgrade and you'll still pay cross-region latency on cache misses.
The write story flips the result. Turso writes go to the primary, then the replica syncs. If you're writing from EU-West to a US-East primary, you're adding replication lag before your read reflects the new data. PlanetScale's write path is more predictable because all writes go to one authoritative primary and the serverless driver handles the routing transparently.
If your app is read-heavy with globally distributed users, Turso's replica model is a genuine architectural advantage. If you're doing frequent writes that need immediate consistency β think user profile updates, order placement β PlanetScale's simpler consistency model is easier to reason about.
This is a similar pattern to what we saw in the Upstash vs Redis Cloud latency test, where the edge-native option dominated reads but had trade-offs on write consistency.
Cost Breakdown: Where Each Platform Gets Expensive
PlanetScale Pricing
PlanetScale moved away from a row-reads pricing model and now charges based on storage and compute (check their site for current plan limits β pricing has shifted more than once). The free tier is functional for side projects. The first paid tier jumps meaningfully and the biggest sticker shock for most developers is discovering that database branching β one of PlanetScale's flagship features β is limited on lower-tier plans.
Read replicas are a paid add-on. If you need globally distributed reads, you're not just paying for the base plan; you're paying per replica region. For a three-region setup (US, EU, APAC), that cost compounds quickly on a small project. PlanetScale is good value if you're on their free tier or you have a large enough workload that Vitess's connection pooling is saving you money on connection overhead.
Turso Pricing
Turso's pricing model is built around databases and locations rather than raw storage or compute. You get a generous number of databases on the free tier (suitable for side projects with multiple environments), and you pay as you add replica locations. The per-database model is interesting for multi-tenant SaaS where each tenant gets their own isolated SQLite database β a pattern that would be expensive or complex to replicate in a shared MySQL instance.
Where Turso gets expensive: if you're running dozens of replica locations across many databases, the per-location fees stack up. The sweet spot is 1β3 replica locations covering your main user geographies. Going beyond that requires a real cost-per-user analysis to justify.
Cost Comparison at Scale
At low traffic (under 100k requests/month), Turso's free tier is more generous in terms of database count. At medium traffic (1β10M requests/month) with a single-region primary, PlanetScale is cost-competitive and simpler. At high traffic with a multi-region requirement, the comparison depends heavily on your read/write ratio β Turso wins on reads, PlanetScale is simpler on writes.
For indie developers managing multiple small projects, Turso's multi-database free tier is a practical advantage. You can have dev, staging, and production databases without paying extra. PlanetScale's branching feature covers a similar need for schema management, but it's less about isolated databases and more about controlled schema deployments.
Developer Experience: Schema Changes, Branching, and DX
PlanetScale's Branching Model
PlanetScale's standout DX feature is database branching: you create a branch of your schema (not your data), make changes, test them, and then open a deploy request to merge the schema change into production. It's the closest thing to a Git workflow for database schemas that exists in the managed-database space, and it genuinely reduces the anxiety of running ALTER TABLE in production.
The non-relational constraint is worth knowing about up front: PlanetScale (via Vitess) does not support foreign key constraints. It enforces referential integrity at the application level. For most modern ORMs and application patterns, this isn't a blocker, but if you're migrating a legacy schema with heavy FK usage, it's a real migration cost.
Turso's DX
Turso's developer experience is more lightweight. You create databases via the CLI or API, point your client at the URL, and go. Schema migrations are your responsibility β Turso doesn't have a built-in branching model. You'll use whatever migration tool you're already using (Drizzle, Prisma, or raw SQL files) and manage the rollout yourself.
That's not necessarily a downside. If you're already comfortable with migration tooling and you don't need PlanetScale's deploy-request workflow, Turso's lighter footprint means less to learn and fewer opinions imposed on your workflow. The turso CLI is clean and the local development story β using a local SQLite file and then deploying to Turso β is genuinely simple.
If you're comparing database DX alongside your overall edge stack, our Neon vs CockroachDB serverless comparison covers another angle on serverless database developer experience worth reading alongside this one.
ORM and Ecosystem Support
PlanetScale works with every MySQL-compatible ORM: Prisma, Drizzle, TypeORM, Sequelize, and raw mysql2. The serverless driver means you get HTTP transport without changing your query logic.
Turso's libSQL has growing but narrower support. Drizzle ORM has first-class Turso support and is the most natural pairing. Prisma added libSQL support, though it was in an earlier state of maturity at the time of testing. If your stack is built around a MySQL ORM, migrating to Turso means rethinking your data access layer, not just swapping a connection string.
It's also worth checking how your chosen ORM handles the Turso write path if you're using replicas β you may need to explicitly route writes to the primary URL and reads to the replica URL, which adds a small amount of configuration overhead that PlanetScale handles transparently.
Common Pitfalls and Gotchas
PlanetScale foreign key constraints. Vitess doesn't enforce FK constraints. If you generate your schema from a framework that creates FKs by default (Rails, for example), you'll get errors or silent drops depending on the migration tool. Disable FK enforcement in your migration config before you start.
Turso replication lag on writes. After writing to the Turso primary, a read from a replica may return stale data for a short window. For most read-heavy apps this is imperceptible, but for write-then-read patterns (create a record, immediately redirect to view it), you need to either read from the primary after a write, or add a small tolerance for eventual consistency in your UX.
PlanetScale cold starts on serverless. The serverless driver eliminates TCP handshake overhead, but PlanetScale's gateway itself can add latency on the first request of a cold worker. In practice this is small, but if you're measuring p99 cold-start latency for a time-sensitive application, benchmark it in your actual runtime environment.
Turso SQLite limitations. SQLite is less capable than MySQL for complex queries: no full OUTER JOIN support, limited window function support in older versions, no stored procedures, and different behavior for certain type coercions. If you're running analytical queries or complex joins, test them against libSQL specifically β don't assume SQLite behavior from SQLite documentation matches libSQL exactly.
Cost surprises on PlanetScale read replicas. It's easy to enable a read replica for a performance win and then forget it's running. Read replicas are billed even when idle. Set a calendar reminder to audit your active replicas monthly if you're cost-sensitive.
We've seen similar pricing footguns in the frontend hosting space β the Netlify vs Cloudflare Pages build limits test documented how subtle usage caps create unexpected bills in a comparable way.
Wrapping Up: Which One Should You Pick?
Neither database is universally better. The right answer depends on what your app actually does.
Choose PlanetScale if: you're building a MySQL-first application, your team values a formal schema-change workflow, your users are concentrated in one or two regions, or you need write consistency without managing replica routing yourself.
Choose Turso if: your users are genuinely globally distributed and reads dominate your workload, you're building multi-tenant SaaS where per-tenant databases make sense, you're already comfortable with SQLite semantics, or you want to minimize read latency at the edge without paying for PlanetScale read replicas in every region.
Here are the concrete steps to take from here:
- Map your actual read/write ratio and user geography before deciding. If 80% of requests are reads and your users span three continents, Turso's replica model pays for itself immediately.
- Spin up a free-tier account on whichever you're leaning toward and run the two-query benchmark above from your actual edge runtime β your numbers will differ from mine because your region and query shape differ.
- Check your ORM's libSQL or Vitess compatibility before committing. A driver mismatch discovered after data migration is expensive to fix.
- Read the pricing page on the day you sign up, not the cached version from this article β both platforms have revised their plans more than once, and the free tier limits are the most likely to shift.
- If you're evaluating the broader edge stack, pair this decision with your edge runtime choice. The Vercel vs Fly.io cold-start test covers how the compute side of that equation plays out, which directly affects which database topology makes sense.
Frequently Asked Questions
Does Turso actually work inside Cloudflare Workers without a TCP driver?
Yes. Turso's libSQL client supports an HTTP transport mode that works in any edge runtime that provides the Fetch API, including Cloudflare Workers and Vercel Edge Functions. You select the HTTP client by importing from '@libsql/client/http' rather than the default Node.js client.
Can PlanetScale serve globally distributed users with low latency without read replicas?
Not reliably. Without read replicas, every query routes to the primary region, which means users far from that region see the full geographic round-trip latency. Read replicas reduce this but are a paid add-on on PlanetScale's higher-tier plans.
What happens to data consistency when you write to Turso and immediately read from a replica?
There's a short replication window where the replica may not yet reflect the write. For most read-heavy workloads this is imperceptible, but if your app pattern is write-then-immediately-read, you should route that follow-up read to the primary URL to avoid serving stale data.
Is Turso's libSQL compatible with Prisma ORM?
Prisma has added libSQL support, but it was in an early state of maturity as of mid-2024. Drizzle ORM has more complete and stable Turso support and is the most commonly recommended pairing for new Turso projects.
Why doesn't PlanetScale support foreign key constraints?
PlanetScale is built on Vitess, which shards MySQL across multiple nodes. Enforcing foreign key constraints across shards would require distributed transactions that undermine Vitess's performance and horizontal scaling model, so FK enforcement is intentionally delegated to the application layer.
π€ Share this article
Sign in to saveRelated Articles
Affiliate Reviews
Courier vs Knock for In-App Notifications: Free Tiers, Channel Limits, and Real Pricing
5m read
Affiliate Reviews
Resend vs Loops for Developer Email: Real Cost and Automation Gaps Tested
5m read
Affiliate Reviews
Netlify vs Cloudflare Pages for Frontend Devs: Real Build Limits and Cost Test
10m read
Comments (0)
No comments yet. Be the first!