Become a member!

PostgreSQL 18: Le Novità per Sviluppatori e DBA

  • 👉 This article is available in english.
  • 👉 Questo articolo è disponibile anche in italiano.
  • 👉 Este artículo también está disponible en español.

PostgreSQL 18, rilasciato il 25 settembre 2025, rappresenta una delle versioni più significative degli ultimi anni. Questa release introduce cambiamenti architetturali fondamentali e numerose funzionalità pensate per migliorare la vita degli sviluppatori. In questo articolo esploreremo le migliorie più importanti, con particolare attenzione a come queste possano beneficiare chi lavora con Delphi e DelphiMVCFramework.

🚀 L’I/O Asincrono: Un Cambio di Paradigma Architetturale

La novità più rivoluzionaria di PostgreSQL 18 è senza dubbio l’introduzione del sottosistema di I/O asincrono (AIO). Per decenni, PostgreSQL ha utilizzato un modello di I/O sincrono: quando un processo backend necessitava di leggere dati dal disco, emetteva una chiamata di lettura e rimaneva in attesa fino al completamento dell’operazione. Questo significava che il processo era completamente bloccato durante le operazioni di I/O, incapace di eseguire calcoli o altre elaborazioni.

Con PostgreSQL 18, questo modello cambia radicalmente. Il nuovo sottosistema AIO permette al database di avviare multiple operazioni di lettura e continuare a elaborare dati mentre attende i risultati. È come passare da uno chef che prepara un piatto alla volta, attendendo che ogni ingrediente arrivi prima di iniziare il successivo, a uno chef che ordina tutti gli ingredienti contemporaneamente e lavora su ciò che è già disponibile mentre il resto sta arrivando.

Tre Modalità di I/O

PostgreSQL 18 introduce il parametro di configurazione io_method che permette di scegliere tra tre diverse implementazioni:

sync: Mantiene il comportamento tradizionale di PostgreSQL 17 e versioni precedenti. Utile per troubleshooting o per sistemi legacy.

worker (default): Utilizza processi worker dedicati in background per gestire le operazioni di I/O. Quando una query necessita di dati dal disco, PostgreSQL invia la richiesta a un worker disponibile invece di bloccare il processo principale. Il numero di worker è controllato dal parametro io_workers (default: 3). Questa è l’opzione predefinita ed è cross-platform (funziona su Linux, Windows, macOS).

io_uring (solo Linux): Sfrutta l’interfaccia io_uring del kernel Linux (versione 5.1+), creando un buffer condiviso tra PostgreSQL e il kernel per ridurre l’overhead delle system call. Questa modalità offre generalmente le migliori prestazioni ma richiede un kernel Linux recente e PostgreSQL compilato con il supporto --with-liburing. In ambienti cloud Linux, questa è la scelta ottimale per massimizzare le prestazioni.

Operazioni Supportate

Nella versione 18, l’I/O asincrono è attivo per le seguenti operazioni:

  • Sequential Scan: scansioni complete di tabelle
  • Bitmap Heap Scan: scansioni basate su bitmap di indici
  • VACUUM: operazioni di manutenzione e pulizia

Le operazioni di scrittura, incluse quelle sul Write-Ahead Log (WAL), rimangono sincrone per preservare le garanzie ACID del database.

📊 Performance Misurabili

I benchmark effettuati in ambienti cloud (AWS, Azure) mostrano miglioramenti impressionanti:

  • ⚡ Fino a 2-3x di throughput per workload con intensive operazioni di lettura
  • ⚡ Riduzione significativa della latenza I/O, specialmente su storage cloud
  • ⚡ Miglior utilizzo delle risorse hardware esistenti

Un test eseguito su AWS con istanza c7i.8xlarge (32 vCPU, 64GB RAM) e volume EBS io2 (100GB, 20000 IOPS) ha mostrato che con io_uring si raggiungono velocità di lettura di circa 3.4 GB/sec, contro i 2.6 GB/sec del metodo sync - un miglioramento del 30%. La differenza è ancora più marcata in scenari con latenza I/O elevata, tipici degli ambienti cloud.

Cosa Significa per gli Sviluppatori Delphi

Per chi sviluppa con Delphi e DelphiMVCFramework, questi miglioramenti si traducono in:

  • Query più veloci su dataset di grandi dimensioni
  • Migliori prestazioni per le operazioni di sequential scan e bitmap heap scan
  • Riduzione dei tempi di VACUUM, fondamentale per la manutenzione di database in produzione

I vantaggi nelle letture sono già sostanziali per la maggior parte delle applicazioni web e REST API sviluppate con DMVC, specialmente quelle che eseguono query analitiche o reportistiche su grandi volumi di dati.

Formazione PostgreSQL: Tutte le prossime sessioni del corso PostgreSQL per Sviluppatori e DBA includeranno una sezione dedicata alle novità di PostgreSQL 18, con esempi pratici su I/O asincrono, UUIDv7, virtual generated columns e temporal constraints. Il corso è disponibile in italiano e inglese, sia on-site che da remoto, con focus su casi d’uso reali e best practices per applicazioni in produzione.

🔑 UUIDv7: Finalmente UUID Efficienti come Primary Key

Una delle funzionalità più attese dagli sviluppatori è il supporto nativo per UUIDv7. Chi ha mai utilizzato UUID come chiave primaria conosce bene il problema: le UUID tradizionali (v4) sono completamente casuali e questo causa una frammentazione severa degli indici B-tree, con conseguente degrado delle prestazioni nelle operazioni di INSERT.

Il Problema delle UUID Random

Immagina di avere una biblioteca dove i libri vengono inseriti in posizioni casuali sugli scaffali. Ogni nuovo libro potrebbe finire all’inizio, alla fine o in mezzo alla collezione esistente. Questo è ciò che accade con UUIDv4: ogni nuovo record viene inserito in una posizione casuale dell’indice B-tree, causando:

  • Page split frequenti
  • Cache inefficiente
  • Aumento dell’I/O per le operazioni di scrittura
  • Degrado progressivo delle prestazioni

La Soluzione: UUIDv7

UUIDv7 risolve elegantemente questo problema incorporando un timestamp Unix (in millisecondi) nei primi 48 bit dell’identificatore, seguito da 12 bit di precisione sub-millisecondo per garantire l’unicità. Questo significa che le nuove UUID sono naturalmente ordinate cronologicamente e vengono inserite sequenzialmente alla fine dell’indice, proprio come un BIGSERIAL.

Caratteristica importante: L’implementazione PostgreSQL garantisce la monotonicità per tutti i valori UUIDv7 generati dalla stessa sessione (stesso processo backend), anche quando generati nello stesso millisecondo.

-- Genera un UUID v7 ordinato per timestamp corrente
SELECT uuidv7();
-- Risultato: 01980de8-ad3d-715c-b739-faf2bb1a7aad

-- Genera un UUID v7 per un momento specifico (con intervallo opzionale)
SELECT uuidv7(NOW() - INTERVAL '1 hour');

-- Estrai il timestamp da un UUID v7
SELECT uuid_extract_timestamp(uuidv7());
-- Risultato: 2025-09-26 14:30:15.123+02

-- Verifica la versione dell'UUID
SELECT uuid_extract_version(uuidv7());
-- Risultato: 7

-- Utilizzo come chiave primaria
CREATE TABLE orders (
    id UUID PRIMARY KEY DEFAULT uuidv7(),
    customer_id BIGINT NOT NULL,
    total DECIMAL(10,2),
    created_at TIMESTAMPTZ DEFAULT NOW()
);

✨ Vantaggi Concreti

I benefici di UUIDv7 sono molteplici:

  1. Prestazioni da BIGSERIAL: inserimenti sequenziali che mantengono l’indice compatto
  2. Unicità globale: perfetto per sistemi distribuiti e microservizi
  3. Ordinamento naturale: possibilità di ordinare per ID senza colonne aggiuntive
  4. Compatibilità: standard da 128-bit compatibile con i sistemi esistenti

Per chi usa DelphiMVCFramework in architetture distribuite o microservizi, UUIDv7 rappresenta la soluzione ideale. È possibile generare chiavi primarie su qualsiasi nodo dell’applicazione senza coordinazione centrale, mantenendo al contempo prestazioni eccellenti.

Migrazione da UUIDv4 a UUIDv7

Se hai già una tabella con UUIDv4 e vuoi iniziare a usare UUIDv7 per i nuovi record:

-- Cambia il default della colonna
ALTER TABLE orders
ALTER COLUMN id SET DEFAULT uuidv7();

-- I record esistenti mantengono il loro UUIDv4
-- I nuovi record useranno UUIDv7
-- Entrambi possono coesistere nella stessa tabella

Nelle tue applicazioni Delphi/DMVC, non serve cambiare nulla: continuerai a mappare la colonna come TGUID o string, e il database si occuperà di generare UUIDv7 per i nuovi inserimenti.

💡 Virtual Generated Columns: Calcoli On-Demand

PostgreSQL supportava già le “stored generated columns” dalla versione 12, ma queste occupavano spazio su disco perché il valore veniva calcolato e memorizzato durante INSERT/UPDATE. PostgreSQL 18 introduce le virtual generated columns come opzione predefinita.

Come Funzionano

Le colonne virtuali non occupano spazio su disco (tecnicamente usano solo 1 bit nel bitmap dei null). Il valore viene calcolato al momento della lettura, quando la colonna viene effettivamente richiesta in una query.

CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    base_price DECIMAL(10,2) NOT NULL,
    tax_rate DECIMAL(5,4) DEFAULT 0.22, -- IVA italiana
    discount_rate DECIMAL(5,4) DEFAULT 0.00,
    
    -- Virtual (default in PostgreSQL 18)
    selling_price DECIMAL(10,2) GENERATED ALWAYS AS (
        base_price * (1 + tax_rate) * (1 - discount_rate)
    ),
    
    -- Stored solo se necessario indicizzare o replicare
    profit_margin DECIMAL(10,2) GENERATED ALWAYS AS (
        base_price * discount_rate
    ) STORED
);

Quando Usare Virtual vs Stored

Usa Virtual quando:

  • Il calcolo è veloce (semplici operazioni aritmetiche)
  • I dati sottostanti cambiano frequentemente
  • Vuoi risparmiare spazio su disco
  • Non hai bisogno di indicizzare la colonna

Usa Stored quando:

  • Devi creare un indice sulla colonna calcolata
  • Il calcolo è complesso e pesante
  • Hai bisogno di replicazione logica del valore calcolato
  • La colonna viene letta molto più spesso di quanto venga scritta

Caso d’Uso Pratico: E-commerce

Supponiamo di sviluppare un sistema e-commerce con DelphiMVCFramework. Le colonne virtuali sono perfette per:

CREATE TABLE cart_items (
    id SERIAL PRIMARY KEY,
    cart_id INTEGER NOT NULL,
    product_id INTEGER NOT NULL,
    quantity INTEGER NOT NULL,
    unit_price DECIMAL(10,2) NOT NULL,
    
    -- Calcolato on-demand, zero overhead su INSERT
    subtotal DECIMAL(10,2) GENERATED ALWAYS AS (
        quantity * unit_price
    ),
    
    -- Anche formattazioni possono essere virtuali
    display_price VARCHAR(20) GENERATED ALWAYS AS (
        '€ ' || ROUND(quantity * unit_price, 2)::TEXT
    )
);

Quando l’utente aggiunge articoli al carrello, le operazioni di INSERT sono velocissime perché non c’è overhead di calcolo. Il subtotal e display_price vengono calcolati solo quando effettivamente servono (es. nella visualizzazione del carrello).

🎯 Skip Scan su Indici B-tree Multicolonna

Una miglioria apparentemente minore ma con impatto significativo è il supporto per “skip scan” sugli indici B-tree multicolonna. Prima di PostgreSQL 18, per utilizzare un indice su (region, category, date), era necessario specificare le colonne nell’ordine esatto, partendo da region (la regola “left-most”).

Il Problema Tradizionale

CREATE INDEX idx_sales ON sales (region, category, date);

-- Questa query NON poteva usare l'indice in PostgreSQL 17
SELECT * FROM sales 
WHERE category = 'Electronics' 
AND date > '2024-01-01';

La query sopra richiedeva un full table scan perché region (prima colonna dell’indice) non era specificata nel WHERE.

La Soluzione: Skip Scan

PostgreSQL 18 introduce la capacità di “saltare” le colonne iniziali dell’indice, consentendo l’utilizzo parziale dell’indice anche quando non tutte le colonne prefisso sono specificate.

-- In PostgreSQL 18, questa query USA l'indice!
SELECT * FROM sales 
WHERE category = 'Electronics' 
AND date > '2024-01-01';

Questo è particolarmente utile quando:

  • La cardinalità della prima colonna è bassa (pochi valori distinti)
  • Si hanno query che utilizzano combinazioni diverse delle colonne indicizzate
  • Non si vuole creare molteplici indici per coprire tutte le possibili combinazioni

Importante: Il query planner decide automaticamente se usare lo skip scan basandosi sulle statistiche della tabella. Lo skip scan è più efficace quando la colonna “saltata” ha pochi valori distinti. Non è necessario abilitarlo manualmente - PostgreSQL lo usa quando è vantaggioso.

Per gli sviluppatori che usano DMVC, questo significa poter progettare indici più flessibili senza preoccuparsi troppo dell’ordine delle colonne nelle WHERE clause.

🔄 Enhanced RETURNING: Accesso a OLD e NEW Values

PostgreSQL 18 estende la clausola RETURNING per permettere l’accesso simultaneo ai valori vecchi (OLD) e nuovi (NEW) durante operazioni di UPDATE, DELETE e MERGE.

-- Traccia i cambiamenti in un UPDATE
UPDATE customers
SET email = 'newemail@example.com',
    last_updated = NOW()
WHERE id = 1234
RETURNING 
    OLD.email as previous_email,
    NEW.email as current_email,
    OLD.last_updated as previous_update,
    NEW.last_updated as current_update;

Casi d’Uso Pratici

Audit Trail Automatico:

-- Inserisci in una tabella di audit durante l'UPDATE
WITH updated AS (
    UPDATE products
    SET price = price * 1.10
    WHERE category = 'Premium'
    RETURNING
        id,
        OLD.price as old_price,
        NEW.price as new_price,
        CURRENT_USER as changed_by
)
INSERT INTO price_history (product_id, old_price, new_price, changed_by)
SELECT id, old_price, new_price, changed_by FROM updated;

Nota importante: Per operazioni UPDATE, sia OLD che NEW contengono valori; per INSERT, OLD è NULL; per DELETE, NEW è NULL. È anche possibile usare alias per OLD e NEW: RETURNING WITH (OLD AS o, NEW AS n) o.email, n.email.

Per chi sviluppa REST API con DelphiMVCFramework, questa feature permette di implementare risposte più ricche senza query aggiuntive:

// Nel controller DMVC
function TProductController.UpdatePrice(const ProductId: Integer; 
    const NewPrice: Currency): IMVCResponse;
var
  Result: TJSONObject;
begin
  Result := TJSONObject.Create;
  try
    // Single query che restituisce sia vecchio che nuovo valore
    Connection.ExecSQL(
      'UPDATE products SET price = :new_price WHERE id = :id ' +
      'RETURNING ' +
      '  OLD.price as previous_price, ' +
      '  NEW.price as current_price, ' +
      '  OLD.updated_at as previous_update',
      [NewPrice, ProductId]
    );
    
    // Popola il JSON con i dati restituiti
    Result.AddPair('previous_price', Connection.Fields[0].AsCurrency);
    Result.AddPair('current_price', Connection.Fields[1].AsCurrency);
    
    Render(Result);
  finally
    Result.Free;
  end;
end;

📅 Temporal Constraints: WITHOUT OVERLAPS

PostgreSQL 18 introduce il supporto per vincoli temporali usando la clausola WITHOUT OVERLAPS, perfetta per gestire prenotazioni, pianificazioni e qualsiasi scenario dove periodi di tempo non devono sovrapporsi.

-- Prerequisito: installare l'estensione btree_gist
CREATE EXTENSION IF NOT EXISTS btree_gist;

CREATE TABLE room_bookings (
    room_id INTEGER,
    guest_name VARCHAR(100),
    booking_period tstzrange,

    -- Previene sovrapposizioni temporali per la stessa stanza
    PRIMARY KEY (room_id, booking_period WITHOUT OVERLAPS)
);

-- Questo INSERT funziona
INSERT INTO room_bookings VALUES
(101, 'Mario Rossi', '[2025-10-01 14:00, 2025-10-01 16:00)');

-- Questo fallisce: periodo sovrapposto!
INSERT INTO room_bookings VALUES
(101, 'Luca Bianchi', '[2025-10-01 15:00, 2025-10-01 17:00)');
-- ERROR: conflicting key value violates exclusion constraint

Questo è incredibilmente utile per sistemi di prenotazione, calendari, gestione turni e qualsiasi scenario dove la sovrapposizione temporale deve essere evitata a livello di database. Nota: i vincoli temporali richiedono l’estensione btree_gist e utilizzano internamente indici GiST invece di B-tree.

⚙️ Upgrade Più Veloci e Meno Invasivi

PostgreSQL 18 migliora significativamente l’esperienza di upgrade tra major version, un aspetto cruciale per sistemi in produzione.

Preservazione delle Statistiche del Planner

Novità importante: le statistiche del query planner vengono preservate durante l’upgrade. In passato, dopo un upgrade con pg_upgrade, era necessario eseguire un ANALYZE completo su tutto il database prima che il planner avesse informazioni sufficienti per generare piani di esecuzione ottimali. Questo poteva richiedere ore su database di grandi dimensioni.

Con PostgreSQL 18, le statistiche esistenti vengono migrate, permettendo al database di essere immediatamente operativo con performance ottimali.

Miglioramenti a pg_upgrade

  • Flag --jobs: permette di parallelizzare i controlli pre-upgrade
  • Nuova opzione --no-statistics: consente di saltare il trasferimento delle statistiche se non necessario
  • Migliore gestione di database con molti oggetti (tabelle, sequence, indici)

Post-upgrade: Dopo l’upgrade, è consigliato eseguire:

  1. vacuumdb --all --analyze-in-stages --missing-stats-only per generare rapidamente statistiche minime per le relazioni che ne sono prive
  2. vacuumdb --all --analyze-only per aggiornare le statistiche cumulative

Per chi gestisce database PostgreSQL in produzione con applicazioni Delphi/DMVC, questi miglioramenti riducono significativamente la finestra di downtime necessaria per gli upgrade.

OAuth 2.0: Autenticazione Moderna

PostgreSQL 18 introduce il supporto per OAuth 2.0, permettendo l’integrazione con sistemi di Single Sign-On (SSO) moderni come Azure AD, Okta, Auth0, ecc.

# In pg_hba.conf
hostssl all all 0.0.0.0/0 oauth

La validazione dei token OAuth viene gestita tramite librerie estensibili, configurabili attraverso il parametro oauth_validator_libraries.

Questo è particolarmente rilevante per applicazioni enterprise sviluppate con DelphiMVCFramework che devono integrarsi con infrastrutture di autenticazione centralizzate.

🎁 Altre Migliorie Degne di Nota

Parallel GIN Index Builds

La creazione di indici GIN (usati per JSON, array, full-text search) può ora essere parallelizzata, riducendo significativamente i tempi di creazione su dataset di grandi dimensioni.

Enhanced EXPLAIN

EXPLAIN ANALYZE ora mostra automaticamente l’uso dei buffer e fornisce metriche dettagliate su CPU, WAL e statistiche di I/O per ogni nodo del piano di esecuzione.

-- Più informazioni senza flag aggiuntivi
EXPLAIN ANALYZE SELECT * FROM large_table WHERE category = 'Active';

-- Include CPU, WAL, statistiche di lettura
EXPLAIN (ANALYZE, VERBOSE) 
SELECT * FROM orders 
WHERE date > CURRENT_DATE - INTERVAL '30 days';

Data Checksum Abilitati di Default

I nuovi cluster PostgreSQL 18 hanno i data checksum abilitati per default quando si inizializza un database con initdb. Questo migliora la capacità di rilevare corruzione dei dati causata da problemi dell’I/O system, particolarmente importante in ambienti cloud. Se per qualche motivo si vuole disabilitare questa feature (con un piccolo impatto sulle performance), è possibile usare il flag --no-data-checksums durante l’inizializzazione.

Miglioramenti al Wire Protocol

PostgreSQL 18 introduce la versione 3.2 del wire protocol, il primo aggiornamento dal 2003 (PostgreSQL 7.4). Anche se libpq continua a usare la versione 3.0 per default, questo apre la strada a futuri miglioramenti nei client.

Considerazioni per Sviluppatori Delphi e DelphiMVCFramework

Preparazione alla Migrazione

Se stai sviluppando con Delphi e utilizzi FireDAC o componenti nativi PostgreSQL:

  1. Testa con la Beta: PostgreSQL 18 è disponibile via Docker per test:
docker run --name pg18 \
  -e POSTGRES_PASSWORD=postgres \
  -p 5432:5432 \
  postgres:18
  1. Verifica Compatibilità: la maggior parte delle applicazioni esistenti funzionerà senza modifiche, ma è sempre meglio testare

  2. Full-Text Search: se usi full-text search o pg_trgm, potrebbe essere necessario reindicizzare dopo l’upgrade

  3. Rivedi gli Indici: con skip scan, potresti essere in grado di semplificare la strategia di indicizzazione

Opportunità di Ottimizzazione

Con PostgreSQL 18, considera di:

  1. Migrare a UUIDv7 se usi UUID come chiavi primarie in sistemi distribuiti
  2. Usare Virtual Generated Columns per calcoli frequenti che attualmente fai in Delphi
  3. Configurare I/O Asincrono per workload con intensive operazioni di lettura
  4. Implementare Temporal Constraints per scenari di booking/prenotazione

Esempio: Ottimizzazione di una REST API DMVC

// Prima: calcolo lato applicazione
function TOrderController.GetOrderTotal(OrderId: Integer): Currency;
var
  Items: TDataSet;
  Total: Currency;
begin
  Total := 0;
  Items := GetOrderItems(OrderId);
  try
    while not Items.Eof do
    begin
      Total := Total + Items.FieldByName('quantity').AsInteger * 
                       Items.FieldByName('price').AsCurrency;
      Items.Next;
    end;
    Result := Total;
  finally
    Items.Free;
  end;
end;

// Dopo: con Virtual Generated Column
// Nel database:
// ALTER TABLE order_items 
// ADD COLUMN line_total DECIMAL(10,2) 
// GENERATED ALWAYS AS (quantity * price);

function TOrderController.GetOrderTotal(OrderId: Integer): Currency;
begin
  // Singola query, calcolo efficiente lato database
  Result := Connection.ExecSQLScalar(
    'SELECT SUM(line_total) FROM order_items WHERE order_id = :id',
    [OrderId]
  );
end;

🎯 Conclusioni

PostgreSQL 18 rappresenta un salto qualitativo significativo, sia dal punto di vista architetturale con l’introduzione dell’I/O asincrono, sia per le numerose funzionalità pensate per semplificare la vita degli sviluppatori.

Per chi sviluppa con Delphi e DelphiMVCFramework, questa versione offre:

  • Prestazioni migliorate grazie all’I/O asincrono, particolarmente evidente in ambienti cloud
  • UUIDv7 per chiavi primarie efficienti in architetture distribuite
  • Virtual Generated Columns per spostare logica di business dal codice Delphi al database quando appropriato
  • Upgrade più veloci con minore impatto sulla produzione
  • Nuove possibilità di modellazione con temporal constraints e enhanced RETURNING

La filosofia di PostgreSQL di mantenere la retrocompatibilità significa che la migrazione da versioni precedenti sarà generalmente smooth, permettendo di beneficiare immediatamente dei miglioramenti di performance senza dover riscrivere codice esistente.

PostgreSQL 18 è disponibile per il download dal sito ufficiale postgresql.org e può essere testato immediatamente tramite container Docker per valutare l’impatto delle nuove funzionalità sulle proprie applicazioni Delphi e DMVC.

Approfondimenti e Formazione

Le funzionalità presentate in questo articolo rappresentano solo una panoramica delle novità di PostgreSQL 18. Per chi desidera approfondire questi argomenti con esempi pratici, ottimizzazioni specifiche per il proprio dominio applicativo e sessioni hands-on, il corso PostgreSQL per Sviluppatori e DBA offre un percorso strutturato che copre sia le fondamenta del database che le feature avanzate, incluse tutte le novità della versione 18.

Il corso può essere erogato in italiano o inglese, in modalità on-site presso la vostra sede o completamente da remoto, con contenuti adattabili alle specifiche esigenze del team e del settore di riferimento.


Articolo basato su documentazione ufficiale PostgreSQL 18, benchmarks della community e analisi di use case reali per applicazioni enterprise.

Comments

comments powered by Disqus