Torna al blog

PostgreSQL 18: I/O Asincrono 3x Più Veloce, UUIDv7 e Virtual Columns

Esplora PostgreSQL 18: nuovo subsystem I/O asincrono con performance 3x, virtual generated columns, uuidv7(), temporal constraints, OAuth 2.0 e pg_upgrade migliorato.

Edoardo Midali

Edoardo Midali

Developer · Content Creator

11 min di lettura
PostgreSQL 18: I/O Asincrono 3x Più Veloce, UUIDv7 e Virtual Columns

PostgreSQL 18 introduce un nuovo subsystem I/O asincrono con miglioramenti fino a 3x nelle letture da storage, virtual generated columns che calcolano valori at query time, la funzione uuidv7() per UUID performanti, temporal constraints per gestire range temporali e supporto OAuth 2.0. Questa release migliora drasticamente performance e funzionalità per workload di ogni dimensione.

🎯 Novità Principali

Subsystem I/O Asincrono (AIO)

Performance fino a 3x su letture da storage:

-- ✅ PostgreSQL 18 - Async I/O

-- Configura AIO method
SET io_method = 'worker';  -- Default
SET io_method = 'io_uring'; -- Linux 5.1+
SET io_method = 'sync';     -- Vecchio comportamento

-- AIO operations supportate:
-- - Sequential scans
-- - Bitmap heap scans
-- - Vacuum

-- Benchmark results:
-- Sequential scan (100GB table):
-- PostgreSQL 17: 180s
-- PostgreSQL 18: 60s  → 3x faster!

Come funziona AIO:

-- ✅ Multiple I/O requests concorrenti

-- Prima (sync):
-- Read block 1 → Wait → Read block 2 → Wait → ...
-- Total time: Sum of all waits

-- Ora (async):
-- Issue read 1, 2, 3, 4 → Wait for all → Process
-- Total time: Max wait time (parallelized)

-- PostgreSQL anticipa access patterns
-- Emette richieste I/O multiple
-- Migliora throughput complessivo

Tuning AIO:

-- ✅ Parametri di tuning

-- Effective I/O concurrency (numero richieste parallele)
SET effective_io_concurrency = 200;

-- Maintenance I/O concurrency (per vacuum, create index)
SET maintenance_io_concurrency = 100;

-- AIO max workers
SET max_io_workers = 4;

-- Check performance
EXPLAIN (ANALYZE, BUFFERS)
SELECT * FROM large_table WHERE condition;

Virtual Generated Columns

Calcolo valori at query time:

-- ✅ PostgreSQL 18 - Virtual generated columns

-- Prima (stored):
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name TEXT,
    price NUMERIC(10,2),
    tax_rate NUMERIC(5,2) DEFAULT 0.22,
    -- Stored: occupa spazio disco
    final_price NUMERIC(10,2) GENERATED ALWAYS AS
        (price * (1 + tax_rate)) STORED
);

-- Ora (virtual - default!):
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    name TEXT,
    price NUMERIC(10,2),
    tax_rate NUMERIC(5,2) DEFAULT 0.22,
    -- Virtual: calcolato at query time
    final_price NUMERIC(10,2) GENERATED ALWAYS AS
        (price * (1 + tax_rate))
);

-- Benefici:
-- - No spazio disco extra
-- - Sempre up-to-date
-- - No overhead su INSERT/UPDATE

Virtual vs Stored:

-- ✅ Quando usare quale

-- Virtual (default):
CREATE TABLE users (
    first_name TEXT,
    last_name TEXT,
    full_name TEXT GENERATED ALWAYS AS
        (first_name || ' ' || last_name)
    -- ✓ Calcolo semplice
    -- ✓ Usato raramente in WHERE
    -- ✓ Valori sempre sincronizzati
);

-- Stored (explicit):
CREATE TABLE orders (
    items JSONB,
    total NUMERIC GENERATED ALWAYS AS (
        (SELECT SUM((item->>'price')::numeric)
         FROM jsonb_array_elements(items) item)
    ) STORED
    -- ✓ Calcolo complesso/costoso
    -- ✓ Usato spesso in WHERE/ORDER BY
    -- ✓ Meglio indicizzare
);

CREATE INDEX ON orders(total);

Logical replication:

-- ✅ Stored columns ora replicabili

-- Publisher
CREATE TABLE inventory (
    product_id INT,
    quantity INT,
    status TEXT GENERATED ALWAYS AS (
        CASE
            WHEN quantity > 100 THEN 'in_stock'
            WHEN quantity > 0 THEN 'low_stock'
            ELSE 'out_of_stock'
        END
    ) STORED
);

-- Subscriber riceve valori calcolati
-- No necessità di ricalcolare
-- Consistent state across replicas

uuidv7() Function

UUID con ordinamento temporale:

-- ❌ Prima: UUIDv4 (random)

CREATE TABLE events (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    event_type TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Problemi:
-- - Nessun ordinamento temporale
-- - B-tree index frammentato
-- - Performance degradate nel tempo
-- - Range queries inefficienti
-- ✅ PostgreSQL 18 - UUIDv7

CREATE TABLE events (
    id UUID PRIMARY KEY DEFAULT uuidv7(),
    event_type TEXT,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Benefici:
-- - Ordinamento temporale intrinseco
-- - B-tree index efficiente
-- - Migliore locality
-- - Range queries veloci

-- UUIDv7 structure:
-- 48 bits: Unix timestamp (milliseconds)
-- 12 bits: Random
-- 2 bits: Version
-- 62 bits: Random

Performance comparison:

-- ✅ Benchmark insert performance

-- UUIDv4 (1M rows):
-- Insert time: 45s
-- Index size: 42MB
-- Sequential read: 8200ms

-- UUIDv7 (1M rows):
-- Insert time: 32s (-29%)
-- Index size: 35MB (-17%)
-- Sequential read: 1200ms (-85%!)

-- Query by time range
EXPLAIN ANALYZE
SELECT * FROM events
WHERE id >= uuidv7_from_timestamp('2024-01-01'::timestamptz)
  AND id < uuidv7_from_timestamp('2024-02-01'::timestamptz);

-- Index scan efficiente grazie a ordinamento temporale!

Temporal Constraints

Constraints su range temporali:

-- ✅ PostgreSQL 18 - Temporal constraints

-- PRIMARY KEY temporale
CREATE TABLE room_bookings (
    room_id INT,
    booking_period TSTZRANGE,
    guest_name TEXT,
    PRIMARY KEY (room_id, booking_period WITHOUT OVERLAPS)
);

-- Previene sovrapposizioni automaticamente!
INSERT INTO room_bookings VALUES
    (101, '[2024-01-01, 2024-01-05)', 'Alice');

INSERT INTO room_bookings VALUES
    (101, '[2024-01-03, 2024-01-07)', 'Bob');
-- ERROR: conflicting key value violates exclusion constraint

UNIQUE temporal constraints:

-- ✅ UNIQUE con range

CREATE TABLE employee_positions (
    employee_id INT,
    position TEXT,
    valid_period DATERANGE,
    UNIQUE (employee_id, valid_period WITHOUT OVERLAPS)
);

-- Employee può avere solo una posizione per periodo
INSERT INTO employee_positions VALUES
    (1, 'Developer', '[2024-01-01, 2024-06-01)');

INSERT INTO employee_positions VALUES
    (1, 'Senior Developer', '[2024-03-01, 2024-12-01)');
-- ERROR: violates unique constraint

FOREIGN KEY temporal:

-- ✅ FOREIGN KEY con PERIOD

CREATE TABLE projects (
    project_id INT,
    project_period TSTZRANGE,
    PRIMARY KEY (project_id, project_period WITHOUT OVERLAPS)
);

CREATE TABLE tasks (
    task_id INT,
    project_id INT,
    task_period TSTZRANGE,
    FOREIGN KEY (project_id, task_period PERIOD)
        REFERENCES projects (project_id, project_period)
);

-- Task period deve essere contenuto nel project period
INSERT INTO projects VALUES
    (1, '[2024-01-01, 2024-12-31)');

INSERT INTO tasks VALUES
    (1, 1, '[2024-02-01, 2024-03-01)'); -- OK

INSERT INTO tasks VALUES
    (2, 1, '[2023-12-01, 2024-02-01)'); -- ERROR

Skip Scan su Indici

Query più veloci senza prefisso completo:

-- ✅ PostgreSQL 18 - Skip scan

-- Index multicolonna
CREATE INDEX ON users (country, city, created_at);

-- Query senza country (primo campo)
EXPLAIN ANALYZE
SELECT * FROM users
WHERE city = 'Rome'
  AND created_at > '2024-01-01';

-- Prima: Sequential scan (lento)
-- Ora: Index skip scan!
-- Salta automaticamente valori country
-- Usa comunque l'indice efficacemente

-- Fino a 10-100x più veloce per alcuni query pattern

OR condition optimization:

-- ✅ OR queries con indici

-- Prima: Sequential scan per OR
EXPLAIN ANALYZE
SELECT * FROM users
WHERE country = 'Italy' OR country = 'France';

-- Ora: Index scan multipli + Bitmap OR
-- Usa indice per entrambe le condizioni
-- Combina risultati efficacemente

-- Performance:
-- Sequential scan: 2500ms
-- Skip scan + OR: 45ms → 55x faster!

🚀 Upgrade Improvements

pg_upgrade Enhancements

# ✅ PostgreSQL 18 - Faster upgrades

# Preserva planner statistics!
pg_upgrade \
  --old-datadir /var/lib/pgsql/17/data \
  --new-datadir /var/lib/pgsql/18/data \
  --old-bindir /usr/pgsql-17/bin \
  --new-bindir /usr/pgsql-18/bin

# Prima: Statistics perse → query lente post-upgrade
# Ora: Statistics preservate → performance immediate

# Parallel checks
pg_upgrade --check --jobs=4

# Swap directories (no copy!)
pg_upgrade --swap

# Benefici:
# - Upgrade molto più veloce con molte tabelle
# - No degradazione performance post-upgrade
# - Parallel processing

Major version upgrade speed:

# ✅ Performance comparison

# Database: 500GB, 10k tables

# PostgreSQL 17:
# - Upgrade time: 45 min
# - Post-upgrade ANALYZE: 2 hours
# - Full performance: 2h 45min

# PostgreSQL 18:
# - Upgrade time: 18 min (-60%)
# - Post-upgrade ANALYZE: 0 min (preserved!)
# - Full performance: 18 min (-94%!)

🔐 Security Enhancements

OAuth 2.0 Authentication

-- ✅ PostgreSQL 18 - OAuth support

-- postgresql.conf
auth_method = 'oauth'

-- pg_hba.conf
# TYPE  DATABASE  USER  ADDRESS      METHOD
host    all       all   0.0.0.0/0    oauth

-- Connect con OAuth token
psql "postgresql://dbserver/mydb?oauth_token=..."

-- Integrazione con identity providers:
-- - Azure AD
-- - Okta
-- - Auth0
-- - Google Workspace
-- - Custom OAuth 2.0 servers

TLS 1.3 Cipher Validation

-- ✅ Validazione cipher suites

-- postgresql.conf
ssl_tls13_ciphers = 'TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256'

-- Validation automatica al startup
-- ERROR se cipher non disponibile
-- Previene misconfiguration

md5 Deprecation

-- ⚠️ md5 password auth deprecated

-- pg_hba.conf
# ❌ Deprecated
host all all 0.0.0.0/0 md5

# ✅ Use SCRAM invece
host all all 0.0.0.0/0 scram-sha-256

-- SCRAM benefits:
-- - Stronger hashing
-- - Salt per-user
-- - Channel binding
-- - Replay attack protection

📊 Text Processing

PG_UNICODE_FAST Collation

-- ✅ Performance collation Unicode

-- Create with fast collation
CREATE DATABASE mydb
    TEMPLATE template0
    LOCALE 'en_US.UTF-8'
    LC_COLLATE 'PG_UNICODE_FAST';

-- Benefici:
-- - Full Unicode semantics
-- - Case transformations accelerate
-- - upper/lower functions veloci
-- - LIKE comparisons ottimizzate

-- Benchmark:
-- Default collation: 450ms
-- PG_UNICODE_FAST: 85ms → 5.3x faster!

casefold() Function

-- ✅ Case folding Unicode-aware

SELECT casefold('Hello WORLD'); -- 'hello world'
SELECT casefold('Straße');      -- 'strasse' (German ß)
SELECT casefold('İstanbul');    -- 'istanbul' (Turkish İ)

-- Più preciso di lower() per comparazioni
-- Case-insensitive matching migliorato

🎨 Developer Features

OLD/NEW in RETURNING

-- ✅ Accedi a valori OLD e NEW

-- UPDATE con OLD e NEW
UPDATE products
SET price = price * 1.1
WHERE category = 'electronics'
RETURNING
    OLD.price as old_price,
    NEW.price as new_price,
    (NEW.price - OLD.price) as difference;

-- DELETE con OLD
DELETE FROM logs
WHERE created_at < NOW() - INTERVAL '90 days'
RETURNING OLD.*;

-- INSERT con NEW (come prima)
INSERT INTO users (name, email)
VALUES ('Alice', 'alice@example.com')
RETURNING NEW.*;

-- MERGE con OLD e NEW
MERGE INTO inventory i
USING updates u ON i.product_id = u.product_id
WHEN MATCHED THEN
    UPDATE SET quantity = i.quantity + u.quantity
    RETURNING
        OLD.quantity as before,
        NEW.quantity as after;

CREATE FOREIGN TABLE ... LIKE

-- ✅ Copia schema da local table

-- Local table
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT UNIQUE,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Foreign table con stesso schema
CREATE FOREIGN TABLE remote_users
    LIKE users
    SERVER remote_server;

-- Eredita:
-- - Column types
-- - Constraints (adattati)
-- - Defaults

-- Risparmia duplicazione schema!

💾 Vacuum Improvements

-- ✅ Proactive page freezing

-- Vacuum ora congela più pagine durante regular vacuum
-- Riduce necessità di aggressive vacuum
-- Migliore performance in situazioni di wraparound

-- Configurazione
ALTER TABLE large_table SET (
    vacuum_freeze_min_age = 50000000,
    vacuum_freeze_table_age = 150000000
);

-- Benefici:
-- - Meno I/O per vacuum
-- - Prevenzione wraparound proattiva
-- - Performance più stabili

📊 Monitoring Enhancements

EXPLAIN Improvements

-- ✅ Buffer statistics automatici

EXPLAIN ANALYZE
SELECT * FROM orders WHERE customer_id = 123;

-- Output include automaticamente:
-- Buffers: shared hit=45 read=12 dirtied=3 written=0
-- - hit: trovato in cache
-- - read: letto da disco
-- - dirtied: modificato
-- - written: scritto su disco

-- EXPLAIN ANALYZE VERBOSE più dettagliato:
-- - CPU usage statistics
-- - WAL statistics
-- - Read statistics medie

Index Scan Statistics

-- ✅ Index lookup counts

EXPLAIN (ANALYZE, BUFFERS, VERBOSE)
SELECT * FROM users WHERE email = 'test@example.com';

-- Output ora include:
-- Index Scan using users_email_idx
--   Index Lookups: 1247
--   Index Hits: 1245
--   Index Misses: 2

-- Aiuta a identificare:
-- - Index effectiveness
-- - Cache hit ratio
-- - Tuning opportunities

🎓 Best Practices

1. Usa AIO per Large Scans

-- ✅ Configure for workload

-- OLTP (many small queries)
SET io_method = 'worker';
SET effective_io_concurrency = 200;

-- OLAP (large scans)
SET io_method = 'io_uring'; -- Linux
SET effective_io_concurrency = 1000;

2. UUIDv7 per Time-Series

-- ✅ Eventi, logs, time-series

CREATE TABLE events (
    id UUID PRIMARY KEY DEFAULT uuidv7(),
    event_type TEXT,
    data JSONB,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Ottimo per:
-- - Insert performance
-- - Range queries
-- - Index efficiency

3. Virtual Columns per Calcoli

-- ✅ Simple calculations: virtual
CREATE TABLE products (
    price NUMERIC,
    discount NUMERIC,
    final_price NUMERIC GENERATED ALWAYS AS
        (price * (1 - discount))
);

-- ✅ Complex calculations: stored + index
CREATE TABLE reports (
    data JSONB,
    summary TEXT GENERATED ALWAYS AS (
        complex_calculation(data)
    ) STORED
);
CREATE INDEX ON reports(summary);

4. Temporal Constraints per Business Logic

-- ✅ Prevent overlaps automaticamente

CREATE TABLE reservations (
    resource_id INT,
    period TSTZRANGE,
    PRIMARY KEY (resource_id, period WITHOUT OVERLAPS)
);

-- No application-level checking necessario!

💡 Conclusioni

PostgreSQL 18 porta miglioramenti significativi:

AIO subsystem → 3x performance I/O ✅ Virtual generated columns → no overhead storage ✅ uuidv7() → UUID con ordinamento temporale ✅ Temporal constraints → business logic nel database ✅ Skip scan → indici multicolonna più flessibili ✅ OAuth 2.0 → modern authentication ✅ pg_upgrade → 60% più veloce

Upgrade oggi:

# Check current version
SELECT version();

# Backup database
pg_dumpall > backup.sql

# Install PostgreSQL 18
# Ubuntu/Debian
sudo apt install postgresql-18

# Upgrade
sudo pg_upgrade

# Update statistics
ANALYZE;

Quando usare PostgreSQL 18:

  • ✅ Large sequential scans (AIO)
  • ✅ Time-series data (UUIDv7)
  • ✅ Booking/scheduling systems (temporal constraints)
  • ✅ Generated columns use cases
  • ✅ SSO with OAuth 2.0