Cloud & Infrastructure · Databases
PostgreSQL 18, Ten Months In: Is the Upgrade Actually Worth It?
PostgreSQL 18 shipped in September 2025 and is now on its 18.4 point release. With PostgreSQL 19 still in beta, here's what 18's async I/O, UUIDv7, virtual generated columns, and OAuth support actually mean for a production upgrade decision today.
Shashikant Gupta
5 min read
Sponsored
PostgreSQL 18 has been out for ten months, long enough that the “wait for the first few point releases” advice no longer applies. It’s on 18.4 as of May 2026, and PostgreSQL 19 is still in beta, which makes 18 the newest version you should actually be running in production today, not the newest version that exists. If you’ve been putting off the upgrade decision, here’s what actually changed and whether it’s worth your maintenance window.
The async I/O subsystem is the real headline
Postgres has historically issued storage reads one at a time, waiting for each to complete before starting the next. PostgreSQL 18 introduces an asynchronous I/O subsystem that can issue multiple read requests concurrently, and the project’s own benchmarks show 2-3x throughput improvements on sequential scans, bitmap heap scans, and vacuum operations, specifically on workloads that are bound by storage latency rather than CPU.
Turning it on is a configuration change, not a schema or query change:
# postgresql.conf
io_method = worker # works on all platforms
# or, on Linux:
io_method = io_uring # lower overhead, requires a recent kernel
The sync option preserves the old one-request-at-a-time behavior if you need to rule out AIO as a variable while debugging something else. For most storage-bound workloads, this is close to a free performance win: no application changes, just a config flag and a restart.
uuidv7() fixes a real primary-key anti-pattern
If you’ve used gen_random_uuid() for primary keys, you’ve likely also hit the downside: UUIDv4 values are fully random, so every insert lands at an unpredictable point in the B-tree index. That causes index bloat and poor cache locality, the exact opposite of what an auto-incrementing integer gives you for free.
-- Before: fully random insert order, poor index locality
CREATE TABLE orders (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
created_at timestamptz NOT NULL DEFAULT now()
);
-- PostgreSQL 18: timestamp-ordered, still globally unique
CREATE TABLE orders (
id uuid PRIMARY KEY DEFAULT uuidv7(),
created_at timestamptz NOT NULL DEFAULT now()
);
uuidv7() embeds a timestamp in the leading bits of the UUID, so new rows insert in roughly chronological order, close to the locality benefit of a sequential integer, while keeping the collision-resistance and non-guessability that made you choose a UUID in the first place. This is a strong default for any new table’s primary key going forward; it’s not worth a migration for existing tables unless index bloat on that table is an active, measured problem.
Virtual generated columns: compute at read time, not write time
Generated columns aren’t new, but PostgreSQL 18 adds a virtual variant that computes its value at query time instead of storing it on disk:
ALTER TABLE orders ADD COLUMN total_with_tax numeric
GENERATED ALWAYS AS (subtotal * 1.08) VIRTUAL;
This trades a small per-query CPU cost for zero storage overhead and, critically, no table rewrite when you add the column, which matters a lot on a large table where a stored generated column would mean a long, locking ALTER TABLE. Use virtual for columns you read occasionally and want to add without downtime; keep using stored generated columns for anything read on nearly every query, where recomputing on the fly would add up.
The upgrade itself hurts less
The most underrated change in 18 isn’t a new feature, it’s what pg_upgrade no longer breaks. Before 18, a major version upgrade discarded the query planner’s statistics, so the freshly upgraded cluster ran on stale assumptions until autovacuum’s ANALYZE finished rebuilding them, sometimes producing a window of genuinely bad query plans on a busy system right after the upgrade you scheduled specifically to avoid disruption. PostgreSQL 18 carries statistics through the upgrade, so performance stays close to the pre-upgrade baseline instead of dipping and recovering.
What to check before you upgrade
| Area | What to verify |
|---|---|
| Extensions | Confirm every extension you depend on has a PostgreSQL 18–compatible build |
io_method | Test worker first; io_uring needs a sufficiently recent Linux kernel |
| OAuth | Only relevant if you’re adopting the new oauth authentication method in pg_hba.conf; existing auth methods are unaffected |
| Connection poolers | Verify your pooler (PgBouncer, pgcat) has a tested-compatible release for 18’s wire protocol changes |
| Staging load test | Run your actual query mix against a staging copy before flipping the production upgrade |
Should you wait for 19 instead?
No, not for a production system. PostgreSQL 19 is still in beta as of mid-2026, with general availability expected around September or October based on the project’s usual yearly cadence. Its headline feature, the REPACK command for online table rewrites, is worth testing against a staging snapshot now if bloat management is a pain point for you, but running a beta release against production data goes directly against the PostgreSQL project’s own guidance. Eighteen is the version to run today; nineteen is the version to be testing in parallel.
If your team’s upgrade path involves more than a version bump, whether that’s connection pooling changes, zero-downtime migration planning, or a broader infrastructure review, that’s the kind of work worth scoping properly rather than squeezing into the same maintenance window as the Postgres upgrade itself.
Ten months of point releases is a reasonable maturity bar. If you’re still on Postgres 16 or earlier and storage I/O shows up in your slow query logs, this is a good week to schedule the upgrade.
Frequently asked questions
- What's the single biggest reason to upgrade to PostgreSQL 18?
- The asynchronous I/O subsystem, if your workload is storage-bound. It delivers 2-3x throughput improvements on sequential scans, bitmap heap scans, and vacuum operations by letting Postgres issue multiple I/O requests concurrently instead of waiting on each one to finish before issuing the next. You get this by setting io_method to worker or io_uring (Linux only); it requires no query or schema changes.
- Should I use uuidv7() instead of gen_random_uuid() for new primary keys?
- For most new tables, yes. UUIDv4 (what gen_random_uuid() produces) is fully random, which means new rows insert at random points across a B-tree index, causing index bloat and poor cache locality on write-heavy tables. UUIDv7 embeds a timestamp in the leading bits, so values sort roughly chronologically, giving you the same global-uniqueness benefit of a UUID with insert locality closer to a sequential integer.
- Is PostgreSQL 18 stable enough for production, or should I wait for 19?
- 18 is the stable choice today. It's on its fourth point release (18.4, shipped May 2026) with ten months of real production usage behind it. PostgreSQL 19 is still in beta as of mid-2026 and won't reach general availability until roughly September or October, following the project's usual annual cadence. Running a beta in production is explicitly against the PostgreSQL project's own guidance.
- Do virtual generated columns require a migration if I already use stored generated columns?
- No, and you don't have to switch. Stored generated columns (computed at write time, taking disk space) still work exactly as before. Virtual generated columns are a new option that compute their value at read time instead, trading a small per-query CPU cost for zero storage and instant column addition on large tables, since there's no rewrite needed to add one. Pick per-column based on whether you read that column far more often than you write to the table, or the reverse.
- What actually changed in the pg_upgrade experience?
- Before PostgreSQL 18, a major version upgrade discarded the query planner's statistics, so the upgraded cluster ran on stale or default assumptions until autovacuum's ANALYZE caught up, which could mean a window of bad query plans on a busy system right when you can least afford them. PostgreSQL 18 carries those statistics through the upgrade, so the cluster performs close to its pre-upgrade baseline immediately rather than degrading and recovering.
Sources
Sponsored
More from this category
More from Cloud & Infrastructure
R.01 The AWS CloudFront Outage That Took Down Canvas, Blackboard, and Hugging Face at Once
R.02 Kubernetes 1.36 Haru: What's Actually Worth Upgrading For
R.03 AWS's Trillion-Dollar Billing Bug Is a Warning About Automated Cost Alerts
Sponsored
Discussion
Join the conversation.
Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.
Sponsored