DEV Community

Cover image for Your Dev Database Has Multi-AZ Enabled (You're Paying Double for No Reason) πŸ’Έ
Suhas Mallesh
Suhas Mallesh

Posted on • Edited on

Your Dev Database Has Multi-AZ Enabled (You're Paying Double for No Reason) πŸ’Έ

RDS Multi-AZ doubles your database costs for high availability. Perfect for production, wasteful for dev/staging. Here's how to disable it with Terraform and save 50%.

Quick audit: Check your non-production RDS databases right now.

How many have Multi-AZ enabled?

If the answer is "all of them," you're literally paying double for databases that don't need high availability.

Single-AZ RDS (db.t3.medium):  $60/month
Multi-AZ RDS (db.t3.medium):   $120/month

For a dev database that:
- Can tolerate 5-10 minute downtime
- Gets wiped weekly anyway
- Nobody uses on weekends

Annual waste: $720 per database 😱
Enter fullscreen mode Exit fullscreen mode

Let me show you how to fix this with one line of Terraform.

πŸ’Έ The Multi-AZ Tax

What Multi-AZ gives you:

  • Automatic failover to standby instance (~60-120 seconds)
  • Synchronous replication to standby
  • No data loss during failure
  • 99.95% availability SLA

What it costs:

  • Exactly 2x the instance price (you pay for standby too)
  • Exactly 2x the storage (replicated)
  • 2x backup storage (from both instances)

Perfect for: Production databases serving customers

Wasteful for: Dev, staging, QA, demo, test environments

🎯 When to Keep Multi-AZ

Use Multi-AZ when:

  • βœ… Production database
  • βœ… Customer-facing application
  • βœ… Revenue-generating service
  • βœ… SLA requires < 2 min failover
  • βœ… Zero tolerance for data loss

Don't use Multi-AZ when:

  • ❌ Development environment
  • ❌ Staging/QA environment
  • ❌ Personal projects
  • ❌ Internal tools
  • ❌ Test databases
  • ❌ 5-10 min downtime is acceptable

πŸ› οΈ Terraform Implementation

Disable Multi-AZ (Single Change)

Before:

resource "aws_db_instance" "staging" {
  identifier     = "app-staging"
  engine         = "postgres"
  instance_class = "db.t3.medium"

  allocated_storage = 100
  storage_type      = "gp3"

  multi_az = true  # ← Costing you double!

  backup_retention_period = 7
  skip_final_snapshot     = true
}
Enter fullscreen mode Exit fullscreen mode

After:

resource "aws_db_instance" "staging" {
  identifier     = "app-staging"
  engine         = "postgres"
  instance_class = "db.t3.medium"

  allocated_storage = 100
  storage_type      = "gp3"

  multi_az = false  # ← Changed! Saves 50%

  backup_retention_period = 7
  skip_final_snapshot     = true
}
Enter fullscreen mode Exit fullscreen mode

Deploy:

terraform apply

# RDS will modify the instance (takes 5-10 minutes)
# Removes standby instance
# Immediate 50% cost reduction
Enter fullscreen mode Exit fullscreen mode

Smart Multi-AZ by Environment

# variables.tf
variable "environment" {
  description = "Environment name"
  type        = string
}

locals {
  # Multi-AZ only for production
  is_production = var.environment == "production"

  multi_az_config = {
    production = true
    staging    = false
    dev        = false
    qa         = false
  }
}

resource "aws_db_instance" "app" {
  identifier     = "app-${var.environment}"
  engine         = "postgres"
  instance_class = local.is_production ? "db.r6g.large" : "db.t3.medium"

  allocated_storage = 100
  storage_type      = "gp3"

  # Multi-AZ based on environment
  multi_az = local.multi_az_config[var.environment]

  # Production gets longer retention
  backup_retention_period = local.is_production ? 30 : 7

  # Production gets final snapshot
  skip_final_snapshot = !local.is_production

  # Production uses encrypted storage
  storage_encrypted = local.is_production

  tags = {
    Environment = var.environment
    MultiAZ     = local.multi_az_config[var.environment]
  }
}
Enter fullscreen mode Exit fullscreen mode

Production Module with Environment Logic

# modules/rds-instance/main.tf

variable "name" {
  description = "Database identifier"
  type        = string
}

variable "environment" {
  description = "Environment (production, staging, dev)"
  type        = string

  validation {
    condition     = contains(["production", "staging", "dev", "qa"], var.environment)
    error_message = "Environment must be production, staging, dev, or qa."
  }
}

variable "engine" {
  description = "Database engine"
  type        = string
  default     = "postgres"
}

variable "instance_class" {
  description = "Instance class"
  type        = string
}

variable "allocated_storage" {
  description = "Storage in GB"
  type        = number
  default     = 100
}

locals {
  is_production = var.environment == "production"

  # Environment-specific settings
  config = {
    multi_az                = local.is_production
    backup_retention_period = local.is_production ? 30 : 7
    skip_final_snapshot     = !local.is_production
    storage_encrypted       = local.is_production
    deletion_protection     = local.is_production
  }
}

resource "aws_db_instance" "this" {
  identifier     = "${var.name}-${var.environment}"
  engine         = var.engine
  instance_class = var.instance_class

  allocated_storage = var.allocated_storage
  storage_type      = "gp3"

  # Environment-based configuration
  multi_az                = local.config.multi_az
  backup_retention_period = local.config.backup_retention_period
  skip_final_snapshot     = local.config.skip_final_snapshot
  storage_encrypted       = local.config.storage_encrypted
  deletion_protection     = local.config.deletion_protection

  # Always use automated backups
  backup_window      = "03:00-04:00"
  maintenance_window = "sun:04:00-sun:05:00"

  tags = {
    Name        = "${var.name}-${var.environment}"
    Environment = var.environment
    MultiAZ     = local.config.multi_az
    ManagedBy   = "terraform"
  }
}

output "endpoint" {
  value = aws_db_instance.this.endpoint
}

output "multi_az_enabled" {
  value = aws_db_instance.this.multi_az
}
Enter fullscreen mode Exit fullscreen mode

Usage

# Production - Multi-AZ enabled
module "db_production" {
  source = "./modules/rds-instance"

  name           = "myapp"
  environment    = "production"
  instance_class = "db.r6g.large"
  allocated_storage = 500
}

# Staging - Single-AZ
module "db_staging" {
  source = "./modules/rds-instance"

  name           = "myapp"
  environment    = "staging"
  instance_class = "db.t3.medium"
  allocated_storage = 100
}

# Dev - Single-AZ
module "db_dev" {
  source = "./modules/rds-instance"

  name           = "myapp"
  environment    = "dev"
  instance_class = "db.t3.small"
  allocated_storage = 50
}

output "databases" {
  value = {
    production = {
      endpoint = module.db_production.endpoint
      multi_az = module.db_production.multi_az_enabled
    }
    staging = {
      endpoint = module.db_staging.endpoint
      multi_az = module.db_staging.multi_az_enabled
    }
    dev = {
      endpoint = module.db_dev.endpoint
      multi_az = module.db_dev.multi_az_enabled
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“Š Real-World Savings

Typical Startup (3 environments)

Before (all Multi-AZ):

Production: db.r6g.large Multi-AZ  = $350/month
Staging:    db.t3.large Multi-AZ   = $120/month
Dev:        db.t3.medium Multi-AZ  = $120/month

Total: $590/month
Annual: $7,080
Enter fullscreen mode Exit fullscreen mode

After (Production Multi-AZ only):

Production: db.r6g.large Multi-AZ  = $350/month  (kept)
Staging:    db.t3.large Single-AZ  = $60/month   (saved 50%)
Dev:        db.t3.medium Single-AZ = $60/month   (saved 50%)

Total: $470/month
Annual: $5,640

Savings: $120/month = $1,440/year (20% reduction!) πŸŽ‰
Enter fullscreen mode Exit fullscreen mode

Scale: 10 Non-Prod Databases

10 Γ— db.t3.medium Multi-AZ = $1,200/month

Switch to Single-AZ:
10 Γ— db.t3.medium Single-AZ = $600/month

Savings: $600/month = $7,200/year (50% reduction!) πŸ’°
Enter fullscreen mode Exit fullscreen mode

πŸ’‘ Pro Tips

1. Audit All Your Databases

# List all RDS instances with Multi-AZ status
aws rds describe-db-instances \
  --query 'DBInstances[*].[DBInstanceIdentifier,MultiAZ,DBInstanceClass]' \
  --output table

# Filter only Multi-AZ non-prod
aws rds describe-db-instances \
  --query 'DBInstances[?MultiAZ==`true`].[DBInstanceIdentifier]' \
  --output text | grep -E '(dev|staging|qa|test)'
Enter fullscreen mode Exit fullscreen mode

2. Use Snapshots for Recovery

Single-AZ doesn't mean no backups:

resource "aws_db_instance" "dev" {
  # ... other config ...

  multi_az = false  # Single-AZ

  # Still have automated backups!
  backup_retention_period = 7
  backup_window          = "03:00-04:00"

  # Can restore from snapshot if needed
}
Enter fullscreen mode Exit fullscreen mode

Recovery from snapshot takes 5-10 minutes β€” acceptable for dev/staging.

3. Monitor Downtime (If You Care)

resource "aws_cloudwatch_metric_alarm" "db_down" {
  alarm_name          = "${var.name}-database-down"
  comparison_operator = "LessThanThreshold"
  evaluation_periods  = 1
  metric_name         = "DatabaseConnections"
  namespace           = "AWS/RDS"
  period              = 60
  statistic           = "Average"
  threshold           = 1
  alarm_description   = "Database has no connections"

  dimensions = {
    DBInstanceIdentifier = aws_db_instance.dev.id
  }
}
Enter fullscreen mode Exit fullscreen mode

For Single-AZ, you'll see downtime during maintenance windows. Plan accordingly.

4. Test Your Disaster Recovery

# Create manual snapshot
aws rds create-db-snapshot \
  --db-instance-identifier app-dev \
  --db-snapshot-identifier app-dev-manual-snapshot

# Restore from snapshot (test recovery)
aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier app-dev-restored \
  --db-snapshot-identifier app-dev-manual-snapshot
Enter fullscreen mode Exit fullscreen mode

Proves you can recover even without Multi-AZ.

⚠️ What You Lose (Be Honest)

Without Multi-AZ, you lose:

  • ❌ Automatic failover (manual recovery needed)
  • ❌ Synchronous replication (use backups instead)
  • ❌ 99.95% SLA (becomes 99.5% single-AZ)
  • ❌ Zero RPO during failure (some data loss possible)

You gain:

  • βœ… 50% cost reduction
  • βœ… Still have automated backups
  • βœ… Can restore from snapshot in 5-10 min
  • βœ… Good enough for non-production

For production: Keep Multi-AZ. Worth every penny.

For everything else: Single-AZ + backups is plenty.

🎯 Quick Decision Matrix

Database Type Multi-AZ? Reason
Production customer-facing βœ… Yes Downtime = lost revenue
Production internal ⚠️ Maybe Depends on impact
Staging ❌ No Can tolerate downtime
QA/Test ❌ No Gets reset often anyway
Dev ❌ No Developers can wait 10 min
Demo ❌ No Schedule demos around maintenance
Personal project ❌ No Definitely not

πŸš€ Quick Start

# 1. Audit current Multi-AZ usage
aws rds describe-db-instances \
  --query 'DBInstances[?MultiAZ==`true`].[DBInstanceIdentifier,DBInstanceClass,MultiAZ]' \
  --output table

# 2. Identify non-production databases
# (anything with dev, staging, qa, test in name)

# 3. Update Terraform
# Change: multi_az = true β†’ multi_az = false

# 4. Apply changes
terraform apply

# 5. Watch your bill drop next month πŸ’°
Enter fullscreen mode Exit fullscreen mode

🎯 Summary

Multi-AZ doubles RDS costs for high availability:

  • Production: Worth it
  • Non-production: Wasteful

The fix:

  • One line in Terraform: multi_az = false
  • 50% instant savings on non-prod databases
  • Still get automated backups
  • Recovery time: 5-10 minutes (acceptable)

Typical savings:

  • 3 non-prod databases: $1,440/year
  • 10 non-prod databases: $7,200/year

Stop paying for high availability in environments that don't need it. Disable Multi-AZ on non-prod and save 50%. πŸš€


Disabled Multi-AZ on non-prod? How much did you save? Share in the comments! πŸ’¬

Follow for more AWS cost optimization with Terraform! ⚑

Top comments (0)