Loading learning content...
For decades, database scaling faced an apparent dilemma: choose SQL with ACID guarantees but accept scaling limits, or choose NoSQL with horizontal scalability but sacrifice relational features. This trade-off shaped how entire generations of engineers thought about data architecture.
NewSQL databases challenge this dichotomy. They promise the familiar SQL interface and ACID transactions of traditional relational databases while delivering the horizontal scalability previously exclusive to NoSQL systems. Born from advances in distributed systems research, these databases represent a fundamental rethinking of how scalable data systems can work.
But promises are easy—understanding whether NewSQL fits your needs requires examining the engineering reality beneath the marketing.
By the end of this page, you will understand what makes NewSQL databases architecturally different, the major NewSQL options and their characteristics, how to evaluate whether NewSQL is right for your workload, migration paths from traditional SQL databases, and the trade-offs and limitations of NewSQL systems.
NewSQL databases share several architectural principles that distinguish them from both traditional SQL and NoSQL systems.
Traditional SQL databases handle transactions on a single node. Scaling requires application-level sharding with sagas or 2PC for cross-shard consistency.
NewSQL databases implement distributed transactions natively. The database itself coordinates multi-node transactions, abstracting this complexity from applications. Techniques like:
Unlike application-level sharding where you manage shard keys and routing, NewSQL databases shard automatically. You define tables; the database:
NewSQL databases support standard SQL (often PostgreSQL or MySQL wire protocol compatible) plus extensions for:
| Characteristic | Traditional SQL | NoSQL | NewSQL |
|---|---|---|---|
| Data Model | Relational (tables, SQL) | Varied (document, key-value, etc.) | Relational (tables, SQL) |
| Transactions | ACID (single node) | Usually none or limited | Distributed ACID |
| Scaling | Vertical + manual sharding | Horizontal (native) | Horizontal (automatic) |
| Consistency | Strong (single node) | Eventually consistent (often) | Strong or tunable |
| Query Language | SQL | Varied (often none) | SQL |
| Schema | Fixed schema | Flexible/schemaless | Fixed schema |
| Operational Complexity | Lower (single node) | Variable | Higher (distributed ops) |
NewSQL doesn't violate the CAP theorem. Distributed ACID transactions have real costs: higher write latency, complex failure modes, and operational sophistication requirements. NewSQL offers different trade-offs, not the elimination of trade-offs.
Several technical innovations enable NewSQL capabilities:
Most NewSQL databases use the Raft consensus protocol (or variants like Multi-Raft) for replication:
This provides strong consistency within each range without a single global coordinator bottleneck.
Ordering transactions across a distributed system requires some notion of time. Options:
Physical clocks (GPS, atomic): Google Spanner uses TrueTime with GPS/atomic clocks. Expensive but provides true global ordering.
Logical clocks: Lamport clocks provide ordering but not wall-clock correlation.
Hybrid Logical Clocks: Combine physical time (when available) with logical counters. Used by CockroachDB, YugabyteDB. Provides good-enough ordering without specialized hardware.
NewSQL databases use MVCC to allow concurrent reads without blocking writes:
Distributed MVCC extends this across nodes, with timestamp coordination ensuring consistent snapshots across the cluster.
1234567891011121314151617181920212223242526272829303132333435363738394041424344
Distributed MVCC Transaction Flow═══════════════════════════════════════════════════════════════ Transaction: Transfer $100 from Account A (Node 1) to Account B (Node 2) 1. BEGIN TRANSACTION └─ Coordinator assigns timestamp T1 using HLC 2. READ account_a WHERE id = 'A' └─ Node 1 returns latest version where version.ts < T1 └─ Acquired read lock at T1 3. READ account_b WHERE id = 'B' └─ Node 2 returns latest version where version.ts < T1 └─ Acquired read lock at T1 4. WRITE account_a SET balance = balance - 100 └─ Node 1 creates new version at T1 (not yet visible) └─ Acquired write intent lock 5. WRITE account_b SET balance = balance + 100 └─ Node 2 creates new version at T1 (not yet visible) └─ Acquired write intent lock 6. PREPARE COMMIT └─ Coordinator asks both nodes to prepare └─ Both confirm write intents are stable in Raft log 7. COMMIT at T1 └─ Coordinator writes commit record to Raft └─ Both nodes mark versions at T1 as committed └─ Versions now visible to transactions with ts > T1 8. CLEANUP └─ Read locks released └─ Write intents converted to committed versions Conflict Detection:───────────────────If concurrent transaction T2 tries to write Account A:- T2 sees T1's write intent during its write attempt- T2 either waits (if T1 might commit) or pushes T1's timestamp- Serializability maintained via timestamp + lock-based conflict detectionAs data grows, NewSQL databases automatically manage partition sizes:
This eliminates the manual resharding operations required with application-level sharding.
Several NewSQL databases have achieved production maturity. Each has distinct characteristics and optimal use cases.
The original inspiration for modern NewSQL. Key features:
Trade-offs: Expensive, vendor lock-in to Google Cloud, requires careful schema design for performance.
Best for: Global applications needing strong consistency, organizations already on GCP, financial/regulated workloads.
Open-source, Spanner-inspired database:
Trade-offs: Higher latency than single-node PostgreSQL, complex operational model, requires distributed systems expertise.
Best for: Applications needing horizontal scale with PostgreSQL compatibility, multi-region deployments without Spanner lock-in.
| Database | Protocol | Consistency | Open Source | Cloud Managed |
|---|---|---|---|---|
| Google Spanner | gRPC/Spanner | External (TrueTime) | No | Yes (GCP only) |
| CockroachDB | PostgreSQL | Serializable | Yes (BSL) | Yes (multi-cloud) |
| TiDB | MySQL | Snapshot isolation | Yes (Apache 2.0) | Yes (TiDB Cloud) |
| YugabyteDB | PostgreSQL/Cassandra | Serializable | Yes (Apache 2.0) | Yes (multi-cloud) |
| PlanetScale | MySQL | Serializable (Vitess) | No (Vitess is) | Yes |
| CockroachDB (Serverless) | PostgreSQL | Serializable | No | Yes |
Distributed SQL database with MySQL compatibility:
Trade-offs: Snapshot isolation (not serializable by default), complex multi-component architecture.
Best for: MySQL-based applications needing scale, hybrid transactional-analytical workloads.
Distributed SQL with PostgreSQL and Cassandra API compatibility:
Trade-offs: Younger ecosystem, some PostgreSQL compatibility gaps.
Best for: PostgreSQL applications needing scale, organizations wanting open-source with commercial support.
Being 'PostgreSQL compatible' or 'MySQL compatible' means supporting the wire protocol and most SQL syntax. It doesn't mean 100% compatibility. Stored procedures, extensions, specific functions, and advanced features may differ. Always test your specific application thoroughly.
NewSQL databases have different performance profiles than traditional SQL. Understanding these characteristics helps you evaluate fit:
Distributed transactions require consensus across nodes. A write that takes 1ms on PostgreSQL might take 5-20ms on a NewSQL database, depending on:
Impact: Applications sensitive to write latency may see measurable degradation. High-frequency trading, real-time gaming, and similar microsecond-sensitive workloads may not be suitable.
Reads can be faster than traditional sharded systems:
Caveat: Distributed query plans for complex JOINs across ranges may add latency compared to single-node execution.
NewSQL shines for horizontal scaling:
Limitation: Write scaling is limited by contention. If all transactions touch the same rows, adding nodes doesn't help—you're bottlenecked on coordination for those specific rows.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
/** * Performance comparison: PostgreSQL vs CockroachDB * * This benchmark illustrates typical latency differences. * Run on your actual workload for accurate comparisons. */ interface BenchmarkResult { operation: string; samples: number; p50Ms: number; p99Ms: number; throughput: number;} async function runLatencyBenchmark( db: DatabaseClient, iterations: number = 1000): Promise<BenchmarkResult[]> { const results: BenchmarkResult[] = []; // Point reads (by primary key) const readLatencies: number[] = []; for (let i = 0; i < iterations; i++) { const start = performance.now(); await db.query('SELECT * FROM users WHERE id = $1', [randomId()]); readLatencies.push(performance.now() - start); } results.push({ operation: 'Point Read', samples: iterations, p50Ms: percentile(readLatencies, 50), p99Ms: percentile(readLatencies, 99), throughput: iterations / (sum(readLatencies) / 1000), }); // Single-row writes const writeLatencies: number[] = []; for (let i = 0; i < iterations; i++) { const start = performance.now(); await db.query( 'INSERT INTO events (id, user_id, data) VALUES ($1, $2, $3)', [uuid(), randomId(), JSON.stringify({ test: true })] ); writeLatencies.push(performance.now() - start); } results.push({ operation: 'Single Insert', samples: iterations, p50Ms: percentile(writeLatencies, 50), p99Ms: percentile(writeLatencies, 99), throughput: iterations / (sum(writeLatencies) / 1000), }); // Multi-statement transaction const txnLatencies: number[] = []; for (let i = 0; i < iterations; i++) { const start = performance.now(); await db.transaction(async (tx) => { const user = await tx.query( 'SELECT * FROM users WHERE id = $1 FOR UPDATE', [randomId()] ); await tx.query( 'UPDATE users SET updated_at = NOW() WHERE id = $1', [user.rows[0].id] ); await tx.query( 'INSERT INTO audit_log (user_id, action) VALUES ($1, $2)', [user.rows[0].id, 'benchmark_update'] ); }); txnLatencies.push(performance.now() - start); } results.push({ operation: 'RMW Transaction', samples: iterations, p50Ms: percentile(txnLatencies, 50), p99Ms: percentile(txnLatencies, 99), throughput: iterations / (sum(txnLatencies) / 1000), }); return results;} /* * Example results (illustrative, not benchmarks): * * PostgreSQL (single node): * ┌──────────────────┬─────────┬─────────┬────────────┐ * │ Operation │ p50 ms │ p99 ms │ throughput │ * ├──────────────────┼─────────┼─────────┼────────────┤ * │ Point Read │ 0.5 │ 2.0 │ 5000/s │ * │ Single Insert │ 1.0 │ 5.0 │ 2000/s │ * │ RMW Transaction │ 2.0 │ 10.0 │ 800/s │ * └──────────────────┴─────────┴─────────┴────────────┘ * * CockroachDB (3-node, same region): * ┌──────────────────┬─────────┬─────────┬────────────┐ * │ Operation │ p50 ms │ p99 ms │ throughput │ * ├──────────────────┼─────────┼─────────┼────────────┤ * │ Point Read │ 1.0 │ 5.0 │ 3000/s │ * │ Single Insert │ 5.0 │ 20.0 │ 500/s │ * │ RMW Transaction │ 10.0 │ 50.0 │ 200/s │ * └──────────────────┴─────────┴─────────┴────────────┘ * * BUT: CockroachDB scales to 10x throughput by adding nodes. * PostgreSQL requires application-level sharding. */NewSQL isn't always the right choice. Use this framework to evaluate fit:
NewSQL databases typically cost more than traditional SQL:
Infrastructure: Minimum 3 nodes for fault tolerance (vs. 1 for PostgreSQL). Each node needs substantial resources.
Managed service pricing: CockroachDB Cloud, Spanner, TiDB Cloud charge premium prices for the distributed coordination.
Operational expertise: Running distributed databases requires skills in distributed systems, performance tuning, and complex failure scenarios.
Rough estimate: Expect 2-5× the cost of a comparable PostgreSQL deployment for small to medium workloads. The gap narrows at very large scale where manual sharding costs become significant.
Many teams adopt NewSQL before they need it, paying the complexity and cost tax years before deriving benefits. If your data fits on a single PostgreSQL instance with read replicas, that's probably the right architecture today. Migrate to NewSQL when you're 12-18 months from hitting single-node limits.
Migrating from traditional SQL to NewSQL requires careful planning:
Before migrating, audit your application for compatibility:
SQL syntax: Most standard SQL works, but check for:
Schema features:
Extensions:
NewSQL databases have different optimal schema patterns:
12345678910111213141516171819202122232425262728293031323334353637383940414243
-- CockroachDB schema design considerations -- 1. Use UUIDs for primary keys (distribute better than sequences)CREATE TABLE users ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), email STRING NOT NULL, name STRING, created_at TIMESTAMPTZ DEFAULT now()); -- 2. Avoid auto-increment IDs (create hotspots on a single range)-- BAD: SERIAL or IDENTITY columns-- GOOD: UUIDs or hashed IDs -- 3. Consider hash-sharded indexes for high-contention columnsCREATE TABLE events ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID, event_time TIMESTAMPTZ DEFAULT now(), data JSONB, -- Hash-sharded index prevents range hotspots INDEX events_by_time (event_time) USING HASH WITH (bucket_count = 16)); -- 4. Use REGIONAL BY ROW for geo-partitioningALTER DATABASE my_app SET PRIMARY REGION = 'us-east1';ALTER DATABASE my_app ADD REGION 'eu-west1';ALTER DATABASE my_app ADD REGION 'ap-southeast1'; CREATE TABLE user_data ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID, region crdb_internal_region NOT NULL, data JSONB) LOCALITY REGIONAL BY ROW; -- 5. Design transactions to be single-region when possible-- Cross-region transactions have high latency -- 6. Use follower reads for read-heavy, latency-tolerant queriesSET enable_follower_reads = on;SELECT * FROM users AS OF SYSTEM TIME follower_read_timestamp() WHERE id = $1;Big Bang Migration (high risk, faster):
Gradual Migration (lower risk, longer):
Shadow Mode (safest, resource-intensive):
Don't migrate your most critical production database first. Start with a secondary workload—internal tools, analytics, or a new feature. Build operational experience before migrating revenue-critical systems.
Operating NewSQL databases requires different skills and processes than traditional databases:
NewSQL performance tuning involves:
Distributed databases have more complex failure modes:
Unless you have strong platform engineering capabilities, consider managed NewSQL services. CockroachDB Cloud, TiDB Cloud, and Spanner eliminate much of the operational burden. The premium pricing often costs less than building equivalent internal expertise.
Let's consolidate the key insights from our exploration of NewSQL databases:
We've explored the complete landscape of SQL database scaling:
Vertical scaling — Exhaust single-machine capacity first. It's simple and often sufficient longer than expected.
Read replicas — Scale reads horizontally. Handle consistency trade-offs with intelligent routing.
Functional partitioning — Divide by domain to scale both reads and writes without sharding complexity.
Application-level sharding — The ultimate scaling lever, but with significant engineering cost.
NewSQL — Let the database handle distribution. Accept latency trade-offs for operational simplicity.
The right strategy depends on your specific workload, team capabilities, and growth trajectory. Most successful scaling journeys progress through this sequence incrementally, adding complexity only when simpler options are exhausted.
You've completed the SQL Database Scaling Patterns module! You now understand the full spectrum of techniques for scaling SQL databases—from vertical optimization through distributed NewSQL systems. These patterns form the foundation for architecting data-intensive applications that can grow from startup to global scale.