Guide to Upgrading PostgreSQL on AWS RDS for Production Environments
At one of my previous organizations, we were still running a PostgreSQL 12.x version on AWS RDS. This database powered our Metabase instance , which was critical for analytics across the org. The catch? Since PostgreSQL 12 had already gone out of mainstream support, we had to rely on extended support — and pay extra for it.
Naturally, this sparked conversations about cost savings and long-term sustainability. The decision was clear: we needed to upgrade PostgreSQL to a supported version, for us it was PostgreSQL 17.
But like any database upgrade, it wasn’t just about clicking an “Upgrade” button. It meant understanding compatibility, preparing for edge cases, and making sure we didn’t bring down production dashboards that half the company relied on.
That’s when I started drafting a structured upgrade plan — something I wish I had when we first went through it.
The Upgrade Journey: Steps I Followed
Step 1: Change Log
First, I dug into PostgreSQL’s release notes. This was important to identify deprecated features or breaking changes that could cause Metabase queries to fail.
Step 2: Compatible Target Version
AWS doesn’t allow skipping major versions, so I had to check the valid upgrade paths from 12.x using the AWS CLI. Luckily, for us, it was a straightforward single-step upgrade.
aws rds describe-db-engine-versions \
--engine postgres \
--engine-version 12 \
--query "DBEngineVersions[*].ValidUpgradeTarget[*].{EngineVersion:EngineVersion}" \
--output text
📖 Reference: AWS RDS Upgrade Targets
Step 3: Instance class compatibility
Not every instance type supports all PostgreSQL versions, so I had to cross-check our RDS class against AWS’s documentation. In our case, we were on db.m6i.large, which turned out to support PostgreSQL 17 perfectly for our needs.
📖 Reference: AWS RDS Instance Class
Step 4: Parameter group
PostgreSQL upgrades can introduce new parameters or remove existing ones, which may impact your setup. To be safe, I created a new parameter group for the target version and reviewed it. Since we hadn’t customized much, the defaults worked fine.
Step 5: Database Content
Clean up potential blockers before upgrading:
Prepared transactions:
SELECT count(*) FROM pg_catalog.pg_prepared_xacts;
Replication slots:
SELECT * FROM pg_replication_slots;
reg* data types:
SELECT count(*) FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n, pg_catalog.pg_attribute a
WHERE c.oid = a.attrelid
AND NOT a.attisdropped
AND a.atttypid IN ('pg_catalog.regproc'::pg_catalog.regtype,
'pg_catalog.regprocedure'::pg_catalog.regtype,
'pg_catalog.regoper'::pg_catalog.regtype,
'pg_catalog.regoperator'::pg_catalog.regtype,
'pg_catalog.regconfig'::pg_catalog.regtype,
'pg_catalog.regdictionary'::pg_catalog.regtype)
AND c.relnamespace = n.oid
AND n.nspname NOT IN ('pg_catalog', 'information_schema');
Invalid databases:
SELECT datname FROM pg_database WHERE datconnlimit = -2;
Unknown data types:
SELECT DISTINCT data_type FROM information_schema.columns
WHERE data_type ILIKE 'unknown';
📖 Reference: Official Postgres Upgrade Docs
Step 6: Extension Compatibility
Extensions (like uuid-ossp, pgcrypto, etc.) are not automatically upgraded during a major version upgrade. So I listed all installed extensions and verified their compatibility with the new version.
SELECT *
FROM pg_extension pe
JOIN pg_available_extension_versions pev
ON pev.name = pe.extname;
Verify compatibility in AWS Postgres Extensions Release Note
Step 7: Pending maintenance
Checked RDS console for any pending maintenance tasks — these can conflict with upgrades, as running them during the upgrade can cause issues. You can view pending maintenance in the AWS RDS console under your DB instance’s “Maintenance & backups” section.
Step 8: Take snapshot
Took a manual snapshot before starting. It serves as a fallback in case the upgrade fails or anything goes wrong.
Step 9: Check Application Compatibility
Finally, I tested Metabase itself with the upgraded PostgreSQL version in staging. This step was crucial because Metabase Docker images often lock supported PostgreSQL versions. That’s when we ran into a new issue: our current Metabase version didn’t support PostgreSQL 17. This meant we had to upgrade Metabase as well. I created a forked Docker image with a few of our custom patches, then tested it against the new database to ensure everything worked smoothly.
Final Checklist
- [] Change log
- [] Compatible Target version
- [] Instance class compatibility
- [] Parameter group
- [] Database Content
- [] Extension compatibility
- [] Pending maintenance
- [] Take snapshot
- [] Application compatibility with new postgres version
Other Points to Consider
- Blue-Green Deployment: We used a blue-green deployment approach to test the upgrade on a separate environment, which allowed for a smooth transition to production.
- Performance Monitoring: Throughout the upgrade process, we monitored the RDS database diligently to ensure everything was working smoothly. We kept an eye on CPU, memory, disk usage, and query performance both before and after the upgrade to catch any regressions early.
- Driver & Connection Compatibility: We made sure that all applications were using PostgreSQL drivers compatible with the new version to avoid connection issues.
- Security Review: We reviewed roles, permissions, and SSL settings to ensure security wasn’t impacted.
- Communicate Downtime: I announced the upgrade downtime for Metabase and scheduled it at night, when traffic was minimal, to reduce disruption for users.
For us, the upgrade was about more than just security or new features — it was about avoiding unnecessary costs while ensuring reliability. What started as a cost-saving initiative turned into a lesson on systematic upgrades and resilience planning.
If you’re in a similar boat with aging PostgreSQL versions on RDS, hopefully, this step-by-step narrative gives you a practical starting point.
—
Refs:
202509230243
Top comments (0)