All posts PostgreSQL 18: What's New and Why It Matters
ยทRivestack Team

PostgreSQL 18: What's New and Why It Matters

PostgreSQL
database
updates

PostgreSQL 18 is here, and for once, the release notes are genuinely exciting. Not "we fixed some edge case in the query planner" exciting (though they did that too). We're talking about async I/O, which is the kind of change that makes database engineers spill their coffee in a good way.

Let's go through the features that matter for production workloads, especially if you're running AI applications with pgvector.

Asynchronous I/O (The Big One)

For the last 30+ years, PostgreSQL has used synchronous I/O. When a backend process needed to read a page from disk, it would ask the OS, then sit there waiting. Not doing anything. Just... waiting. Like a developer staring at a progress bar.

PostgreSQL 18 finally introduces native async I/O. Backend processes can now submit multiple read requests and continue doing useful work while the OS fetches pages from disk. When the data arrives, the process picks it up and keeps going.

How It Works

On Linux, PostgreSQL 18 uses io_uring, the kernel's modern async I/O interface. On other platforms, it falls back to POSIX AIO:

-- Check your current I/O method
SHOW io_method;

-- On Linux with io_uring support (the fast path)
SET io_method = 'io_uring';

-- On macOS or older Linux kernels
SET io_method = 'posix_aio';

In most cases, you don't need to set anything. PostgreSQL 18 detects the best available method automatically.

Why This Matters for Vector Search

Here's where it gets interesting for pgvector users. When PostgreSQL traverses an HNSW index, it reads nodes scattered across the index. With synchronous I/O, each cache miss blocks the process until the page is fetched. With async I/O, PostgreSQL can prefetch the next nodes while processing the current one.

The result depends on how much of your index fits in memory:

  • Index fits in memory: Minimal difference (everything is cached anyway)
  • Index partially fits: 20-30% throughput improvement
  • Index doesn't fit at all: 30-40% improvement on sequential scans, and noticeable gains on HNSW traversal

In our benchmarks on NVMe storage, we saw sequential scan performance improve by 30-40% on tables larger than shared_buffers. For HNSW specifically, the improvement varies depending on ef_search and index size, but it's consistently positive.

The takeaway: if your vector dataset is too large to fit in memory (which is common at 1M+ vectors with 1536 dimensions), PostgreSQL 18 gives you a meaningful speed boost for free. No configuration changes, no new indexes, no code changes.

A Quick Note on io_uring Security

io_uring has had some security vulnerabilities in the past, which made some people nervous. The PostgreSQL team worked closely with Linux kernel developers to ensure their io_uring usage follows best practices. If you're running a kernel from 2025 or later, you're fine. If you're on an older kernel, PostgreSQL gracefully falls back to synchronous I/O.

Improved JSONB Performance

If you're storing metadata alongside your embeddings (and you should be), JSONB got a nice upgrade in PostgreSQL 18.

What Changed

The JSONB engine was refactored to reduce memory allocations and improve parsing speed. The biggest wins are:

  • jsonb_path_query is up to 2x faster on deeply nested documents. If you're querying metadata like $.properties.source.name, you'll notice.
  • jsonb_set and jsonb_insert are faster on large documents. Updating a single field in a 10KB JSONB blob used to copy the entire thing. PG18 is smarter about partial updates.
  • GIN index builds for JSONB columns are 15-25% faster, which matters when you're indexing millions of rows with rich metadata.

Practical Example

A common pattern with pgvector is storing embedding metadata as JSONB so you can filter before or after the vector search:

-- Store documents with rich metadata
CREATE TABLE documents (
  id BIGSERIAL PRIMARY KEY,
  content TEXT,
  embedding VECTOR(1536),
  metadata JSONB
);

-- Create a GIN index for metadata queries
CREATE INDEX ON documents USING gin (metadata);

-- Filter by metadata, then find similar vectors
SELECT id, content,
       1 - (embedding <=> '[0.02, ...]'::vector) AS similarity
FROM documents
WHERE metadata @> '{"source": "arxiv", "year": 2026}'
ORDER BY embedding <=> '[0.02, ...]'::vector
LIMIT 10;

In PostgreSQL 18, both the GIN index lookup and the JSONB containment check (@>) are faster. For workloads that combine metadata filtering with vector search, this is a quiet but meaningful improvement.

Better Partitioning

Partitioning in PostgreSQL has improved with every release, and PG18 continues the trend.

Smarter Partition Pruning

The query planner now eliminates irrelevant partitions earlier in the planning process. For tables with hundreds of partitions, this alone can cut planning time in half.

Why does this matter? If you're partitioning your vector tables by date or tenant (which is a good idea at scale), the planner needs to figure out which partitions to scan. With PG17, the planner would consider all partitions first, then prune. With PG18, it prunes during initial path generation, which means less work and faster queries.

Partition-Wise Joins

PG18 also improves partition-wise joins, where PostgreSQL can join two partitioned tables partition-by-partition instead of building a giant hash table. This matters if you're joining your partitioned embedding table with a partitioned metadata table:

-- Partition embeddings by month
CREATE TABLE embeddings (
  id BIGSERIAL,
  created_at TIMESTAMPTZ NOT NULL,
  embedding VECTOR(1536),
  metadata JSONB
) PARTITION BY RANGE (created_at);

-- Create monthly partitions
CREATE TABLE embeddings_2026_01 PARTITION OF embeddings
  FOR VALUES FROM ('2026-01-01') TO ('2026-02-01');
CREATE TABLE embeddings_2026_02 PARTITION OF embeddings
  FOR VALUES FROM ('2026-02-01') TO ('2026-03-01');
CREATE TABLE embeddings_2026_03 PARTITION OF embeddings
  FOR VALUES FROM ('2026-03-01') TO ('2026-04-01');

With monthly partitions, you can drop old data instantly (DROP TABLE embeddings_2025_01), vacuum individual months without locking the whole table, and keep your HNSW indexes manageable in size.

Logical Replication Improvements

Logical replication got three features that make it actually usable for serious deployments:

Failover Slots

Previously, replication slots were tied to a specific server. If the primary failed over to a standby, your logical replication subscribers would lose their position and need to resync from scratch. On a large vector database, that resync could take hours.

PG18 introduces failover slots that survive a primary-to-standby promotion. Your logical replication just keeps working after a failover. This sounds like a small thing, but it's the difference between "failover took 5 seconds" and "failover took 5 seconds plus 6 hours of resyncing."

Parallel Apply

Logical replication subscribers can now apply changes using multiple worker processes. If you're replicating a high-write workload (like a pipeline that continuously inserts embeddings), parallel apply keeps the subscriber from falling behind.

In our testing, parallel apply with 4 workers reduced replication lag by 60-70% on embedding-heavy write workloads compared to the single-worker default.

Conflict Detection

Multi-primary setups (where more than one server accepts writes) have always been the wild west of PostgreSQL replication. PG18 adds built-in conflict detection that logs and optionally resolves conflicts automatically. It's not a magic bullet for multi-primary, but it's a huge step forward from "hope nothing conflicts."

Other Notable Changes

A few more things worth mentioning:

  • EXPLAIN improvements. EXPLAIN ANALYZE now shows I/O timing per node, making it much easier to identify which part of your query is hitting disk. Essential for debugging slow vector searches.
  • Connection scalability. PostgreSQL 18 handles more concurrent connections with less overhead, thanks to improvements in the lock manager and shared buffer management. Good news if your AI pipeline opens a lot of connections.
  • pg_stat_io enhancements. The I/O statistics view now tracks async I/O separately, so you can see exactly how much of your I/O is benefiting from the new async path.

Rivestack and PostgreSQL 18

All new Rivestack instances run PostgreSQL 18 by default. If you're on an older version, you'll need to create a new instance since in-place major version upgrades are not currently supported. This is due to the complexity of pg_upgrade on managed infrastructure with HNSW indexes and replication slots. The safe path is pg_dump/pg_restore to a fresh PG18 instance, and we're happy to help if you need guidance on migrating.

The async I/O improvements pair particularly well with our NVMe storage. NVMe drives can handle hundreds of thousands of concurrent I/O operations, but with synchronous I/O, PostgreSQL could only submit one at a time per backend. Async I/O finally lets the database take full advantage of what the hardware can do. It's like upgrading from a single-lane road to a highway, where the road was always there, but now PostgreSQL can actually use all the lanes.

If you want to try PostgreSQL 18 with pgvector on NVMe, create a free instance and see the difference for yourself.

Rivestack

Rivestack

Managed PostgreSQL with pgvector built in. 2,000 QPS vector search on NVMe. Vectors and relational data in one place. EU and US-East regions.

๐Ÿ‡ซ๐Ÿ‡ท Based in France, European Union

2026 Rivestack