Skip to main content

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:
MetricValueUse When
CPU UtilizationRDSReaderAverageCPUUtilizationRead workloads are CPU-bound (default)
Database ConnectionsRDSReaderAverageDatabaseConnectionsYou have many short-lived connections or connection pooling pressure
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
}

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

VariableTypeDefaultDescription
autoscaling_enabledboolfalseEnable autoscaling for read replicas
autoscaling_min_capacitynumber0Minimum number of read replicas
autoscaling_max_capacitynumber2Maximum number of read replicas
predefined_metric_typestringRDSReaderAverageCPUUtilizationMetric to scale on (RDSReaderAverageCPUUtilization or RDSReaderAverageDatabaseConnections)
autoscaling_target_cpunumber70CPU % target when using CPU metric
autoscaling_target_connectionsnumber700Connection count target when using connections metric
autoscaling_scale_in_cooldownnumber300Seconds to wait after a scale-in before allowing another
autoscaling_scale_out_cooldownnumber300Seconds to wait after a scale-out before allowing another
autoscaling_policy_namestringtarget-metricName of the App Auto Scaling policy

Build docs developers (and LLMs) love