Post

PostgreSQL 18 Beta: Key Features and Performance Enhancements

A comprehensive overview of PostgreSQL 18 Beta's latest features, performance improvements, and new capabilities for modern database applications.

PostgreSQL 18 Beta: Key Features and Performance Enhancements

Asynchronous I/O (AIO) Subsystem

The I/O Bottleneck Problem

Traditional PostgreSQL I/O operations were synchronous – a design that worked well for local SSDs but struggles with cloud storage (AWS EBS, Azure Disk) where latency fluctuates.

The AIO Solution

PostgreSQL 18 introduces platform-specific async I/O:

  • Linux: Uses io_uring (kernel 5.6+ required)
  • Other OS: Fallback to worker processes

Benchmark Results (AWS r6i.large, 1TB EBS gp3):

OperationPG17 TimePG18 Time (io_uring)Improvement
Cold-cache SELECT *15.8s5.7s2.77x
VACUUM FULL42m29m1.45x

Configuration

1
2
3
4
5
6
7
8
-- Check current I/O method
SHOW io_method;

-- Set io_uring (Linux only)
ALTER SYSTEM SET io_method = 'io_uring';

-- Tune read-ahead (default: 16)
ALTER SYSTEM SET effective_io_concurrency = 32;

Important: Monitor pg_aios view for active operations:

1
SELECT * FROM pg_aios;

When to Use

  • Cloud environments
  • OLAP workloads
  • Large VACUUM operations

Skip Scan for Multicolumn B-Tree Indexes

The Index Limitation

Consider this schema:

1
2
3
4
5
6
7
CREATE TABLE employees (
    department_id INT,
    employee_id INT,
    name TEXT
);

CREATE INDEX idx_emp_dept ON employees (department_id, employee_id);

Pre-18, this query couldn’t use the index effectively:

1
SELECT * FROM employees WHERE employee_id = 123;

The Skip Scan Advantage

If department_id has low cardinality (e.g., 10 departments), PostgreSQL 18 will:

  1. Scan distinct department_id values
  2. Search employee_id within each department

Execution Plan:

1
2
3
4
Index Only Scan using idx_emp_dept on employees
  Index Cond: (employee_id = 123)
  Filter: (employee_id = 123)
  Skip Scan: true

Limitations

  • Works best when leading columns have ≤100 distinct values
  • Not applicable for range scans on prefix columns

Planner Statistics Retention During Upgrades

The Upgrade Pain Point

Major version upgrades previously required:

1
2
3
pg_upgrade --link
# Then...
ANALYZE; -- Hours of downtime

PostgreSQL 18 Approach

Statistics are preserved in pg_statistic across upgrades. Real-world impact:

  • 500GB database: Reduces upgrade downtime from 3h → 15m
  • Immediate query optimization post-upgrade

Verification:

1
2
3
SELECT starelid::regclass, staattnum, stainherit 
FROM pg_statistic 
WHERE starelid = 'your_table'::regclass;

Parallel GIN Index Builds

Previous limitation: GIN indexes (used for JSONB/arrays) built serially.

New syntax:

1
2
3
CREATE INDEX CONCURRENTLY idx_gin_data 
ON logs USING GIN (jsonb_data) 
WITH (parallel_workers = 4);

Performance Gains:

Dataset SizePG17 TimePG18 Time (4 workers)Speedup
100M rows82m23m3.56x

Virtual Generated Columns

Stored vs. Virtual

  • Stored: Persisted on disk (logically replicated)
  • Virtual: Computed at query time (default)

Example:

1
2
3
4
5
6
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    price NUMERIC,
    tax_rate NUMERIC,
    total_price NUMERIC GENERATED ALWAYS AS (price * (1 + tax_rate)) STORED
);

Querying Generated Columns:

1
2
-- Uses generated column directly
SELECT * FROM products WHERE total_price > 100;

Temporal Constraints (WITHOUT OVERLAPS)

Solving Time-Based Conflicts

Traditional approach required triggers:

1
2
3
4
5
CREATE TABLE bookings (
    room_id INT,
    during TSTZRANGE,
    EXCLUDE USING GIST (room_id WITH =, during WITH &&)
);

PostgreSQL 18 Syntax:

1
2
3
4
5
6
7
CREATE TABLE bookings (
    room_id INT,
    start_time TIMESTAMPTZ,
    end_time TIMESTAMPTZ,
    PERIOD FOR validity (start_time, end_time),
    UNIQUE (room_id) WITHOUT OVERLAPS
);

Insertion Test:

1
2
3
4
5
6
7
-- Succeeds
INSERT INTO bookings (room_id, start_time, end_time)
VALUES (101, '2024-01-01 09:00', '2024-01-01 17:00');

-- Fails with "duplicate key violates unique constraint"
INSERT INTO bookings (room_id, start_time, end_time)
VALUES (101, '2024-01-01 12:00', '2024-01-01 14:00');

OAuth 2.0 Authentication

Configuration Example (pg_hba.conf):

1
2
3
4
5
6
7
hostssl all             all             oauth    
       clientcert=verify-full
       authz_endpoint="https://auth.example.com/oauth2/auth"
       token_endpoint="https://auth.example.com/oauth2/token"
       jwks_uri="https://auth.example.com/oauth2/certs"
       issuer="https://auth.example.com"
       audience="postgres-prod"

Migration from md5 to SCRAM

1
2
3
4
5
-- Check existing MD5 users
SELECT usename FROM pg_shadow WHERE passwd LIKE 'md5%';

-- Update password to SCRAM
ALTER USER app_user WITH PASSWORD 'new_secure_password';

Enhanced Monitoring

New pg_stat_all_tables Columns

1
2
3
4
5
6
7
8
SELECT 
    relname,
    total_vacuum_time,
    total_analyze_time,
    io_read_time,
    io_write_time 
FROM pg_stat_all_tables 
WHERE schemaname = 'public';

Lock Timeout Reporting

1
2
3
4
5
6
7
SELECT 
    pid,
    wait_event_type,
    query,
    pg_blocking_pids(pid)
FROM pg_stat_activity
WHERE state = 'active';

Developer Experience Improvements

UUIDv7 Example

1
2
3
4
5
6
7
CREATE TABLE events (
    id UUID DEFAULT uuidv7() PRIMARY KEY,
    data JSONB
);

-- Sample ID: 018f2e4e-2393-7e6c-9d3a-1a2b3c4d5e6f
-- Timestamp encoded in first 48 bits

RETURNING Clause Enhancement

1
2
3
4
UPDATE accounts 
SET balance = balance - 100
WHERE id = 123
RETURNING OLD.*, NEW.*;

Data Checksums Enabled by Default

Creation:

1
initdb -D /data/pg18 --data-checksums

Verification:

1
2
SELECT pg_relation_filepath('my_table'), 
       pg_relation_data_checksum('my_table');

Further Reading

  1. PostgreSQL 18 Release Notes (Official)
  2. AIO Deep Dive by Bruce Momjian
  3. Temporal SQL Standard (SQL:2011)
  4. OAuth 2.0 for PostgreSQL Auth
This post is licensed under CC BY 4.0 by the author.