Skip to content

Scaling relational databases

There are four ways to add capacity to a managed relational database on AWS, plus one way to stop needing to. Each addresses a different bottleneck, and picking the wrong one is how teams end up paying for a large instance that is idle on CPU and starved on I/O.

Read-only copies of the database that serve queries the primary would otherwise handle. They can be created in another Availability Zone or another Region, and they can be promoted to standalone databases.

Best for read-heavy applications with moderate write volume, and for placing read capacity close to users in another Region.

The catch is that replication is asynchronous, so replicas lag; the application has to be written to know which queries can tolerate a stale read and route them accordingly. Cost rises linearly with each replica, and none of it helps write throughput.

Limits: up to 15 read replicas per DB instance for MySQL, MariaDB, PostgreSQL and SQL Server; 5 for Oracle; 15 Aurora replicas per Aurora cluster.

Allocated storage can be increased, with no downtime on most engines, and storage autoscaling will do it automatically as free space falls.

Best for databases that are running out of space rather than out of compute.

Storage on RDS can only grow — it cannot be reduced, so an over-provisioned volume is a permanent cost until the database is rebuilt. It also does nothing for CPU or memory, and on gp2 volumes it is entangled with IOPS in a way that has surprised many people: IOPS scale with volume size, so a small volume can be I/O-limited long before it is full. Aurora sidesteps this by growing and shrinking its cluster volume automatically.

Change the DB instance class for more CPU, memory and network.

Best when the workload simply needs more of everything, and the simplest option to reason about because nothing in the application changes.

On non-Aurora engines the change requires a restart, so it means downtime — usually minutes, and usually taken in a maintenance window, or made near-transparent by performing it on a Multi-AZ standby and failing over. Cost grows steeply at the large instance classes, and there is eventually a largest instance.

Hand capacity management to AWS: set a range in Aurora capacity units and let the cluster scale within it, per second, in place.

Best for unpredictable, spiky or intermittent workloads, and for development and test databases, which can be configured to scale to zero and cost almost nothing when idle.

It is not the cheapest option for a steady, well-understood workload — a provisioned instance with a reserved instance against it usually is. Scaling up from a very low floor also takes longer than scaling up from a moderate one, so a database that must respond instantly to a spike should not sit at the minimum. See the Aurora Serverless page.

The option that removes the problem rather than sizing around it. DynamoDB scales horizontally without a ceiling and holds single-digit millisecond latency at any table size, and global tables give multi-Region deployment without the write-conflict problem that defeats active-active relational designs.

The cost is a rewrite. Query flexibility is far narrower — the access patterns must be known and designed for up front — and the data modelling is unfamiliar enough that the first attempt is usually wrong. It pays off for workloads with well-understood access patterns and a scale that relational databases genuinely struggle with; it is a poor trade for a reporting database or one whose queries change often.

OptionCostHelps withMain limitation
Read replicasLinear per replicaRead throughput, regional read latencyNothing for writes; replication lag the application must handle
Storage scalingLowCapacity, and IOPS on size-coupled volume typesCannot be reduced; nothing for compute
Vertical scalingHigh at the top endReads and writes togetherDowntime on non-Aurora; a hard ceiling
Aurora ServerlessVariableUnpredictable and intermittent loadSlower to climb from a very low floor; not cheapest when steady
Refactoring to DynamoDBVariable, often lowest at scaleEverything, eventuallyRequires an application rewrite and a different data model

Related: Multi-AZ is not on this list because it is not a scaling mechanism. The standby serves no reads and exists for availability — see Multi-AZ and RDS Proxy. RDS Proxy is not on it either, but is often the right first move: an application that is exhausting the connection limit does not need a bigger database.

Most real systems end up combining these — a right-sized instance, a replica or two for reporting, storage autoscaling, and a cache in front — rather than choosing one.