Overview
Aurora read replica autoscaling allows your cluster to dynamically add and remove reader instances in response to changes in workload. When demand increases, Aurora provisions additional read replicas. When demand drops, excess replicas are terminated — helping you balance performance and cost automatically.
This module integrates with AWS Application Auto Scaling using a target tracking policy. You define a target metric value (CPU utilization or connection count) and Aurora adjusts the reader count to maintain that target.
How It Works
When autoscaling_enabled = true, the module creates two resources:
aws_appautoscaling_target — registers the Aurora cluster’s ReadReplicaCount as a scalable dimension within the rds service namespace.
aws_appautoscaling_policy — attaches a TargetTrackingScaling policy that continuously measures the chosen metric and scales in/out to maintain the target value.
Cooldown periods prevent rapid oscillation: autoscaling_scale_in_cooldown guards against scaling in too aggressively, and autoscaling_scale_out_cooldown prevents scaling out before a new replica is fully warmed up.
Autoscaling only manages reader instances. At least one writer instance must already exist in the cluster — autoscaling will not create one for you.
Scaling Metric Options
Two predefined metric types are available via the predefined_metric_type variable:
| Metric | Value | Use When |
|---|
| CPU Utilization | RDSReaderAverageCPUUtilization | Read workloads are CPU-bound (default) |
| Database Connections | RDSReaderAverageDatabaseConnections | You have many short-lived connections or connection pooling pressure |
CPU-Based Scaling
Connection-Based Scaling
Scale based on average CPU utilization across all reader instances. The default target is 70%.module "aurora" {
source = "terraform-aws-modules/rds-aurora/aws"
name = "my-aurora-cluster"
engine = "aurora-postgresql"
engine_version = "17.5"
cluster_instance_class = "db.r8g.large"
instances = { 1 = {} }
master_username = "root"
vpc_id = module.vpc.vpc_id
db_subnet_group_name = module.vpc.database_subnet_group_name
autoscaling_enabled = true
autoscaling_min_capacity = 1
autoscaling_max_capacity = 5
# Defaults — shown here for clarity
predefined_metric_type = "RDSReaderAverageCPUUtilization"
autoscaling_target_cpu = 70
autoscaling_scale_in_cooldown = 300
autoscaling_scale_out_cooldown = 300
}
Scale based on the average number of database connections per reader. The default target is 700 connections, which is approximately 70% of the default max_connections for db.r4/r5/r6g.large instances.module "aurora" {
source = "terraform-aws-modules/rds-aurora/aws"
name = "my-aurora-cluster"
engine = "aurora-postgresql"
engine_version = "17.5"
cluster_instance_class = "db.r8g.large"
instances = { 1 = {} }
master_username = "root"
vpc_id = module.vpc.vpc_id
db_subnet_group_name = module.vpc.database_subnet_group_name
autoscaling_enabled = true
autoscaling_min_capacity = 1
autoscaling_max_capacity = 5
predefined_metric_type = "RDSReaderAverageDatabaseConnections"
autoscaling_target_connections = 700
autoscaling_scale_in_cooldown = 300
autoscaling_scale_out_cooldown = 300
}
Working Example
The following example is taken directly from examples/autoscaling/. It provisions a PostgreSQL Aurora cluster with one writer instance, enhanced monitoring, and autoscaling configured to maintain between 1 and 5 reader replicas.
provider "aws" {
region = local.region
}
data "aws_availability_zones" "available" {
# Exclude local zones
filter {
name = "opt-in-status"
values = ["opt-in-not-required"]
}
}
locals {
name = "ex-autoscaling"
region = "eu-west-1"
vpc_cidr = "10.0.0.0/16"
azs = slice(data.aws_availability_zones.available.names, 0, 3)
tags = {
Example = local.name
GithubRepo = "terraform-aws-rds-aurora"
GithubOrg = "terraform-aws-modules"
}
}
module "aurora" {
source = "terraform-aws-modules/rds-aurora/aws"
name = local.name
engine = "aurora-postgresql"
engine_version = "17.5"
cluster_instance_class = "db.r8g.large"
instances = { 1 = {} }
master_username = "root"
vpc_id = module.vpc.vpc_id
db_subnet_group_name = module.vpc.database_subnet_group_name
security_group_ingress_rules = {
private-az1 = {
cidr_ipv4 = element(module.vpc.private_subnets_cidr_blocks, 0)
}
private-az2 = {
cidr_ipv4 = element(module.vpc.private_subnets_cidr_blocks, 1)
}
private-az3 = {
cidr_ipv4 = element(module.vpc.private_subnets_cidr_blocks, 2)
}
}
autoscaling_enabled = true
autoscaling_min_capacity = 1
autoscaling_max_capacity = 5
cluster_monitoring_interval = 60
iam_role_name = "${local.name}-monitor"
iam_role_use_name_prefix = true
iam_role_description = "${local.name} RDS enhanced monitoring IAM role"
iam_role_path = "/autoscaling/"
iam_role_max_session_duration = 7200
apply_immediately = true
skip_final_snapshot = true
enabled_cloudwatch_logs_exports = ["postgresql"]
tags = local.tags
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 6.0"
name = local.name
cidr = local.vpc_cidr
azs = local.azs
public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k)]
private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 3)]
database_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 6)]
tags = local.tags
}
Instance Configuration Patterns
Homogeneous Cluster with Autoscaling
All instances use the same class defined by cluster_instance_class. Autoscaling-managed replicas also use this class. You can pre-provision additional readers alongside autoscaled ones:
cluster_instance_class = "db.r8g.large"
instances = {
one = {}
two = {}
three = {}
}
autoscaling_enabled = true
autoscaling_min_capacity = 2
autoscaling_max_capacity = 5
This creates at least 4 readers (2 static + 2 from autoscaling) and at most 7 (2 static + 5 from autoscaling).
Homogeneous Cluster Scaled Entirely via Autoscaling
For a leaner setup, define only the writer instance and let autoscaling manage all readers:
cluster_instance_class = "db.r8g.large"
instances = {
one = {}
}
autoscaling_enabled = true
autoscaling_min_capacity = 1
autoscaling_max_capacity = 5
This creates 1 writer and between 1–5 readers dynamically.
Heterogeneous Cluster with Autoscaling
For mixed workloads, you can statically provision custom reader instances and also enable autoscaling. Autoscaling-managed instances always use cluster_instance_class:
cluster_instance_class = "db.r8g.large"
instances = {
one = {
instance_class = "db.r8g.2xlarge"
publicly_accessible = true
}
two = {
identifier = "static-member-1"
instance_class = "db.r8g.2xlarge"
}
three = {
identifier = "excluded-member-1"
instance_class = "db.r8g.large"
promotion_tier = 15
}
}
autoscaling_enabled = true
autoscaling_min_capacity = 1
autoscaling_max_capacity = 5
This creates 1 writer plus at least 3 readers (2 static + 1 from autoscaling) and at most 7 readers (2 static + 5 from autoscaling).
Variable Reference
| Variable | Type | Default | Description |
|---|
autoscaling_enabled | bool | false | Enable autoscaling for read replicas |
autoscaling_min_capacity | number | 0 | Minimum number of read replicas |
autoscaling_max_capacity | number | 2 | Maximum number of read replicas |
predefined_metric_type | string | RDSReaderAverageCPUUtilization | Metric to scale on (RDSReaderAverageCPUUtilization or RDSReaderAverageDatabaseConnections) |
autoscaling_target_cpu | number | 70 | CPU % target when using CPU metric |
autoscaling_target_connections | number | 700 | Connection count target when using connections metric |
autoscaling_scale_in_cooldown | number | 300 | Seconds to wait after a scale-in before allowing another |
autoscaling_scale_out_cooldown | number | 300 | Seconds to wait after a scale-out before allowing another |
autoscaling_policy_name | string | target-metric | Name of the App Auto Scaling policy |