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):
Operation | PG17 Time | PG18 Time (io_uring) | Improvement |
---|
Cold-cache SELECT * | 15.8s | 5.7s | 2.77x |
VACUUM FULL | 42m | 29m | 1.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:
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:
- Scan distinct
department_id
values - 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
Faster Full-Text Search
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 Size | PG17 Time | PG18 Time (4 workers) | Speedup |
---|
100M rows | 82m | 23m | 3.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
- PostgreSQL 18 Release Notes (Official)
- AIO Deep Dive by Bruce Momjian
- Temporal SQL Standard (SQL:2011)
- OAuth 2.0 for PostgreSQL Auth