Introduction
The database layer has traditionally been the bottleneck in global application architectures. While compute and caching have moved to the edge, databases remained anchored in a single region, forcing every query to travel thousands of kilometers. Edge databases change this by replicating data to locations worldwide, enabling queries with single-digit millisecond latency from anywhere on the planet.
The edge database landscape has matured rapidly. Turso, built on SQLite's libSQL fork, replicates lightweight databases to 35+ locations. Neon pioneered serverless PostgreSQL with scale-to-zero and branching. PlanetScale (built on Vitess) offers MySQL-compatible sharding with online schema migrations. Each represents a different philosophy on how to bring data closer to users.
This guide compares these three edge databases across architecture, latency, developer experience, pricing, and production readiness. You'll learn when to choose each one, how to integrate them with edge functions, and the patterns that make edge data access fast and reliable.
Understanding Edge Databases: Core Concepts
What Makes a Database "Edge-Native"
An edge database is not simply a traditional database deployed in multiple regions. Edge-native databases are designed from the ground up for the constraints of edge computing: sub-10ms query latency, serverless scaling, minimal connection overhead, and compatibility with edge runtimes that lack full Node.js support.
The key architectural difference is replication strategy. Traditional databases replicate for high availability — a standby server takes over if the primary fails. Edge databases replicate for latency — data is copied to every location where users exist, so queries hit a local replica instead of traveling to a primary region.
This creates a fundamental consistency trade-off. Strong consistency requires every write to be acknowledged by all replicas before returning, which defeats the latency advantage. Most edge databases offer eventual consistency for reads with strong consistency for writes, accepting that reads might be a few milliseconds behind the primary.
The Serverless Connection Model
Traditional databases use persistent TCP connections — a connection pool sits in front of the database and maintains long-lived connections to the server. This model doesn't work at the edge because edge functions are ephemeral — they spin up, handle a request, and disappear. Maintaining a persistent connection per edge function would overwhelm the database.
Edge databases solve this with HTTP-based or WebSocket-based protocols. Instead of TCP connections, each query is an HTTP request. This eliminates connection pooling overhead and works perfectly with edge runtimes. The trade-off is slightly higher per-query latency (1-2ms for HTTP overhead) but this is negligible compared to the latency saved by edge replication.
// Traditional database: persistent TCP connection
import { Pool } from 'pg';
const pool = new Pool({ connectionString: 'postgresql://...' });
const client = await pool.connect();
const result = await client.query('SELECT * FROM users');
client.release();
// Edge database: HTTP-based queries (no connection pool)
import { createClient } from '@libsql/client';
const db = createClient({ url: 'libsql://your-db.turso.io', authToken: '...' });
const result = await db.execute('SELECT * FROM users');
// No connection management neededReplication Topologies
Edge databases use different replication topologies:
Single-Primary, Multi-Replica: One primary handles all writes, replicas serve reads globally. This is the simplest model but writes are limited by the primary's location. Turso uses this model.
Multi-Primary: Multiple locations can accept writes, with conflict resolution. More complex but lower write latency. CockroachDB and YugabyteDB use this model.
Branching: Neon's unique approach creates database branches (like git branches) for development and preview environments. Each branch is a full copy that can diverge from the main branch.
Architecture and Design Patterns
Turso Architecture
Turso is built on libSQL, an open-source fork of SQLite maintained by ChiselStrike. The core insight is that SQLite's single-file database format is perfect for edge replication — a database small enough to fit in a single file is small enough to replicate to 35+ locations.
// Turso client with automatic nearest-replica routing
import { createClient } from '@libsql/client';
const db = createClient({
url: 'libsql://your-db.turso.io',
authToken: process.env.TURSO_AUTH_TOKEN,
});
// This query runs against the nearest edge replica
const users = await db.execute('SELECT * FROM users WHERE active = 1');
// Batch queries for efficiency
const [users, posts, comments] = await db.batch([
'SELECT * FROM users LIMIT 10',
'SELECT * FROM posts ORDER BY created_at DESC LIMIT 20',
'SELECT COUNT(*) as count FROM comments',
]);Turso's key advantage is simplicity. SQLite is the most widely deployed database in the world, and libSQL maintains full compatibility. Existing SQLite tools, ORMs, and knowledge transfer directly. The trade-off is that Turso databases are limited in size (typically under 10GB) and don't support the full SQL feature set of PostgreSQL.
Neon Architecture
Neon is serverless PostgreSQL. It separates storage and compute — the storage layer is a custom distributed system that replicates data across multiple availability zones, while the compute layer runs PostgreSQL instances that scale to zero when idle.
Neon's killer feature is branching. You can create a database branch (like a git branch) that is a full copy of your database at a point in time. This is transformative for development workflows — each pull request gets its own database branch with real data, and branches can be created in milliseconds regardless of database size.
// Neon serverless driver for edge compatibility
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// Simple query — HTTP-based, works at the edge
const users = await sql('SELECT * FROM users WHERE active = $1', [true]);
// Transaction support
const result = await sql.transaction([
sql`INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING id`,
sql`UPDATE users SET order_count = order_count + 1 WHERE id = $1`,
]);Neon's edge support comes through its serverless driver, which uses HTTP for queries instead of TCP connections. This makes it compatible with Cloudflare Workers, Vercel Edge Functions, and Deno Deploy. The trade-off is that each query has HTTP overhead (~1-2ms) and the driver doesn't support cursors or streaming results.
PlanetScale Architecture
PlanetScale is built on Vitess, the database sharding system originally developed at YouTube. It provides MySQL-compatible horizontal sharding with online schema migrations — you can alter tables without downtime or locking.
PlanetScale's unique feature is deploy requests. Schema changes are proposed as deploy requests (like pull requests), reviewed, and applied with zero downtime. Vitess handles the online schema migration internally, using a shadow table approach that copies data to the new schema in the background while the old schema continues serving traffic.
// PlanetScale serverless driver
import { connect } from '@planetscale/database';
const conn = connect({
url: process.env.DATABASE_URL,
});
// Query with connection pooling handled by PlanetScale
const users = await conn.execute('SELECT * FROM users WHERE active = ?', [true]);
// Prepared statements for security
const user = await conn.execute(
'SELECT * FROM users WHERE id = ?',
[userId]
);PlanetScale's edge support comes through its serverless driver, similar to Neon. However, PlanetScale is MySQL-compatible, which may be a better fit for teams with MySQL experience or existing MySQL schemas.
Step-by-Step Implementation
Let's build a complete edge application that demonstrates all three databases with Cloudflare Workers and Hono.
Setting Up the Edge Database Application
npm create cloudflare@latest edge-db-demo -- --type=hello-world
cd edge-db-demo
npm install hono @libsql/client @neondatabase/serverless @planetscale/databaseTurso Integration
import { Hono } from 'hono';
import { createClient } from '@libsql/client';
const app = new Hono();
app.get('/api/turso/users', async (c) => {
const db = createClient({
url: c.env.TURSO_URL,
authToken: c.env.TURSO_AUTH_TOKEN,
});
const start = performance.now();
const result = await db.execute('SELECT * FROM users LIMIT 20');
const latency = performance.now() - start;
return c.json({
source: 'turso',
latency: `${latency.toFixed(2)}ms`,
count: result.rows.length,
users: result.rows,
});
});Neon Integration
import { neon } from '@neondatabase/serverless';
app.get('/api/neon/users', async (c) => {
const sql = neon(c.env.NEON_DATABASE_URL);
const start = performance.now();
const users = await sql('SELECT * FROM users LIMIT 20');
const latency = performance.now() - start;
return c.json({
source: 'neon',
latency: `${latency.toFixed(2)}ms`,
count: users.length,
users,
});
});PlanetScale Integration
import { connect } from '@planetscale/database';
app.get('/api/planetscale/users', async (c) => {
const conn = connect({ url: c.env.PLANETSCALE_URL });
const start = performance.now();
const result = await conn.execute('SELECT * FROM users LIMIT 20');
const latency = performance.now() - start;
return c.json({
source: 'planetscale',
latency: `${latency.toFixed(2)}ms`,
count: result.rows.length,
users: result.rows,
});
});Database Benchmark Comparison
app.get('/api/benchmark', async (c) => {
const results: Record<string, number> = {};
// Turso benchmark
const tursoDb = createClient({
url: c.env.TURSO_URL,
authToken: c.env.TURSO_AUTH_TOKEN,
});
const tursoStart = performance.now();
await tursoDb.execute('SELECT * FROM users LIMIT 100');
results.turso = performance.now() - tursoStart;
// Neon benchmark
const neonSql = neon(c.env.NEON_DATABASE_URL);
const neonStart = performance.now();
await neonSql('SELECT * FROM users LIMIT 100');
results.neon = performance.now() - neonStart;
// PlanetScale benchmark
const psConn = connect({ url: c.env.PLANETSCALE_URL });
const psStart = performance.now();
await psConn.execute('SELECT * FROM users LIMIT 100');
results.planetscale = performance.now() - psStart;
return c.json(results);
});Real-World Use Cases and Case Studies
Use Case 1: Global SaaS with Turso
A multi-tenant SaaS platform serves 5,000 customers across 30 countries. Using Turso, each tenant's data is replicated to the nearest edge location. Read queries complete in 2-5ms regardless of user location. The platform's P99 latency dropped from 340ms (single-region PostgreSQL) to 15ms (Turso edge replicas). The SQLite compatibility meant the existing team could migrate without learning a new query language.
Use Case 2: Development Workflow with Neon
A startup uses Neon's branching feature to create database branches for every pull request. When a developer opens a PR, CI creates a Neon branch with a copy of production data (anonymized), runs integration tests against it, and destroys the branch when the PR merges. This eliminates the "works on my machine" database problem and reduced database-related production incidents by 60%.
Use Case 3: High-Scale E-Commerce with PlanetScale
An e-commerce platform processing 50,000 orders per hour uses PlanetScale for its horizontal sharding capability. The database is sharded by tenant ID, with each shard handling up to 10,000 orders per minute. Deploy requests handle schema migrations for new features without downtime — the platform has deployed 200+ schema changes in the past year without a single minute of downtime.
Best Practices for Production
-
Choose based on data model complexity: Turso excels for simple schemas (< 100 tables) with moderate data volumes. Neon is best for complex PostgreSQL schemas with joins, CTEs, and advanced SQL. PlanetScale is ideal for high-write workloads that need horizontal sharding.
-
Implement read-write splitting: Route reads to edge replicas and writes to the primary. Most edge databases support this automatically, but verify the routing behavior for your specific use case.
-
Use connection pooling for traditional drivers: If you must use a traditional database driver (not the HTTP-based edge driver), use connection pooling. PgBouncer for PostgreSQL, ProxySQL for MySQL.
-
Monitor replication lag: Edge replicas may lag behind the primary by milliseconds to seconds. Monitor this lag and alert if it exceeds acceptable thresholds for your application.
-
Implement retry logic with exponential backoff: Edge database connections can fail due to network issues between edge locations. Implement retry logic with exponential backoff and circuit breaker patterns.
-
Use prepared statements: All three databases support prepared statements, which prevent SQL injection and improve query performance through plan caching.
-
Cache at the edge layer: Don't query the edge database for data that changes infrequently. Use edge KV or CDN caching for reference data, and only query the database for dynamic user-specific data.
-
Plan for data migration: If migrating from a traditional database, plan the data migration carefully. Turso requires SQLite format, Neon accepts PostgreSQL dumps, and PlanetScale accepts MySQL imports. Use schema conversion tools for cross-database migrations.
Common Pitfalls and Solutions
| Pitfall | Impact | Solution |
|---|---|---|
| Querying from edge to single-region DB | No latency improvement | Use edge databases with replicated reads |
| Assuming strong consistency on reads | Stale data after writes | Read from primary for consistency-critical queries |
| Large database on Turso | Performance degradation, size limits | Keep Turso databases under 10GB, shard for larger datasets |
| Missing connection pooling | Connection exhaustion under load | Use HTTP-based edge drivers or connection poolers |
| Ignoring replication lag | Serving outdated data | Monitor lag, use version vectors, read-after-write consistency |
| No failover strategy | Complete outage on replica failure | Implement multi-region fallback, cache stale data |
Performance Optimization
// Edge database performance monitoring
function withLatencyTracking<T>(
name: string,
fn: () => Promise<T>
): Promise<{ result: T; latency: number }> {
return (async () => {
const start = performance.now();
const result = await fn();
const latency = performance.now() - start;
// Log slow queries
if (latency > 50) {
console.warn(`Slow query [${name}]: ${latency.toFixed(2)}ms`);
}
return { result, latency };
})();
}
// Usage with edge database
const { result: users, latency } = await withLatencyTracking('users-list', () =>
db.execute('SELECT * FROM users WHERE active = 1')
);
console.log(`Query took ${latency.toFixed(2)}ms, returned ${users.rows.length} rows`);Comparison with Alternatives
| Feature | Turso | Neon | PlanetScale | Traditional PostgreSQL |
|---|---|---|---|---|
| Database Engine | SQLite (libSQL) | PostgreSQL | MySQL (Vitess) | PostgreSQL |
| Edge Replicas | 35+ locations | 3 regions | Multi-region | None |
| Read Latency (edge) | 2-5ms | 5-15ms | 5-15ms | 50-300ms |
| Write Latency | 20-50ms | 20-50ms | 10-30ms | 1-5ms |
| Max Database Size | ~10GB | Unlimited | Unlimited | Unlimited |
| Branching | No | Yes (git-like) | No | No |
| Schema Migrations | Manual | Manual | Deploy requests | Manual |
| Scale to Zero | Yes | Yes | No | No |
| Pricing Model | Per-query | Per-compute hour | Per-row read | Per-instance |
| Edge Driver | HTTP (native) | HTTP (serverless) | HTTP (serverless) | TCP (not edge-compatible) |
Advanced Patterns
Multi-Database Architecture
Use different databases for different concerns:
// Turso for read-heavy data (products, content)
const turso = createClient({ url: env.TURSO_URL, authToken: env.TURSO_TOKEN });
// Neon for complex relational data (orders, analytics)
const neon = neon(env.NEON_URL);
// KV for caching and sessions
const kv = env.KV;
app.get('/api/products/:id', async (c) => {
const id = c.req.param('id');
// L1: Edge KV cache
const cached = await kv.get(`product:${id}`, 'json');
if (cached) return c.json(cached);
// L2: Turso edge replica (fast reads)
const product = await turso.execute({
sql: 'SELECT * FROM products WHERE id = ?',
args: [id],
});
if (product.rows.length === 0) return c.json({ error: 'Not found' }, 404);
// Cache for 5 minutes
await kv.put(`product:${id}`, JSON.stringify(product.rows[0]), {
expirationTtl: 300,
});
return c.json(product.rows[0]);
});Edge Database with Local Cache
// LRU cache at the edge for ultra-fast repeated reads
const cache = new Map<string, { data: unknown; expiry: number }>();
async function cachedQuery<T>(
db: LibsqlClient,
key: string,
sql: string,
args: unknown[],
ttlMs: number = 5000
): Promise<T> {
const cached = cache.get(key);
if (cached && cached.expiry > Date.now()) {
return cached.data as T;
}
const result = await db.execute({ sql, args });
cache.set(key, { data: result.rows, expiry: Date.now() + ttlMs });
return result.rows as T;
}Testing Strategies
import { unstable_dev } from 'wrangler';
describe('Edge Database Integration', () => {
let worker: any;
beforeAll(async () => {
worker = await unstable_dev('src/index.ts', {
experimental: { disableExperimentalWarning: true },
});
});
afterAll(async () => {
await worker.stop();
});
test('Turso query completes within 20ms', async () => {
const resp = await worker.fetch('/api/turso/users');
const data = await resp.json();
expect(data.latency).toBeDefined();
expect(parseFloat(data.latency)).toBeLessThan(20);
});
test('Neon query returns correct data', async () => {
const resp = await worker.fetch('/api/neon/users');
const data = await resp.json();
expect(data.users).toBeDefined();
expect(Array.isArray(data.users)).toBe(true);
});
test('benchmark compares all databases', async () => {
const resp = await worker.fetch('/api/benchmark');
const data = await resp.json();
expect(data).toHaveProperty('turso');
expect(data).toHaveProperty('neon');
expect(data).toHaveProperty('planetscale');
});
});Future Outlook
Edge databases are converging toward a unified model: serverless scaling, edge replication, HTTP-based protocols, and compatibility with all major edge runtimes. Turso is expanding its SQL feature set. Neon is adding more edge locations. PlanetScale is improving its edge driver.
The next frontier is edge-native features: vector search for AI applications, full-text search for content platforms, and real-time subscriptions for collaborative applications. These features are already available in traditional databases but are being adapted for the edge's unique constraints.
Conclusion
Edge databases are the missing piece in the edge computing puzzle. Without edge data, edge functions are limited to caching and routing. With Turso, Neon, or PlanetScale, edge functions can query data with single-digit millisecond latency from anywhere in the world.
Key takeaways:
- Turso is ideal for simple schemas and SQLite-compatible workloads with global reads
- Neon excels for complex PostgreSQL schemas with branching workflows
- PlanetScale is best for high-write workloads needing horizontal sharding
- All three offer HTTP-based edge drivers compatible with edge runtimes
- Implement multi-layer caching: edge KV → edge database → origin
- Monitor replication lag and implement retry logic for edge database access
Choose based on your team's existing expertise (SQLite → Turso, PostgreSQL → Neon, MySQL → PlanetScale), your data model complexity, and your scaling requirements. The edge database landscape is evolving rapidly, and all three are production-ready for the right use case.