PostgreSQL 18 brings native uuidv7() and configurable asynchronous I/O via io_method
Released in September 2025, PostgreSQL 18 has now been in production for over a year and brought two changes that call for adjustments to code and configuration: the uuidv7() function for primary keys and the asynchronous I/O subsystem controlled by io_method.

PostgreSQL 18 was released on September 25, 2025, according to the project's official release notes. More than a year later, the version is now a natural upgrade candidate for anyone running 16 or 17 in production, and two changes on the feature list call for direct attention from whoever writes application code and whoever configures the server: the native uuidv7() function and the asynchronous I/O subsystem (AIO), exposed through the io_method variable.
Neither change is cosmetic. The first changes how primary key indexes behave under insert load. The second changes how Postgres talks to disk.
uuidv7() replaces the extension you're probably already using
Before Postgres 18, generating a UUID as a primary key meant choosing between gen_random_uuid() (UUIDv4, fully random) or installing a third-party extension like pg_uuidv7 to get time-sortable UUIDs. Version 18 solves this natively:
CREATE TABLE pedidos (
id uuid PRIMARY KEY DEFAULT uuidv7(),
criado_em timestamptz NOT NULL DEFAULT now()
);The difference between UUIDv4 and UUIDv7 isn't cosmetic. UUIDv4 is random byte by byte, so each insert lands at an unpredictable point in the btree index tree. This produces what database folks call index bloat from fragmentation: scattered pages, a buffer cache working harder, and higher write amplification in high-write workloads. UUIDv7 embeds a timestamp in the first bits, so UUIDs generated in sequence stay close to one another in sort order, and inserts tend to land at the end of the tree, the way a bigserial would behave.
In short: swapping gen_random_uuid() for uuidv7() in a table with high write volume tends to ease the pressure on the primary key index, without giving up the UUID's advantage over a sequential integer, which is not leaking the record count or depending on a single centralized sequence.
The trade-off nobody warns you about
UUIDv7 trades randomness for temporal predictability, and that has a price:
- Timestamp leakage: by construction, any UUIDv7 reveals the approximate moment it was generated. For public IDs (URLs, tokens exposed to the client), that can be information you didn't want to give away.
- It isn't guaranteed to be unique by arrival order across different processes: the ordering depends on each backend's clock; under clock skew between replicas or processes, the order may not be perfectly monotonic.
- Migrating an existing column isn't free: changing the
DEFAULTon a table already populated withgen_random_uuid()doesn't reorder the old rows, so the locality gain only applies to new records.
To migrate an existing column, the path is simple, but it needs to be explicit about what it doesn't do:
ALTER TABLE pedidos ALTER COLUMN id SET DEFAULT uuidv7();This changes generation going forward. It doesn't reorganize the index retroactively, and a REINDEX won't usefully mix the old UUIDs with the new ones, because the value itself has already been written. If the goal is reducing fragmentation in a legacy table, the conversation is about rebuilding the table, not about swapping the default.
io_method: from a single syscall to asynchronous I/O
The second change is more structural: Postgres 18 adds an asynchronous I/O subsystem (AIO), as the release notes record, allowing the backend to queue multiple read requests instead of blocking one at a time. This improves sequential scans, bitmap heap scans, and VACUUM operations.
Control sits in the server variable io_method, in postgresql.conf, accompanied by two auxiliary variables: io_combine_limit and io_max_combine_limit, which control how much each I/O operation can batch together.
In practice, this also unlocks something that sat inert in most installations: the effective_io_concurrency and maintenance_io_concurrency parameters, used to signal to the operating system that it can perform read-ahead (prefetch). Before, they only really worked on systems with fadvise() support. With AIO, they now apply on more platforms, which is also why Postgres 18 raised the default value of both from 1 to 16.
What to check before changing it
Changing io_method isn't an adjustment you make blindly, because the right value depends on the operating system and, in a managed environment, on what the provider allows:
| Scenario | What to consider |
|---|---|
Linux with io_uring support | Greater potential gain in scans and vacuum, but depends on a recent kernel and on the provider allowing the configuration |
Linux without io_uring or another OS | Postgres falls back to a dedicated-workers mode, more conservative |
| Managed database (RDS, Cloud SQL, and similar) | Check whether the provider exposes io_method before assuming the gain is available |
Changing io_method requires a server restart, so it's not something to test in production without first validating it in an equivalent environment. And, like any I/O subsystem swap, the real effect depends on the access pattern: a workload with heavy large sequential reads (reports, VACUUM on huge tables) tends to feel it more than an OLTP system doing pinpoint index lookups.
One useful detail for anyone debugging: Postgres 18 adds the pg_aios view, which shows the file handles in use by asynchronous I/O, giving direct visibility into what AIO is doing in real time.
What else weighs on the migration
Beyond the two highlights, the documentation's own incompatibility list has items that directly affect whoever maintains a data pipeline:
initdbnow enables data checksums by default; anyone who needs the old behavior uses the new--no-data-checksumsflag. This matters becausepg_upgraderequires the source and target cluster checksums to match.COPY FROMin CSV no longer treats\.as an end-of-file marker (except inpsqlreading fromSTDIN), and now requires\.to appear alone on the line. Old import scripts that relied on the previous behavior can break silently.pg_upgradenow preserves the optimizer's statistics, which removes the need to run a fullANALYZEright after the upgrade, a step that historically left large databases with bad plans for hours.
None of these three is as eye-catching as uuidv7() or io_method, but they're the ones most likely to cause silent incidents in ETL pipelines and deploy scripts that assume previous-version behavior.
When it's not worth migrating now
If the application already uses bigserial as a primary key and doesn't suffer from distributed insert concurrency, switching to uuidv7() just for the trend brings no real gain: the problem UUIDv7 solves (index fragmentation from randomness) simply doesn't exist in that scenario. And if the environment is managed and doesn't expose io_method, much of the asynchronous I/O gain announced in the release notes stays out of reach until the provider enables the configuration, even after the version upgrade.
For those who already have an upgrade plan to Postgres 18, it's worth treating uuidv7() as an application change (an explicit trade-off around timestamp leakage) and io_method as an infrastructure change (depends on the OS and on load testing before production): these are two independent decisions, and neither is mandatory just because the version brought both features.
Translated from the Brazilian Portuguese original · Read the original
GitHub Actions gets lockfiles to pin workflow dependencies
In public preview since October 14, 2026, the feature locks the workflow to the exact commit of each action, even if the referenced tag or branch changes later.


