MinhVo

Minh Vo

rss feed

Slaying code & making it lit fr fr 🔥 tagline

Hey there 👋 I'm an AI Engineer with 7 years of experience building scalable web and mobile applications. Currently at Neurond AI (May 2025 — present), architecting an Enterprise AI Assistant Platform with multi-tenant RAG on pgvector, multi-provider LLM orchestration, and Azure-native infrastructure. Previously spent 5+ years at SNAPTEC (Sep 2019 — Apr 2025), leading SaaS themes, admin dashboards, and e-commerce platforms — earned the Hero of the Year award in 2021. I specialize in TypeScript, React, Next.js, and AI-Native engineering with Claude Code and Cursor.bio

Back to blogs

Cloud-Native Databases: CockroachDB, PlanetScale, and Neon

Compare CockroachDB, PlanetScale, and Neon: architecture, scalability, pricing, and developer experience.

DatabaseCloud-NativeCockroachDBPlanetScale

By MinhVo

Introduction

The database landscape has undergone a dramatic transformation in recent years. Traditional relational databases, while powerful, often struggle to meet the demands of modern cloud-native applications that require global distribution, automatic scaling, and zero-downtime operations. Cloud-native databases have emerged to address these challenges, offering developers the familiarity of SQL with the scalability and resilience of distributed systems.

Three platforms stand out in this space: CockroachDB, PlanetScale, and Neon. Each takes a different approach to solving the same fundamental problem—how do you provide a PostgreSQL-compatible or MySQL-compatible experience that scales horizontally across regions without sacrificing consistency or developer experience? CockroachDB focuses on global consistency with its distributed SQL architecture, PlanetScale brings Git-like schema management to MySQL, and Neon reimagines PostgreSQL with serverless branching and instant provisioning.

Cloud-native database architecture

Choosing the right cloud-native database depends on your application's specific requirements: consistency guarantees, latency patterns, scaling characteristics, operational complexity, and cost structure. This guide provides a deep technical comparison of all three platforms to help you make an informed decision for your next project.

Understanding Cloud-Native Databases: Core Concepts

Cloud-native databases are purpose-built to run in distributed cloud environments. Unlike traditional databases that run on a single server or a manually managed cluster, cloud-native databases handle replication, sharding, failover, and scaling automatically. They embrace the principles of the cloud: elasticity, resilience, and pay-as-you-go pricing.

The fundamental architecture of cloud-native databases involves separating storage from compute. This separation allows each layer to scale independently—you can add more compute nodes for query throughput without duplicating storage, or expand storage capacity without provisioning additional compute resources. This architecture also enables features like instant branching (creating a copy of your database for development or testing) and point-in-time recovery without the overhead of full database copies.

Distributed database architecture

Another key concept is the consistency model. Cloud-native databases must choose between strong consistency (where every read reflects the most recent write) and eventual consistency (where reads may temporarily return stale data). CockroachDB provides serializable isolation by default, ensuring strong consistency across all nodes. PlanetScale and Neon offer standard SQL isolation levels with their own trade-offs between consistency and performance characteristics that developers must understand.

The consensus mechanism is the backbone of distributed databases. CockroachDB uses the Raft consensus algorithm to ensure that a majority of replicas agree on each write before committing it. This approach provides fault tolerance—the system continues operating correctly even if some nodes fail—but introduces latency for writes that must cross geographic boundaries.

Architecture and Design Patterns

CockroachDB Architecture

CockroachDB is a distributed SQL database that implements a custom storage engine built on top of RocksDB (now Pebble). Data is automatically sharded into ranges of approximately 512 MB, and each range is replicated across three or more nodes using the Raft consensus protocol. The system uses a multi-version concurrency control (MVCC) approach to handle concurrent transactions without blocking.

The architecture consists of three layers: the SQL layer that parses and optimizes queries, the transaction layer that manages distributed transactions using a timestamp-based protocol, and the storage layer that persists data as key-value pairs. CockroachDB achieves serializable isolation by default through its transaction layer, which uses a combination of write intents and timestamp ordering to detect and resolve conflicts.

// CockroachDB connection with Prisma
import { PrismaClient } from '@prisma/client'
 
const prisma = new PrismaClient({
  datasources: {
    db: {
      url: process.env.COCKROACHDB_URL
    }
  }
})
 
// Automatic connection pooling and retry logic
async function createOrder(userId: string, items: OrderItem[]) {
  return prisma.$transaction(async (tx) => {
    const order = await tx.order.create({
      data: { userId, status: 'pending' }
    })
 
    for (const item of items) {
      await tx.orderItem.create({
        data: { orderId: order.id, ...item }
      })
    }
 
    return order
  })
}

PlanetScale Architecture

PlanetScale is built on top of Vitess, the database clustering system originally developed at YouTube to scale MySQL. Vitess provides horizontal sharding, connection pooling, and query routing without requiring application changes. PlanetScale adds a developer-friendly layer on top of Vitess with branching, deploy requests, and automatic schema migrations that avoid table locks.

The core innovation of PlanetScale is its branching model, borrowed from Git workflows. Developers create a branch of their database schema, make changes in isolation, and then open a deploy request to merge those changes into production. PlanetScale automatically generates a non-blocking schema migration plan that avoids table locks and replication lag during deployment.

// PlanetScale with Drizzle ORM
import { drizzle } from 'drizzle-orm/planetscale-serverless'
import { connect } from '@planetscale/database'
 
const connection = connect({
  url: process.env.DATABASE_URL
})
 
const db = drizzle(connection)
 
// Schema changes are managed through PlanetScale branching
async function getUserWithPosts(userId: string) {
  return db.query.users.findFirst({
    where: (users, { eq }) => eq(users.id, userId),
    with: {
      posts: {
        where: (posts, { eq }) => eq(posts.published, true),
        orderBy: (posts, { desc }) => [desc(posts.createdAt)]
      }
    }
  })
}

Neon Architecture

Neon reimagines PostgreSQL with a separation of storage and compute. The storage layer uses a custom distributed storage engine that stores data in pages across multiple storage nodes. The compute layer runs standard PostgreSQL instances that connect to the storage layer over the network. This architecture enables features like instant database branching, autoscaling, and scale-to-zero when idle.

Neon's branching is particularly powerful for development workflows. Creating a branch copies the entire database instantly because branches share the same underlying storage pages using copy-on-write semantics. This means you can create a full copy of a production database for testing in seconds, regardless of database size, without incurring additional storage costs until the branches diverge.

// Neon serverless driver
import { neon } from '@neondatabase/serverless'
 
const sql = neon(process.env.DATABASE_URL!)
 
// Connection pooling via Neon's built-in pooler
async function searchProducts(query: string) {
  return sql`
    SELECT id, name, description, price,
           ts_rank(search_vector, plainto_tsquery(${query})) as rank
    FROM products
    WHERE search_vector @@ plainto_tsquery(${query})
    ORDER BY rank DESC
    LIMIT 20
  `
}

Database branching workflow

Step-by-Step Implementation

Setting Up CockroachDB

CockroachDB offers both a managed service (CockroachDB Serverless) and self-hosted options. For production applications, the serverless tier provides automatic scaling with a generous free tier of 10 GiB storage and 50 million request units per month.

// cockroachdb-setup.ts
import { Pool } from 'pg'
 
// CockroachDB is PostgreSQL wire-compatible
const pool = new Pool({
  connectionString: process.env.COCKROACHDB_URL,
  ssl: { rejectUnauthorized: false }
})
 
// Create distributed tables with zone configurations
async function setupDatabase() {
  await pool.query(`
    CREATE TABLE IF NOT EXISTS users (
      id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
      email STRING UNIQUE NOT NULL,
      name STRING NOT NULL,
      region STRING NOT NULL,
      created_at TIMESTAMPTZ DEFAULT now()
    )
  `)
 
  // Configure multi-region replication
  await pool.query(`
    ALTER TABLE users SET LOCALITY REGIONAL BY ROW
  `)
}

Setting Up PlanetScale

PlanetScale's workflow revolves around branches and deploy requests. You create a development branch, apply schema changes, and merge them back to main through a review process.

// planetscale-schema.ts (Drizzle schema)
import { mysqlTable, varchar, timestamp, int } from 'drizzle-orm/mysql-core'
 
export const products = mysqlTable('products', {
  id: varchar('id', { length: 36 }).primaryKey(),
  name: varchar('name', { length: 255 }).notNull(),
  price: int('price').notNull(),
  category: varchar('category', { length: 100 }),
  createdAt: timestamp('created_at').defaultNow()
})
 
// PlanetScale branching workflow:
// 1. pscale branch create my-db add-categories
// 2. Apply schema changes via Drizzle push
// 3. pscale deploy-request create my-db add-categories
// 4. Review and deploy

Setting Up Neon

Neon provides instant database provisioning with branching support built into the platform. The serverless driver works in edge runtimes and serverless functions without connection pooling issues.

// neon-setup.ts with Prisma
import { PrismaClient } from '@prisma/client'
 
// Neon provides separate pooled and direct connection strings
const prisma = new PrismaClient({
  datasources: {
    db: {
      url: process.env.NEON_POOLED_URL // For serverless environments
    }
  }
})
 
// Use direct connection for migrations
const prismaDirect = new PrismaClient({
  datasources: {
    db: {
      url: process.env.NEON_DIRECT_URL // Bypasses connection pooler
    }
  }
})
 
// Branch creation via API
async function createDevBranch() {
  const response = await fetch(`https://console.neon.tech/api/v2/projects/${projectId}/branches`, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${NEON_API_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      branch: { name: 'dev-feature-x', parent_id: mainBranchId }
    })
  })
  return response.json()
}

Real-World Use Cases

Global E-Commerce Platform

An e-commerce platform serving customers across North America, Europe, and Asia needs strong consistency for inventory management and order processing. CockroachDB's multi-region capabilities with regional-by-row locality ensure that users interact with data stored in their region while maintaining global consistency for inventory counts. This reduces read latency from 200ms+ (cross-region) to under 10ms (same-region) while preventing overselling through serializable transactions.

SaaS Application with Rapid Iteration

A SaaS startup shipping features multiple times per day needs a database workflow that supports rapid schema evolution without downtime. PlanetScale's branching model allows developers to experiment with schema changes in isolated branches, run integration tests against branch databases, and deploy changes through a review process. The non-blocking DDL migrations mean that adding a column to a table with millions of rows happens transparently without locking the table.

Development-Heavy Team

A team with many developers working on feature branches needs instant, isolated database copies for each branch. Neon's copy-on-write branching creates full database copies in seconds, allowing each developer to have their own database instance without provisioning overhead or data duplication costs. This eliminates the "works on my machine" problem for database-related issues.

Real-Time Analytics

A fintech application processing millions of transactions daily needs both OLTP and OLAP capabilities. CockroachDB's changefeed feature enables real-time data streaming to analytics systems, while Neon's autoscaling handles variable query loads from ad-hoc analytical queries that might spike during business hours and drop to zero overnight.

Best Practices for Production

  1. Design for locality: Place data close to where it's accessed most frequently. CockroachDB's regional-by-row and regional-by-table locality options reduce cross-region latency significantly. Map your users to regions and configure data placement accordingly.

  2. Use connection pooling: All three databases benefit from connection pooling, especially in serverless environments. PlanetScale provides built-in connection pooling through Vitess, Neon offers a pooled connection string, and CockroachDB works with PgBouncer. Without pooling, serverless functions can exhaust connection limits quickly.

  3. Implement retry logic: Distributed databases may return retriable errors due to transaction conflicts or temporary unavailability. Implement exponential backoff with jitter in your application layer. CockroachDB provides built-in transaction retry logic in its client libraries.

  4. Monitor replication lag: In multi-region deployments, monitor replication lag to ensure consistency guarantees are being met. All three platforms provide metrics endpoints for monitoring. Set alerts when lag exceeds your application's tolerance.

  5. Test with production-like data: Use database branching (available in PlanetScale and Neon) to create realistic test environments. Run integration tests against branches that mirror production schema and data volume to catch issues early.

  6. Optimize for your access patterns: Design indexes based on your query patterns. Use EXPLAIN ANALYZE to identify slow queries and create covering indexes to avoid table lookups. Consider partial indexes for queries that filter on common predicates.

  7. Plan for schema migrations: Use non-blocking schema migration tools. PlanetScale handles this automatically; for CockroachDB and Neon, use tools like pgroll or carefully designed migration scripts that avoid long-running locks.

  8. Implement proper monitoring: Set up alerts for connection pool exhaustion, query latency spikes, storage usage, and replication health. Use the platform's native observability tools alongside your APM solution for comprehensive visibility.

Common Pitfalls and Solutions

PitfallImpactSolution
Cross-region write contentionHigh latency, transaction abortsUse regional-by-row locality to co-locate writes with users
Connection pool exhaustion in serverlessConnection errors, cold startsUse platform-specific serverless drivers (Neon, PlanetScale)
Large schema migrations on PlanetScaleMigration failures, long deploy timesBreak migrations into smaller deploy requests
Ignoring CockroachDB's serializable isolationUnexpected transaction conflictsDesign queries to minimize contention, use SELECT FOR UPDATE carefully
Not using Neon's branching for developmentSlow dev cycles, environment driftCreate branches for each feature, automate branch provisioning
Over-relying on JOINs across sharded dataPoor query performanceDenormalize for read-heavy patterns, use materialized views
Storing large BLOBs in the databaseStorage costs, replication overheadUse object storage (S3, R2) for files, store references in the database
Not handling retriable errorsApplication crashes, data lossImplement retry logic with exponential backoff
Using direct connections in serverlessConnection exhaustionUse pooled connection strings provided by the platform

Performance Optimization

Performance tuning for cloud-native databases requires understanding the distributed nature of the system. The most impactful optimizations involve minimizing network round trips, reducing data transfer, and leveraging the platform's caching mechanisms.

// Batch operations to reduce round trips
async function bulkInsertUsers(users: UserInsert[]) {
  // Instead of individual inserts, batch them
  const values = users.map(u => `('${u.email}', '${u.name}', '${u.region}')`).join(',')
  await pool.query(`
    INSERT INTO users (email, name, region) VALUES ${values}
    ON CONFLICT (email) DO NOTHING
  `)
}
 
// Use prepared statements for repeated queries
async function getUserById(userId: string) {
  const client = await pool.connect()
  try {
    const result = await client.query({
      text: 'SELECT * FROM users WHERE id = $1',
      values: [userId]
    })
    return result.rows[0]
  } finally {
    client.release()
  }
}

For CockroachDB, enabling follower reads can significantly reduce read latency for non-critical reads by reading from the nearest replica rather than the leaseholder. PlanetScale benefits from its Vitess query planner, which automatically routes queries to the appropriate shard. Neon's autoscaling adjusts compute resources based on query load, but you should configure appropriate min/max bounds to balance cost and performance predictability.

Comparison with Alternatives

FeatureCockroachDBPlanetScaleNeon
SQL CompatibilityPostgreSQL-compatibleMySQL-compatiblePostgreSQL
Consistency ModelSerializable by defaultRead committedRead committed
Multi-RegionNative multi-regionSingle-region primarySingle-region primary
BranchingNo native branchingGit-like branchingInstant branching
AutoscalingManual scalingPer-plan limitsAutoscaling with scale-to-zero
Connection PoolingExternal (PgBouncer)Built-in (Vitess)Built-in pooler
Free Tier10 GiB storage5 GB storage, 1 billion reads512 MB storage
Pricing ModelRequest Units + storageRows read/writtenCompute hours + storage
Schema MigrationsManual with cautionAutomated non-blockingStandard PostgreSQL migrations

Beyond these three, alternatives include TiDB (open-source distributed SQL compatible with MySQL), YugabyteDB (PostgreSQL-compatible distributed database), Google Cloud Spanner (strongly consistent globally distributed database), and Amazon Aurora (MySQL/PostgreSQL-compatible with separated storage and automatic replication).

Advanced Patterns

Multi-Region Data Residency with CockroachDB

For applications with data residency requirements, CockroachDB's regional-by-row locality ensures that user data stays in the required geographic region while still being part of a globally consistent database.

// Create a table with regional locality
await pool.query(`
  CREATE TABLE user_data (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID NOT NULL,
    data JSONB NOT NULL,
    region crdb_internal_region NOT NULL DEFAULT 'us-east1'
  ) LOCALITY REGIONAL BY ROW
`)
 
// CockroachDB automatically routes queries to the correct region
// based on the gateway region of the connection

Zero-Downtime Schema Changes with PlanetScale

PlanetScale's deploy requests automatically generate non-blocking migration plans, even for complex schema changes like adding NOT NULL columns or changing column types on tables with millions of rows.

-- This ALTER TABLE won't lock the table on PlanetScale
-- Vitess handles it with online DDL behind the scenes
ALTER TABLE orders ADD COLUMN shipping_address JSON;
ALTER TABLE orders MODIFY COLUMN status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled');

Branch-Based CI/CD with Neon

Automate database provisioning in your CI pipeline by creating ephemeral branches for each pull request.

// ci-setup.ts - Create branch for CI testing
async function setupCIBranch(prNumber: string) {
  const branch = await neonClient.createBranch({
    name: `ci-pr-${prNumber}`,
    parent_id: mainBranchId
  })
 
  // Run migrations against the branch
  await runMigrations(branch.connection_string)
 
  // Run tests
  await runTests(branch.connection_string)
 
  // Clean up
  await neonClient.deleteBranch(branch.id)
}

Testing Strategies

Testing cloud-native databases requires a strategy that accounts for distributed behavior, network partitions, and eventual consistency. Use the platform's branching features to create isolated test environments that mirror production.

// Integration test with Neon branching
describe('OrderService', () => {
  let testBranch: NeonBranch
 
  beforeAll(async () => {
    testBranch = await neonClient.createBranch({
      name: `test-${Date.now()}`,
      parent_id: mainBranchId
    })
    await seedTestData(testBranch.connection_string)
  })
 
  afterAll(async () => {
    await neonClient.deleteBranch(testBranch.id)
  })
 
  test('should create order with items', async () => {
    const order = await orderService.create({
      userId: 'test-user',
      items: [{ productId: 'p1', quantity: 2 }]
    })
    expect(order.status).toBe('pending')
    expect(order.items).toHaveLength(1)
  })
})

For CockroachDB, test transaction conflict handling by simulating concurrent writes to the same key range. For PlanetScale, test schema migrations against branch databases before deploying to production to validate that non-blocking DDL plans are generated correctly.

Future Outlook

The cloud-native database space is evolving rapidly. CockroachDB is expanding its serverless offering with improved cost predictability and lower cold-start latency. PlanetScale is investing in additional database features like foreign key support and improved analytics capabilities. Neon continues to push the boundaries of serverless PostgreSQL with features like logical replication support and improved branching workflows.

The convergence of OLTP and OLAP workloads (HTAP) is a major trend, with all three platforms exploring ways to handle analytical queries alongside transactional workloads. Edge computing is another frontier, with Neon and PlanetScale exploring edge-replicated read replicas for ultra-low-latency reads.

The rise of AI applications is also driving new requirements. Vector search capabilities, efficient handling of embeddings, and integration with AI frameworks are becoming table stakes for modern databases. All three platforms are actively working on vector search extensions and optimizations.

Conclusion

CockroachDB, PlanetScale, and Neon each represent a different philosophy in cloud-native database design. CockroachDB prioritizes global consistency and multi-region capabilities, making it ideal for applications with strict data integrity requirements. PlanetScale brings Git-like workflows to database management, excelling in teams that value rapid iteration and safe schema evolution. Neon reimagines PostgreSQL with serverless architecture and instant branching, perfect for development-heavy teams and applications with variable workloads.

Key takeaways for choosing a cloud-native database:

  1. Choose CockroachDB when you need multi-region strong consistency, global data distribution, and can tolerate higher operational complexity.
  2. Choose PlanetScale when you want MySQL compatibility, zero-downtime schema migrations, and a Git-like workflow for database changes.
  3. Choose Neon when you need PostgreSQL compatibility, serverless scaling, instant branching, and a generous free tier for development.

Next steps: Start with the free tier of each platform, build a proof-of-concept with your actual schema and query patterns, and benchmark with realistic data volumes. The right choice depends on your specific workload characteristics, team expertise, and operational requirements. Refer to the official documentation of CockroachDB, PlanetScale, and Neon for detailed guides and best practices.