AWS RDS and Aurora skill for Claude Code
Right engine, tested failover, no surprise I/O bill. Guides Claude through running PostgreSQL and MySQL on AWS from measured load: choosing RDS, Aurora, or Serverless v2, sizing for the working set, testing failover, and keeping settings in custom parameter groups. It plans major upgrades as blue/green deployments with the logical-replication and PITR caveats, reduces RDS Proxy pinning, and runs a script that compares Aurora Standard and I/O-Optimized from your bill.
- name
aws-rds-aurora - size285 lines · ≈3,950 tokens
- files2
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Chooses, sizes and operates Amazon RDS and Aurora for PostgreSQL and MySQL - RDS vs Aurora vs Aurora Serverless v2, instance classes, Multi-AZ instance vs Multi-AZ DB cluster, read replicas and reader endpoints, parameter groups, RDS Proxy pinning, backups and PITR, blue/green deployments for major upgrades, Database Insights, IAM database authentication, and cost traps such as Aurora I/O-Optimized vs Standard and Extended Support charges. Use when provisioning or reviewing an RDS or Aurora database, planning an engine upgrade, a failover or connection storm happens, or the database bill jumps. Try asking
- Should this service use RDS PostgreSQL, Aurora, or Aurora Serverless v2?
- Plan our Aurora PostgreSQL 15 to 17 upgrade with blue/green deployments
- Our Aurora bill doubled; check whether I/O-Optimized or something else is the cause
Use it when
- Provisioning or reviewing an RDS or Aurora database
- Planning a major engine upgrade or a parameter change that needs a reboot
- Failover took too long or the app did not reconnect
- RDS Proxy shows heavy session pinning or connections run out
- The RDS or Aurora bill jumped
Skip it when
- The problem is pool sizing or PgBouncer setup across services - use connection-pooling
- You need restore drills and RPO/RTO design - use backup-restore
- A specific query is slow - use sql-query-tuning
How to check the result
describe-db-clusters shows the planned class, Multi-AZ layout, parameter group and retention; a failover test recovers writes without app restarts; next month's bill by usage type matches the projection.
The common mistake it prevents
Deleting the old blue environment right after a blue/green switchover: the new production database's point-in-time recovery starts at green creation, so earlier recovery points are gone.
A managed database is set up well when the engine and deployment type were chosen from measured load, failover has been tested and the app reconnects on its own, engine settings live in a custom parameter group under version control, upgrades go through blue/green with a rehearsed switchover, and nobody is paying for I/O, idle readers, or Extended Support by accident.
First, inspect what exists
aws rds describe-db-instances --query 'DBInstances[].{id:DBInstanceIdentifier,engine:Engine,ver:EngineVersion,class:DBInstanceClass,multiAZ:MultiAZ,storage:StorageType,gb:AllocatedStorage,pg:DBParameterGroups[0].DBParameterGroupName,cluster:DBClusterIdentifier}' --output table
aws rds describe-db-clusters --query 'DBClusters[].{id:DBClusterIdentifier,engine:Engine,ver:EngineVersion,storage:StorageType,serverless:ServerlessV2ScalingConfiguration,members:length(DBClusterMembers),insights:DatabaseInsightsMode}' --output table
aws rds describe-db-engine-versions --engine aurora-postgresql --default-only \
--query 'DBEngineVersions[].EngineVersion'
aws rds describe-pending-maintenance-actions
Then pull two weeks of CloudWatch for the writer: CPUUtilization, FreeableMemory,
DatabaseConnections, ReadIOPS/WriteIOPS (RDS) or VolumeReadIOPs/VolumeWriteIOPs
(Aurora), ReplicaLag / AuroraReplicaLag, DBLoad (Database Insights), and for
Serverless v2 ServerlessDatabaseCapacity and ACUUtilization. Size from these numbers,
not from the current instance class.
Choose the deployment
| Situation | Choose |
|---|---|
| Small or steady workload, cost first, needs every community extension or engine feature | RDS for PostgreSQL/MySQL, gp3, Multi-AZ instance in production |
| Production needing readable standbys and faster failover on RDS | RDS Multi-AZ DB cluster (writer + 2 readable standbys, semi-synchronous; limited instance classes) |
| High read scale (up to 15 replicas), fast failover, storage that grows on its own, clones | Aurora provisioned |
| Spiky or unpredictable load, dev/test, many small databases | Aurora Serverless v2 (0 or 0.5 to 256 ACU) |
| Steady high utilization 24/7 | Provisioned (RDS or Aurora) with Reserved Instances; Serverless v2 costs more at constant high load |
| Multi-Region DR with low RPO | Aurora Global Database |
| I/O-heavy Aurora where I/O is 25%+ of the Aurora bill | Aurora I/O-Optimized storage (see Cost) |
Facts that drive the choice:
- Aurora storage is six copies across three AZs regardless of instance count; a single writer with no reader still has durable storage but no fast compute failover target.
- RDS Multi-AZ instance standby is not readable; the Multi-AZ DB cluster’s standbys are.
- RDS gp3 baseline is 3,000 IOPS and 125 MiB/s below 400 GiB, and 12,000 IOPS and 500 MiB/s at 400 GiB and above (PostgreSQL, MySQL, MariaDB), with extra IOPS and throughput purchasable only above that threshold.
- Aurora cluster volumes grow automatically, up to 256 TiB on recent engine versions.
Size the instance
- OLTP: memory-optimized classes (
db.r*), current-generation Graviton where the engine supports it. Size memory so the hot working set fits in the buffer cache: a fallingBufferCacheHitRatio(Aurora) or rising read IOPS with flat traffic means it does not. - Avoid burstable
db.t*classes in production; when CPU credits run out, latency collapses with no config change. DBLoad(average active sessions) consistently above the vCPU count means sessions are queuing: break it down by wait event before buying a bigger instance. Lock waits and I/O waits are not fixed by more CPU.- Leave headroom: target sustained CPU under roughly 60-70% on the writer so failover to an equal-size reader and traffic spikes fit.
- Readers that are failover targets should be the same class as the writer.
Aurora Serverless v2
aws rds modify-db-cluster --db-cluster-identifier app-prod \
--serverless-v2-scaling-configuration MinCapacity=4,MaxCapacity=64 --apply-immediately
- 1 ACU is roughly 2 GiB of memory with matching CPU and network; scaling is in steps as small as 0.5 ACU and happens without dropping connections.
- Set
MinCapacityhigh enough to hold the working set in memory; a minimum of 0.5 empties the buffer cache at night and the morning traffic hits storage. MinCapacity=0(auto-pause) fits dev/test and rarely used databases on supported versions; the first connection after a pause waits for resume. Not for production APIs.- Readers in promotion tiers 0-1 scale with the writer (failover-ready); tiers 2-15 scale independently (reporting readers).
max_connectionsis derived fromMaxCapacity, not current capacity.- Total idle cost is roughly (instances) x
MinCapacityACU-hours around the clock.
High availability and read scaling
- Production: Multi-AZ (RDS) or at least one Aurora reader in another AZ, promotion tier 0 or 1.
- Connect writers to the cluster (writer) endpoint and reads to the reader endpoint. The reader endpoint balances per new connection via DNS, not per query; pooled connections stick to one reader.
- Replica lag is real on RDS read replicas (asynchronous). Never read-your-own-write from a replica; route those reads to the writer.
- Keep client DNS caching at 5 seconds or less (JVM
networkaddress.cache.ttl) or the app keeps writing to the old writer after failover. AWS Advanced JDBC and Python wrappers detect topology changes faster than DNS. - Test failover before production, and after big changes. Ask the user before running it on a shared or production database:
aws rds failover-db-cluster --db-cluster-identifier app-prod # Aurora, Multi-AZ DB cluster
aws rds reboot-db-instance --db-instance-identifier app-prod --force-failover # RDS Multi-AZ instance
Measure: time until writes succeed again from the app’s point of view, and whether the pool recovered without a restart.
Parameter groups
- Never edit default groups (you cannot). Create a custom DB parameter group (and a DB cluster parameter group for Aurora and Multi-AZ DB clusters) per engine family; manage it in Terraform or CDK.
- Static parameters need a reboot (
pending-reboot); schedule it, or change during a blue/green deployment. - Baseline PostgreSQL settings worth setting deliberately:
log_min_duration_statement(for example 500 ms),idle_in_transaction_session_timeout,statement_timeoutper role rather than globally,rds.force_ssl = 1,shared_preload_librariesincludingpg_stat_statements, andwork_memsized to concurrency. - MySQL:
slow_query_log = 1,long_query_time,require_secure_transport = ON,innodb_print_all_deadlocks = 1. - Leave memory parameters that RDS derives from the instance class
(
shared_buffers,innodb_buffer_pool_size) on their formula unless you have measured a reason.
RDS Proxy
Use it for Lambda and other bursty clients that open many short connections, to absorb connection storms, and to shorten failover and blue/green switchover impact. It is not a substitute for an application pool in long-running services.
- Watch
DatabaseConnectionsCurrentlySessionPinned. Pinned sessions keep a dedicated database connection and defeat multiplexing. - PostgreSQL pins on: any
SET(includingset_config),PREPARE/EXECUTE/DEALLOCATE, temporary tables, cursors,LISTEN, session-level advisory locks (pg_advisory_lock; the_xact_variants do not pin),DISCARD ALL, and statements over 16 KB. - MySQL pins on: prepared statements, temporary tables,
LOCK TABLES,GET_LOCK, most user or system variable changes (some tracked variables do not pin; session pinning filters can exempt variable setting). - Move identical per-connection
SETstatements into the proxy’s initialization query. - Disable driver-level server-side prepared statements when connecting through the proxy if pinning stays high. Pool sizing math lives in connection-pooling.
Backups and recovery
- Automated backups with retention of at least 7 days (maximum 35); PITR restores to a new instance or cluster, never in place.
- Manual snapshots persist until deleted; copy them to another Region or account for disaster recovery and ransomware protection (or use AWS Backup with vault lock).
- Aurora clones (
restore-db-cluster-to-point-in-time --restore-type copy-on-write) give a production-sized test copy in minutes, billed for changed pages only. - Restore drills, RPO/RTO design, and logical dumps belong to backup-restore. Run one restore per quarter at minimum and time it.
Upgrades with blue/green deployments
aws rds create-blue-green-deployment --blue-green-deployment-name app-pg17 \
--source arn:aws:rds:us-east-1:123456789012:cluster:app-prod \
--target-engine-version 17.6 \
--target-db-cluster-parameter-group-name app-aurora-pg17
# test green, then (user confirms the window):
aws rds switchover-blue-green-deployment --blue-green-deployment-identifier bgd-abc123 \
--switchover-timeout 600
Pick the target version from describe-db-engine-versions; the version above is an
example. Checklist:
- Green created with the new version and a parameter group built for it; extensions updated and tested on green.
- Application test suite and a replay of production queries run against green (read-only); compare query plans for the top statements.
- PostgreSQL logical-replication limits checked: no DDL or large-object changes on
blue during the deployment (replication degrades and you must recreate green),
every table has a primary key or
REPLICA IDENTITY FULL, materialized views refreshed after switchover,pg_partmandisabled on blue,ANALYZErun on green. - Replica lag near zero; long transactions and DDL stopped on blue.
- IAM database-auth policies list both blue and green resources; IAM roles re-associated after switchover.
- Switchover timeout set (default 300 s, range 30-3,600 s); if exceeded, everything rolls back.
- Old blue kept for at least the PITR window: the new production starts its own PITR history at green creation and cannot restore to earlier times.
Limits to know: not supported with Secrets Manager-managed master passwords, cascading or cross-Region replicas, or CloudFormation-managed resources.
Minor versions: enable auto minor version upgrade on non-critical environments first, then production in a maintenance window.
Monitoring
- Database Insights replaced the Performance Insights console (Performance Insights
console end of life: July 31, 2026; the API continues). Standard mode is on by
default. Advanced mode (execution plans, lock analysis, fleet views) requires 465-day
retention and is billed per vCPU or ACU:
aws rds modify-db-cluster --db-cluster-identifier app-prod --database-insights-mode advanced --enable-performance-insights --performance-insights-retention-period 465. - Export PostgreSQL/MySQL logs to CloudWatch Logs; alarm on
FreeStorageSpace(RDS),FreeableMemory,DatabaseConnectionsagainstmax_connections, replica lag, andDiskQueueDepth. - Query-level tuning from
pg_stat_statementsor the slow log goes to sql-query-tuning.
IAM database authentication
-- PostgreSQL
CREATE USER app_user; GRANT rds_iam TO app_user;
-- MySQL
CREATE USER 'app_user' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';
aws rds generate-db-auth-token --hostname app-prod.cluster-xyz.us-east-1.rds.amazonaws.com \
--port 5432 --username app_user --region us-east-1
- Tokens are valid for 15 minutes and are needed only to open a connection; pools must generate a fresh token per new connection (driver plugins do this).
- Requires TLS. Budget 300-1,000 MiB of extra instance memory; avoid on tiny instances.
- A PostgreSQL user granted
rds_iamcan no longer log in with a password. - High new-connection rates: put RDS Proxy in front and use IAM auth to the proxy.
- Token generation is not logged in CloudTrail; audit through IAM policy design.
Cost traps
| Trap | Check |
|---|---|
| Aurora Standard with heavy I/O | I/O share of Aurora spend; run scripts/aurora_storage_cost.py |
| Running an engine past end of standard support | Extended Support is billed per vCPU-hour automatically; check the release calendar and plan upgrades |
| New instances silently enrolling in Extended Support | --engine-lifecycle-support open-source-rds-extended-support-disabled on create (fails instead of enrolling) |
| Serverless v2 minimum too high on idle clusters, or too many readers | ServerlessDatabaseCapacity vs MinCapacity; reader count per cluster |
| Idle or oversized readers and dev clusters running 24/7 | Stop dev clusters, delete unused readers, Serverless v2 with auto-pause for dev |
| gp2 or io1 on RDS | gp3 is cheaper for most workloads; io2 for sustained high IOPS |
| Manual snapshots never deleted, long backup retention | Snapshot inventory and age |
| Cross-AZ and cross-Region data transfer | App and database in the same AZ for chatty traffic; replica placement |
| Database Insights Advanced on every dev database | Use Advanced only where execution plans are needed |
| Steady production on On-Demand | Reserved Instances (size-flexible within a family) |
python3 scripts/aurora_storage_cost.py --compute 3200 --storage 450 --io 1900
python3 scripts/aurora_storage_cost.py --compute 3200 --storage 450 --io 1900 \
--compute-ratio <io_opt_instance_price/standard> --storage-ratio <io_opt_storage_price/standard>
Feed it one month of Cost Explorer spend for the cluster grouped by usage type, and the price ratios from the Aurora pricing page for your Region and instance class. It prints the I/O share, the AWS 25% rule verdict, the projected I/O-Optimized bill, and the break-even I/O spend. Standard to I/O-Optimized can be switched once every 30 days; switching back is allowed any time (NVMe-based instances restart when switching).
Hard rules
- Destructive or disruptive actions need explicit user confirmation, naming the
resource: deleting an instance or cluster (always keep a final snapshot), failover,
blue/green switchover,
--apply-immediatelymodifications that reboot, restoring over a name in use, deleting snapshots. - Never put credentials in code or Terraform state outputs; use Secrets Manager or IAM auth.
- Encryption at rest must be chosen at creation (or via snapshot copy or blue/green with a new KMS key); plan it up front.
- Public accessibility off; databases in private subnets; security groups allow the app only.
Verification
- Configuration diff: instance class, Multi-AZ, parameter group, backup retention,
deletion protection, encryption, and Insights mode match the plan
(
describe-db-instances/describe-db-clusters). - Failover test: app recovers writes within the agreed time without restarts.
- Load test or production replay on the new size or version: p95 latency and
DBLoadwithin targets. - Cost: next month’s bill by usage type matches the projection; I/O and Extended Support lines are expected.
Output
**Database:** app-prod, Aurora PostgreSQL 17, I/O-Optimized, writer db.r8g.2xlarge + 1 reader (tier 0, other AZ)
**Why:** I/O was 34% of Aurora spend; projected saving 7%/month (aurora_storage_cost.py)
**HA:** failover tested 2026-10-20, writes resumed in 31 s, pools recovered
**Params:** app-aurora-pg17 (log_min_duration_statement=500ms, idle_in_transaction_session_timeout=60s)
**Upgrade:** blue/green from 15.x, switchover 600 s timeout, old cluster kept 14 days for PITR
**Monitoring:** Database Insights Standard; alarms on memory, connections, replica lag Bundled files
Loaded only when a step in the skill needs them. Review them here before installing.
scripts/aurora_storage_cost.py
#!/usr/bin/env python3
"""Compare Aurora Standard vs Aurora I/O-Optimized for one cluster from real spend.
Usage:
python3 scripts/aurora_storage_cost.py --compute 3200 --storage 450 --io 1900 \
[--compute-ratio R --storage-ratio S] [--other 120]
Inputs are one month of Aurora Standard spend in any currency, taken from Cost Explorer
or the CUR for the cluster (group by usage type):
--compute instance-hours or Serverless ACU-hours (writer + readers)
--storage cluster storage GB-month
--io read + write I/O request charges
--other backups, data transfer, Database Insights, RDS Proxy (same under both; optional)
Optional price ratios for your Region, read from the Aurora pricing page:
--compute-ratio I/O-Optimized instance (or ACU) price / Standard price, e.g. for your class
--storage-ratio I/O-Optimized storage GB-month price / Standard storage price
With ratios, the script projects the I/O-Optimized bill and the break-even I/O spend.
Without them, it applies the AWS guidance: I/O-Optimized fits when I/O is 25% or more
of total Aurora spend.
Reserved instance discounts: pass compute already net of RIs and use the RI price ratio.
Exit codes: 0 = computed, 2 = bad input.
"""
import argparse
import sys
def positive(name: str, value: float) -> float:
if value < 0:
print(f"error: --{name} must be >= 0", file=sys.stderr)
sys.exit(2)
return value
def main() -> int:
p = argparse.ArgumentParser(description="Aurora Standard vs I/O-Optimized comparison")
p.add_argument("--compute", type=float, required=True)
p.add_argument("--storage", type=float, required=True)
p.add_argument("--io", type=float, required=True)
p.add_argument("--other", type=float, default=0.0)
p.add_argument("--compute-ratio", type=float)
p.add_argument("--storage-ratio", type=float)
a = p.parse_args()
for n in ("compute", "storage", "io", "other"):
positive(n, getattr(a, n))
if (a.compute_ratio is None) != (a.storage_ratio is None):
print("error: pass both --compute-ratio and --storage-ratio, or neither", file=sys.stderr)
return 2
for n in ("compute_ratio", "storage_ratio"):
v = getattr(a, n)
if v is not None and v < 1.0:
print(f"warning: --{n.replace('_', '-')} {v} < 1.0; I/O-Optimized prices are normally higher", file=sys.stderr)
aurora_total = a.compute + a.storage + a.io
if aurora_total <= 0:
print("error: total Aurora spend is zero", file=sys.stderr)
return 2
io_share = a.io / aurora_total
print("Aurora Standard (current month)")
print(f" compute {a.compute:>12,.2f}")
print(f" storage {a.storage:>12,.2f}")
print(f" I/O {a.io:>12,.2f} ({io_share:.1%} of Aurora spend)")
print(f" total {aurora_total:>12,.2f} (+ {a.other:,.2f} other, unaffected)")
print()
rule = "I/O-Optimized" if io_share >= 0.25 else "Standard"
print(f"AWS rule of thumb (I/O >= 25% of Aurora spend -> I/O-Optimized): {rule}")
if a.compute_ratio is not None:
opt_total = a.compute * a.compute_ratio + a.storage * a.storage_ratio
delta = aurora_total - opt_total
breakeven_io = opt_total - a.compute - a.storage
print()
print("Projected Aurora I/O-Optimized (same usage, no I/O charges)")
print(f" compute {a.compute * a.compute_ratio:>12,.2f} (x{a.compute_ratio})")
print(f" storage {a.storage * a.storage_ratio:>12,.2f} (x{a.storage_ratio})")
print(f" total {opt_total:>12,.2f}")
print()
if delta > 0:
print(f"Switch saves about {delta:,.2f} per month ({delta / aurora_total:.1%}).")
else:
print(f"Switch costs about {-delta:,.2f} more per month; stay on Standard.")
print(f"Break-even: I/O-Optimized wins once monthly I/O spend exceeds {breakeven_io:,.2f}.")
verdict = "I/O-Optimized" if delta > 0 else "Standard"
else:
verdict = rule
print("Pass --compute-ratio and --storage-ratio from the pricing page for a projected bill.")
print()
print(f"Recommendation: {verdict}")
print("Notes: Standard -> I/O-Optimized can be changed once every 30 days; switching back is")
print("allowed any time. Check a full month that includes peaks (batch jobs, reindexing,")
print("month-end) before deciding; one quiet month understates I/O.")
return 0
if __name__ == "__main__":
sys.exit(main())
About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.