IBM Db2 is a capable enterprise database, but its PVU-based (Processor Value Unit) licensing plus annual support is both expensive and genuinely hard to forecast as you add cores. PostgreSQL has become the default landing spot for teams leaving commercial databases: it is open source, has no license fee, and over the last decade has closed most of the enterprise feature gap (partitioning, parallel query, logical replication, rich extensions). This is a real migration, not a connection-string swap, because the dialects and procedural languages differ, so scope it as a project with a parallel-run validation phase.
What actually has to change
Three layers move, and they get progressively harder:
- Schema (easier): tables, indexes, constraints, and sequences map cleanly, but datatypes need review. Db2’s
DECIMAL,TIMESTAMP,CLOB/BLOB, and identity columns all have Postgres equivalents, just confirm precision and theGENERATEDidentity behaviour. - SQL dialect (medium): Db2 SQL and Postgres SQL diverge on functions,
FETCH FIRSTvsLIMIT, casting, and some join and CTE behaviours. Most queries port with edits. - Procedural code (hardest): Db2 SQL PL stored procedures, triggers, and functions must be rewritten as PL/pgSQL. This is where the real effort lives, budget for it explicitly.
Tooling that does the heavy lifting
The AWS Schema Conversion Tool (SCT) supports Db2 LUW and automates a large share of the schema and code conversion, producing an assessment report that tells you up front what converts automatically and what needs hand work. Read that report first: it is your project plan. For the data itself, AWS DMS can do an initial load plus change-data-capture so the two databases stay in sync during a parallel run, or you can use db2 export plus Postgres COPY for a simpler bulk move where a maintenance window is acceptable.
The cutover that protects production
- Convert and review: run SCT, then hand-fix the procedures and queries it flags. Do not skip the manual review, automated conversion of complex SQL PL is rarely 100%.
- Load and sync: initial data load into Postgres, then CDC (DMS) to keep it current.
- Parallel-run: point a copy of the application (or read traffic) at Postgres and diff results against Db2 for a representative period. This is the step that catches dialect and rounding surprises.
- Cut over application connections during a planned window once parity holds, and keep Db2 read-only as a fallback until you are confident.
The details that bite
- Sequences and identity: Db2 identity columns and Postgres
GENERATED ... AS IDENTITYbehave subtly differently on import, reset sequence high-water marks after the data load or you will get key collisions. - Isolation and locking: Db2 and Postgres differ in default isolation and locking behaviour; test concurrency, not just correctness.
- Application SQL: ORMs hide a lot, but raw SQL embedded in the app, reporting tools, and ETL jobs all need the same dialect review as the database, inventory every place SQL is written.
- Extensions instead of features: some Db2 features map to Postgres extensions (for example
pg_partmanfor partitioning helpers,pg_cronfor scheduling). Plan those in rather than discovering the gap late.
SQL dialect changes to expect
Most Db2 queries port with edits, but the edits cluster in predictable places. Db2’s FETCH FIRST n ROWS ONLY becomes LIMIT (Postgres also accepts the standard FETCH FIRST form). Db2 built-ins and functions do not all have identically named Postgres equivalents, so string, date, and casting expressions need review one by one. Implicit casts that Db2 tolerates may be rejected by Postgres, which is stricter about type coercion, so expect to add explicit casts. Common table expressions and joins mostly behave the same, but confirm any query that relied on Db2-specific ordering or NULL-sorting behaviour, since defaults differ. None of this is hard individually; the cost is that it is spread across every query in the estate, which is why the SQL inventory matters as much as the procedural rewrite.
Proving the conversion before you trust it
The parallel-run phase is what turns a plausible conversion into a trusted one, and it is the step most often shortened under schedule pressure. After the initial load and CDC have Postgres tracking Db2, point a copy of the application, or a slice of read traffic, at Postgres and diff results against Db2 over a representative period that includes month-end or batch cycles, not just a quiet afternoon. Compare row counts and checksums, run the application regression suite, and compare query plans and latency against the Db2 baseline so a plan regression surfaces before users do. This is the phase that catches rounding differences, isolation surprises, and the handful of procedures SCT converted imperfectly. Promote only when documented acceptance criteria for correctness, performance, and failover are met, and keep Db2 read-only as a fallback through hypercare so rollback is a repoint rather than a restore.
Trading PVUs for engineering time
Db2 to PostgreSQL removes PVU licensing and annual support cost in exchange for a genuine conversion project. Let AWS SCT do the bulk of the schema and SQL PL work, then hand-review what it flags, keep both databases in sync with CDC, and parallel-run before you cut over. Treat the stored-procedure rewrite as the long pole, and reset sequences after load. Model the removed PVU and support spend against the engineering time to convert and run Postgres in the calculator above, and confirm any vendor-certified application still supports Postgres before you commit.