Skip to main content

AWS Cost Optimisation

RDS and Database Cost Optimisation: Reducing Database Spend in AWS

Practical strategies for optimising RDS and database costs including instance rightsizing, storage optimisation, and Reserved Instance planning.

CloudPoint

CloudPoint Team

Database costs can be one of the largest AWS expenses, particularly for data-intensive applications. RDS instances running 24/7 with oversized configurations and inefficient storage quickly add up. This guide covers practical strategies for optimising database costs without sacrificing performance or reliability.

Understanding RDS Pricing

RDS costs include:

Instance Hours: Based on instance type and size Storage: Per GB-month for allocated storage IOPS: Provisioned IOPS (if used) Backups: Beyond free tier (100% of database size) Data Transfer: Cross-AZ, cross-region, to internet

Example (Sydney, db.t3.medium MySQL):

  • Instance: ~$0.088/hour = $64/month
  • Storage: 100 GB × $0.138 = $13.80/month
  • Backup: 100 GB × $0.115 = $11.50/month
  • Total: ~$90/month for a small database

Scale to production sizes and costs multiply quickly.

Strategy 1: Right-Size Database Instances

Analyze Current Utilization

Check CloudWatch metrics:

  • CPUUtilization
  • DatabaseConnections
  • FreeableMemory
  • ReadIOPS / WriteIOPS

Red flags indicating oversizing:

  • CPU < 40% consistently
  • Memory > 50% free
  • Connections well below max

Use Performance Insights

Free tool (7 days retention):

  • SQL query performance
  • Wait events
  • Database load
  • Top consumers

Identify if performance issues are:

  • Instance size (scale up)
  • Query optimisation needed (no scale needed)
  • Index missing (no scale needed)

Right-Sizing Process

  1. Start with smaller instance during testing
  2. Monitor performance under realistic load
  3. Scale up only if needed
  4. Consider Aurora Serverless v2 for variable workloads

Typical savings: 20-40% per right-sized instance

Strategy 2: Aurora Serverless v2

For variable or unpredictable workloads.

When to Use Aurora Serverless

Perfect for:

  • Development and test databases
  • Infrequently used applications
  • Variable workload applications
  • New applications with uncertain load

Benefits:

  • Scales automatically (0.5 to 128 ACUs)
  • Pay per second
  • No charge when idle (can pause)
  • Scales in sub-second

Cost Comparison

Traditional RDS (db.r6g.large):

  • $0.218/hour = $159/month (24/7)

Aurora Serverless v2 (avg 2 ACUs):

  • 2 ACUs × $0.18/hour = $0.36/hour
  • Variable usage (50% of time) = $65/month

Savings: 59% for this usage pattern

Strategy 3: Reserved Instances

Commit to database usage for significant discounts.

RDS Reserved Instances

1-Year Commitment:

  • No upfront: 40% savings
  • Partial upfront: 42% savings
  • All upfront: 43% savings

3-Year Commitment:

  • No upfront: 60% savings
  • Partial upfront: 62% savings
  • All upfront: 66% savings

When to Use RIs

Ideal candidates:

  • Production databases
  • Always-on requirements
  • Predictable workload
  • Stable for 1-3 years

Coverage strategy:

  • 80-90% of production capacity
  • Leave 10-20% for growth/flexibility

Size Flexibility

RIs have size flexibility within instance family:

Purchase: db.r6g.large (2 vCPU) Can use:

  • 2× db.r6g.medium
  • 1× db.r6g.xlarge
  • 4× db.r6g.small

Strategy 4: Storage Optimisation

Choose the Right Storage Type

General Purpose (gp3) - Default choice:

  • $0.138/GB-month
  • 3,000 IOPS baseline
  • 125 MB/s throughput
  • Can provision more IOPS ($0.023/IOPS)

General Purpose (gp2) - Legacy:

  • $0.138/GB-month
  • 3 IOPS per GB (min 100, max 16,000)
  • Burstable

Provisioned IOPS (io1) - High performance:

  • $0.276/GB-month
  • Plus $0.115/IOPS
  • Up to 64,000 IOPS
  • For I/O intensive workloads

Magnetic - Deprecated, don’t use

Recommendation: Use gp3 for most workloads

Migrate gp2 to gp3

aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --storage-type gp3 \
  --apply-immediately

Same cost for base performance, cheaper to add more IOPS.

Optimize Storage Size

RDS storage cannot shrink - choose wisely:

  • Start smaller
  • Monitor usage
  • Expand as needed
  • Leave 20% headroom

Delete Old Snapshots

Manual snapshots persist until deleted:

# List old snapshots
aws rds describe-db-snapshots \
  --query 'DBSnapshots[?SnapshotCreateTime<`2024-01-01`]' \
  --output table

# Delete old snapshots
aws rds delete-db-snapshot \
  --db-snapshot-identifier old-snapshot

Implement automated deletion:

from datetime import datetime, timedelta
import boto3

rds = boto3.client('rds')

def cleanup_old_snapshots(days=90):
    snapshots = rds.describe_db_snapshots(
        SnapshotType='manual'
    )

    cutoff = datetime.now() - timedelta(days=days)

    for snapshot in snapshots['DBSnapshots']:
        snapshot_time = snapshot['SnapshotCreateTime'].replace(tzinfo=None)

        if snapshot_time < cutoff:
            print(f"Deleting {snapshot['DBSnapshotIdentifier']}")
            rds.delete_db_snapshot(
                DBSnapshotIdentifier=snapshot['DBSnapshotIdentifier']
            )

Strategy 5: Multi-AZ Optimisation

Multi-AZ doubles database costs.

When Multi-AZ is Necessary

Required for:

  • Production databases
  • High availability requirements
  • Compliance requirements
  • Minimal downtime tolerance

Not necessary for:

  • Development databases
  • Test databases
  • Non-critical applications
  • Can tolerate downtime for restores

Disable Multi-AZ for Non-Production

aws rds modify-db-instance \
  --db-instance-identifier dev-database \
  --no-multi-az \
  --apply-immediately

Savings: 50% of instance costs for dev/test databases

Strategy 6: Aurora vs RDS

Aurora can be more cost-effective for certain workloads.

Aurora Benefits

Scalability:

  • Storage scales automatically (10 GB to 128 TB)
  • Read replicas (up to 15)
  • Global database

Performance:

  • Up to 5× MySQL performance
  • Up to 3× PostgreSQL performance
  • Lower storage I/O costs

Reliability:

  • 6 copies across 3 AZs
  • Automated backups to S3
  • Fast recovery

Cost Comparison

Example: 500 GB database, moderate I/O

RDS MySQL (db.r6g.large):

  • Instance: $159/month
  • Storage: 500 GB × $0.138 = $69/month
  • IOPS: $50/month (estimated)
  • Total: $278/month

Aurora MySQL:

  • Instance: $159/month (same size)
  • Storage: 500 GB × $0.115 = $57.50/month
  • I/O: 10M requests × $0.23/M = $2.30/month
  • Total: $218.80/month

Savings: 21% with Aurora (varies by I/O pattern)

Aurora I/O-Optimized

For high I/O workloads:

  • No charge for I/O operations
  • Higher storage cost
  • Break-even at ~2.5M I/O per GB per month

Strategy 7: Stop/Start Non-Production Databases

Stop databases when not in use.

Manual Stop/Start

# Stop database
aws rds stop-db-instance \
  --db-instance-identifier dev-database

# Start database
aws rds start-db-instance \
  --db-instance-identifier dev-database

Note: Stopped instances automatically start after 7 days.

Automated Scheduling

import boto3
from datetime import datetime

rds = boto3.client('rds')

def lambda_handler(event, context):
    current_hour = datetime.now().hour
    current_day = datetime.now().weekday()

    # Business hours: Mon-Fri, 8 AM - 6 PM
    is_business_hours = (
        current_day < 5 and
        8 <= current_hour < 18
    )

    # Find databases tagged for scheduling
    databases = rds.describe_db_instances()

    for db in databases['DBInstances']:
        db_id = db['DBInstanceIdentifier']

        # Check for Schedule tag
        tags = rds.list_tags_for_resource(
            ResourceName=db['DBInstanceArn']
        )['TagList']

        schedule = next(
            (t['Value'] for t in tags if t['Key'] == 'Schedule'),
            None
        )

        if schedule != 'business-hours':
            continue

        status = db['DBInstanceStatus']

        if is_business_hours and status == 'stopped':
            print(f"Starting {db_id}")
            rds.start_db_instance(DBInstanceIdentifier=db_id)

        elif not is_business_hours and status == 'available':
            print(f"Stopping {db_id}")
            rds.stop_db_instance(DBInstanceIdentifier=db_id)

Savings: 70% on scheduled databases

Strategy 8: Query and Schema Optimisation

Sometimes the database isn’t the problem - the queries are.

Identify Slow Queries

Use Performance Insights:

  • Top SQL statements
  • CPU time
  • I/O wait
  • Lock wait

Common Issues

Missing Indexes:

-- Add index for frequently queried column
CREATE INDEX idx_user_email ON users(email);

N+1 Queries:

  • Use joins instead of multiple queries
  • Batch operations
  • Implement caching

Full Table Scans:

-- Bad
SELECT * FROM orders WHERE status = 'pending';

-- Good (with index on status)
SELECT id, customer_id, total
FROM orders
WHERE status = 'pending'
AND created_at > CURRENT_DATE - INTERVAL 7 DAY;

Savings: Often can avoid instance upsize entirely

Strategy 9: Backup Optimisation

Backups beyond 100% of database size incur charges.

Automated Backup Retention

aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --backup-retention-period 7  # Down from 30 days

Production: 7-14 days typically sufficient Development: 1-3 days or disable

Snapshot Management

Implement lifecycle:

  1. Daily automated backups (7 days)
  2. Weekly manual snapshots (4 weeks)
  3. Monthly snapshots (12 months)
  4. Yearly snapshots (7 years for compliance)

Delete intermediate snapshots.

Strategy 10: Connection Pooling

Reduce database load and potentially downsize instance.

RDS Proxy

Managed connection pooling:

  • Reduces database connections
  • Improves scalability
  • Transparent to application
  • ~$0.015/hour + $0.000011 per connection

Use cases:

  • Serverless applications (Lambda)
  • Applications with many short-lived connections
  • Connection-limited databases

Application-Level Pooling

More cost-effective than RDS Proxy:

# Python with SQLAlchemy
from sqlalchemy import create_engine

engine = create_engine(
    'mysql://user:pass@host/db',
    pool_size=10,          # Connection pool size
    max_overflow=20,       # Additional connections if needed
    pool_pre_ping=True,    # Verify connections
    pool_recycle=3600      # Recycle after 1 hour
)

Savings: Potential to downsize instance by 1-2 sizes

Monitoring and Governance

CloudWatch Alarms

Monitor cost-impacting metrics:

  • DatabaseConnections > 80% of max
  • FreeableMemory < 1 GB
  • FreeStorageSpace < 10 GB
  • CPUUtilization > 80%

Enhanced Monitoring

$0.01/hour per database for OS-level metrics.

Use when:

  • Investigating performance issues
  • Optimizing instance size
  • Disable otherwise to save cost

Cost Allocation Tags

Tag all databases:

aws rds add-tags-to-resource \
  --resource-name arn:aws:rds:ap-southeast-2:123456789012:db:mydb \
  --tags Key=Application,Value=customer-portal \
         Key=Environment,Value=production \
         Key=Owner,Value=platform-team

Database Cost Optimisation Checklist

Immediate:

  • Identify and stop/delete unused databases
  • Review backup retention periods
  • Delete old manual snapshots
  • Disable Multi-AZ for dev/test

30 days:

  • Right-size oversized instances
  • Migrate gp2 to gp3 storage
  • Implement database scheduling for dev/test
  • Purchase RIs for production databases

90 days:

  • Evaluate Aurora vs RDS for workloads
  • Consider Aurora Serverless for variable loads
  • Implement query optimisation
  • Set up connection pooling

Ongoing:

  • Monthly cost reviews
  • Quarterly RI coverage analysis
  • Regular performance reviews
  • Snapshot lifecycle management

Conclusion

Database cost optimisation requires a combination of right-sizing, purchasing strategy, storage optimisation, and operational efficiency. For Australian businesses, these strategies can reduce database costs by 50-70% while maintaining or improving performance.

Start with quick wins - stop unused databases, optimise backups, disable Multi-AZ for non-production - then progress to Reserved Instances, Aurora migration, and query optimisation.

CloudPoint specialises in RDS and database cost optimisation. We analyze your database estate, identify savings opportunities, and implement sustainable optimisation strategies. Contact us for a database cost assessment.


Want to Optimise Your RDS Costs?

CloudPoint analyses your database usage and implements right-sizing, Reserved Instances, and Aurora migration strategies. Get in touch to start saving.

Learn more about our Cost Optimisation service →