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 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
- Start with smaller instance during testing
- Monitor performance under realistic load
- Scale up only if needed
- 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:
- Daily automated backups (7 days)
- Weekly manual snapshots (4 weeks)
- Monthly snapshots (12 months)
- 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.