Databases at Scale
The database is the beating heart of most applications, and it is usually the first component to buckle under load. Scaling a database is harder than scaling stateless application servers because data has gravity: it is heavy to move, expensive to duplicate, and sensitive to consistency semantics.
This guide covers the techniques used to scale relational and non-relational databases: replication for availability and read throughput, sharding for write throughput, indexing for query speed, partitioning for manageability, and the CAP theorem tradeoffs that shape every distributed datastore.
By the end, you should be able to reason about when to add replicas, when to shard, when to denormalize, and when to trade strong consistency for availability.
Replication copies data from a primary node to one or more replica nodes. It improves availability — if the primary fails, a replica can be promoted — and read throughput — read queries can be distributed across replicas. The cost is complexity: replicas lag behind the primary, and failover must be handled carefully to avoid split-brain.
| 1 | Primary-Replica Replication: |
| 2 | |
| 3 | ┌─────────────┐ |
| 4 | │ Writes │ |
| 5 | └──────┬──────┘ |
| 6 | │ |
| 7 | ┌──────▼──────┐ |
| 8 | │ Primary │ |
| 9 | │ (read+write)│ |
| 10 | └──────┬──────┘ |
| 11 | │ replication stream |
| 12 | ┌───────┼───────┐ |
| 13 | │ │ │ |
| 14 | ┌──────▼───┐ ┌─▼─────┐ ┌─▼─────┐ |
| 15 | │ Replica 1│ │Repl. 2│ │Repl. 3│ |
| 16 | │ (reads) │ │(reads)│ │(reads)│ |
| 17 | └──────────┘ └───────┘ └───────┘ |
Synchronous vs Asynchronous Replication
In synchronous replication, the primary waits for at least one replica to acknowledge a write before committing it to the client. This guarantees durability but increases write latency. In asynchronous replication, the primary acknowledges immediately and replicates in the background. It is faster but risks data loss if the primary fails before replication completes.
| Mode | Durability | Latency | Use Case |
|---|---|---|---|
| Async | Possible data loss on failover | Low | Most web applications, analytics |
| Sync | No loss if acknowledged | Higher | Financial systems, inventory |
| Semi-sync | At least one replica has it | Moderate | Balanced workloads |
warning
Indexes are data structures that speed up reads at the cost of slower writes and additional storage. Without an index, a database scans every row to answer a query. With the right index, the same query touches only a handful of pages.
B-Trees
B-trees are the default index type in most relational databases. They keep data sorted and balanced, allowing logarithmic-time lookups, range scans, and ordered iteration. B-trees work well for equality and range queries on stable, ordered keys.
Hash Indexes
Hash indexes map keys directly to disk locations using a hash function. They provide O(1) equality lookups but cannot support range queries or ordering. They are common in key-value stores like Redis and DynamoDB partition keys.
| Index Type | Best For | Limitations |
|---|---|---|
| B-Tree | Range queries, sorting, equality | Write amplification on inserts |
| Hash | Exact key lookups | No range or prefix queries |
| Bitmap | Low-cardinality columns | Not suitable for high-cardinality data |
| GIN / GiST | Full-text search, JSON, arrays | Larger storage, slower writes |
| Inverted | Search engines, documents | Merge and update costs |
info
Partitioning splits a single logical table into smaller physical chunks within the same database. Unlike sharding, partitioning is usually transparent to the application. It improves query performance and maintenance — old partitions can be archived or dropped cheaply.
| 1 | Time-based partitioning for event logs: |
| 2 | |
| 3 | CREATE TABLE events ( |
| 4 | id BIGINT, |
| 5 | created_at TIMESTAMP, |
| 6 | payload JSONB |
| 7 | ) PARTITION BY RANGE (created_at); |
| 8 | |
| 9 | CREATE TABLE events_2026_01 PARTITION OF events |
| 10 | FOR VALUES FROM ('2026-01-01') TO ('2026-02-01'); |
| 11 | |
| 12 | CREATE TABLE events_2026_02 PARTITION OF events |
| 13 | FOR VALUES FROM ('2026-02-01') TO ('2026-03-01'); |
| 14 | |
| 15 | -- Query planner prunes partitions outside the range. |
| 16 | SELECT * FROM events WHERE created_at > '2026-01-15'; |
note
The CAP theorem states that a distributed data store cannot simultaneously provide Consistency, Availability, and Partition tolerance. In practice, network partitions are unavoidable, so the real choice is between consistency and availability during a partition.
| Property | Description | Example System |
|---|---|---|
| CP | Consistent and partition-tolerant; may refuse writes during partition | etcd, ZooKeeper, HBase |
| AP | Available and partition-tolerant; reads may be stale | Cassandra, DynamoDB, Riak |
| CA | Consistent and available, but not partition-tolerant | Single-node databases |
best practice
Read replicas are the lowest-friction way to scale read-heavy workloads. They are asynchronous copies of the primary database that accept read traffic. The pattern is simple: writes go to the primary; reads go to replicas, with critical reads optionally forced to the primary.
| 1 | Read routing logic: |
| 2 | |
| 3 | function executeQuery(query, requireStrongConsistency) { |
| 4 | if (query.isWrite()) { |
| 5 | return primary.execute(query); |
| 6 | } |
| 7 | if (requireStrongConsistency) { |
| 8 | return primary.execute(query); // fresh read |
| 9 | } |
| 10 | return loadBalancer.pick(replicas).execute(query); // stale read |
| 11 | } |
warning
There is no best database. The right choice depends on data shape, access patterns, consistency needs, and operational expertise. Modern architectures often use multiple databases — a pattern called polyglot persistence.
| Workload | Typical Choice | Why |
|---|---|---|
| Relational transactions | PostgreSQL, MySQL | ACID, joins, complex queries |
| High-write key-value | DynamoDB, ScyllaDB | Massive scale, predictable latency |
| Caching / sessions | Redis, KeyDB | Sub-millisecond latency, TTL |
| Full-text search | Elasticsearch, OpenSearch | Inverted indexes, relevance scoring |
| Time-series | InfluxDB, TimescaleDB | Time-based compression, retention |
| Graph data | Neo4j, Amazon Neptune | Relationship traversal |
pro tip