Right, let’s talk about RDS storage. This is where the rubber meets the road, or more accurately, where your queries meet the disk. AWS gives you a few flavors, and picking the right one isn’t just about cost—it’s about performance and, more importantly, not accidentally building a database that grinds to a halt the moment you get a single user. The two main types you’ll wrestle with are General Purpose SSD (gp3) and Provisioned IOPS (io1/io2). And then there’s autoscaling, which is like giving your database a gym membership but hoping it never actually has to lift anything heavy.

The GP3 Workhorse

gp3 is your new default. It’s the sensible sedan of storage: reliable, cost-effective, and it won’t embarrass you at the lights. The brilliant thing AWS did with gp3 is decouple storage size from I/O performance. With the old gp2, your IOPS were tied to your volume size—a truly bizarre design choice that meant you sometimes had to over-provision expensive storage you didn’t need just to get the performance you did need. Thank goodness that’s over.

With gp3, you get a baseline of 3,000 IOPS and 125 MB/s of throughput for free, regardless of your storage size. You can provision additional IOPS (up to 16,000) and throughput (up to 1,000 MB/s) independently. For 95% of workloads, the baseline is more than enough. You just specify your storage size in gibibytes (GiB) and you’re off.

# Creating a DB instance with a gp3 storage volume
aws rds create-db-instance \
    --db-instance-identifier my-database \
    --db-instance-class db.t3.micro \
    --engine mysql \
    --master-username admin \
    --master-user-password secret99 \
    --allocated-storage 100 \
    --storage-type gp3

Notice you don’t have to specify IOPS. That’s the beauty. It just works.

When You Need the IO1/IO2 Sports Car

Then there’s io1 (and its more durable sibling io2). This is for when your database isn’t just busy; it’s busy. You need predictable, high-performance I/O, and you’re willing to pay for the privilege. Think massive OLTP systems, large-scale SaaS applications, or databases that are just… fussy.

Here, you explicitly provision the IOPS you need, up to a frankly ludicrous 256,000 for io2. The key metric is the IOPS-to-storage ratio. For io1, you can provision up to 1,000 IOPS for every 1 GiB of storage. Need 10,000 IOPS? You’ll need at least 100 GiB of storage, even if you’re only storing 10 GB of data. It’s a tax on performance. io2 is more generous, allowing a higher ratio (1,000 IOPS per 1 GiB for io2 and 500 IOPS per 1 GiB for io1 Block Express), but the principle remains.

# Creating a high-performance DB instance with io2
aws rds create-db-instance \
    --db-instance-identifier my-beast-mode-db \
    --db-instance-class db.r5.xlarge \
    --engine postgres \
    --allocated-storage 100 \ # 100 GiB of storage
    --storage-type io2 \
    --iops 10000 # Provisioning 10,000 IOPS

You use this when you have a known, consistent workload. If you’re not sure, start with gp3 and monitor your metrics. Throwing provisioned IOPS at a random performance problem is an expensive way to be wrong.

The Autoscaling Double-Edged Sword

Storage autoscaling is a fantastic feature that prevents your database from suddenly becoming read-only when it runs out of space. You set a maximum storage limit, and RDS will automatically add storage in increments when it detects you’re running low. It’s a lifesaver.

The pitfall? It only scales storage, not performance. This is the critical detail everyone misses. If you have a gp3 volume, autoscaling from 100 GB to 500 GB does not automatically increase your IOPS or throughput. You’re still sitting at that baseline 3,000 IOPS, which is now spread across a much larger volume. Your performance can effectively dilute as you scale. You must manually adjust your provisioned IOPS on gp3 or your provisioned IOPS on io1/io2 as you grow.

# Creating a DB instance with storage autoscaling enabled
aws rds create-db-instance \
    --db-instance-identifier my-scalable-db \
    --allocated-storage 100 \
    --max-allocated-storage 1000 \ # It can scale up to 1000 GiB
    --storage-type gp3

Best practice? Set a very generous max-allocated-storage value. The cost of extra disk space you’re not using is negligible, but the cost of an outage because you set the limit too low is not. And always, always keep an eye on your CloudWatch metrics for VolumeReadIOPS and VolumeWriteIOPS to ensure your performance is scaling with your storage.