PostgreSQL 19 🐟 Nuove funzionalita'

La versione PostgreSQL 19 introduce nuove funzionalita' all'RDBMS Open Source piu' avanzato ed oggi [NdA 2026-04-01] e' il giorno giusto per parlarne: ecco quindi pubblicato questo documento!

In generale la versione 19 e' un'evoluzione della versione precedente con nuove funzionalita' ma anche con migliori prestazioni ed un costante miglioramento degli aspetti di gestione delle basi dati.

In questo documento sono riportati in dettaglio alcuni nuovi elementi introdotti nella versione 19, riportando esempi pratici di utilizzo:

Ma le novita' non sono solo queste... continuate a leggere!

pg_plan_advice

EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM join_fact f JOIN join_dim d ON f.dim_id = d.id; QUERY PLAN ------------------------------------ Hash Join Hash Cond: (f.dim_id = d.id) -> Seq Scan on join_fact f -> Hash -> Seq Scan on join_dim d Generated Plan Advice: JOIN_ORDER(f d) HASH_JOIN(d) SEQ_SCAN(f d) NO_GATHER(f d) SET pg_plan_advice.advice = 'JOIN_ORDER(d f)'; SELECT * FROM join_fact f JOIN join_dim d ON f.dim_id = d.id;

L'esempio dovrebbe essere chiaro: possiamo usare gli HINT con PostgreSQL!
Non si tratta propriamente di HINT ma di suggerimenti passati all'ottimizzatore, il funzionamento tuttavia e' analogo.

Il plan advice opera in modo integrato e consistente con l'EXPLAIN che puo' indicare se e quanto un advice e' stato applicato.
Oltre a pg_plan_advice e' disponibile anche pg_stash_advice [NdA la cui configurazione e' banale (simile a quella dell'auto_explain), poi e' sufficiente lo statement CREATE EXTENSION pg_stash_advice per poterla utilizzare] che consente di legare un advice_string ad uno specifico query_id.

Si tratta solo di una estensione come contrib module ma e', a mio avviso, un importante cambio di approccio nella gestione dei piani d'esecuzione.
La posizione ufficiale (eg. Optimizer and Query Hints Discussion) era infatti che se l'ottimizzatore sbagliava nella scelta del piano il problema era altrove ed era necessario correggerlo alla fonte (disegno logico/fisico errato, ANALYZE non aggiornate, query da riscrivere) e non indicando un piano alternativo. E' vero ma nella pratica, solo in casi particolari e con la dovuta cautela, fornire un suggerimento o un HINT Advice al planner consente di aggirare velocemente un problema per poi risolverne la causa con il tempo necessario. Lo sanno bene sia la community che ha sviluppato l'ottima estensione pg_hint_plan [NdA non e' l'unica estensione arrivata dal sol di levante] che chi ha sviluppato fork di PostgreSQL con soluzioni alternative (eg. Aurora PostgreSQL EDB PostgreSQL Advanced Server). Ma pg_hint_plan, sebbene disponibile sulla maggioranza dei servizi di PostgreSQL in Cloud, non fa parte delle core extensions e quindi richiede un configurazione specifica; mentre pg_plan_advice ha una configurazione standard ed e' integrato con l'EXPLAIN PLAN.

Come sempre la documentazione ufficiale riporta tutti i dettagli ma una descrizione piu' sintetica si trova in questa paginetta.

Graph Query

Un esempio spesso e' piu' utile di una precisa ma noiosa descrizione!

CREATE TABLE person ( id int PRIMARY KEY, name text, age int ); CREATE TABLE knows ( src_id int REFERENCES person(id), dst_id int REFERENCES person(id), since date ); CREATE PROPERTY GRAPH social_graph VERTEX TABLES ( person KEY (id) LABEL Person PROPERTIES (name, age) ) EDGE TABLES ( knows KEY (src_id, dst_id) SOURCE KEY (src_id) REFERENCES person (id) DESTINATION KEY (dst_id) REFERENCES person (id) LABEL Knows PROPERTIES (since) );

PG19 introduce la possibilita' di rappresentare le strutture a grafo in modo nativo nel modello relazionale indicando come i nodi (vertex) si relazionano (edge) tra loro, come definito nello standard SQL/PGQ (ISO/IEC 9075-16:2023). Nel catalogo sono state aggiunte 5 nuove viste: pg_propgraph_element, pg_propgraph_label, pg_propgraph_property, pg_propgraph_element_label, e pg_propgraph_label_property che descrivono ogni proprieta' dei grafi.

Una volta definito il grafo possiamo interrogarlo con una clausola SQL:

SELECT social_name FROM GRAPH_TABLE (social_graph MATCH (p1 IS person)-[IS knows]->(p2 IS person WHERE p2.age between 30 and 40) COLUMNS (p1.name AS social_name));

Un elemento di internal: PostgreSQL utilizza i metadati con la definizione del grafo per convertire le richieste delle query grafiche in un normale statement SQL che viene eseguito poi normalmente dal Planner:

                                    QUERY PLAN                                    
----------------------------------------------------------------------------------
 Nested Loop  (cost=28.23..65.77 rows=10 width=32)
   ->  Hash Join  (cost=28.07..63.85 rows=10 width=4)
         Hash Cond: (knows.dst_id = person_1.id)
         ->  Seq Scan on knows  (cost=0.00..30.40 rows=2040 width=8)
         ->  Hash  (cost=28.00..28.00 rows=6 width=4)
               ->  Seq Scan on person person_1  (cost=0.00..28.00 rows=6 width=4)
                     Filter: ((age >= 30) AND (age <= 40))
   ->  Index Scan using person_pkey on person  (cost=0.15..0.19 rows=1 width=36)
         Index Cond: (id = knows.src_id)
 Generated Plan Advice:
   JOIN_ORDER(knows person#2 person)
   NESTED_LOOP_PLAIN(person)
   HASH_JOIN(person#2)
   SEQ_SCAN(knows person#2)
   INDEX_SCAN(person public.person_pkey)
   NO_GATHER(person knows person#2)

Come sempre la documentazione ufficiale riporta tutti i dettagli ma una descrizione piu' sintetica si trova in questa paginetta.

REPACK

Si tratta di un comando molto semplice, con funzionalita' analoghe ai comandi VACUUM e CLUSTER ma con un'importante novita': con REPACK (CONCURRENTLY) table_name; viene ricostruita fisicamente la tabella come con un VACUUM FULL ma con un lock analogo ad un normale VACUUM.

Come e' noto un VACUUM FULL utilizza il pesante ACCESS EXCLUSIVE per tutto il tempo dell'operazione mentre il nuovo comando REPACK con l'opzione CONCURRENTLY utilizza il lock ACCESS EXCLUSIVE solo all'inizio ed alla fine dell'attivita' per l'inizializzazione ed il cambio del relfilenode, ed un meno pesante SHARE UPDATE EXCLUSIVE per effettuare la lunga e pesante ricostruzione fisica dell'oggetto: PG19 REPACK

La richiesta dei due ACCESS EXCLUSIVE, seppur di brevissima durata, e' comunque un'importante differenza rispetto a quanto richiesto da un normale VACUUM perche' l'avvio di un REPACK (CONCURRENTLY) puo' essere bloccato da una semplice SELECT. Il REPACK e' una funzionalita' molto importante ed attesa da tempo.

INSERT ... ON CONFLICT DO SELECT

Una funzionalita' spesso richiesta nelle applicazioni e' quella di ricercare un record e, se non lo si trova, inserirlo. L'operazione richiesta e' una Get-or-create. Dalla versione 19 PostgreSQL riesce a fornire questa funzionalita' con un solo efficiente statement SQL:

INSERT INTO users (email, name) VALUES ('john@example.com', 'John Smith') ON CONFLICT (email) DO SELECT RETURNING *;

Per ottenere lo stesso risultato in precedenza era possibile utilizzare il seguente statement:

INSERT INTO users (email, name) VALUES ('john@example.com', 'John Smith') ON CONFLICT (email) DO UPDATE SET email = EXCLUDED.email RETURNING *;

Questo statement pero' ha un importante effetto collaterale: genera un'update ad ogni conflitto e questo con PostgreSQL vuol dire che viene creata una nuova tupla, che la tupla precedente diverra' una dead tuple che dovra' essere ripulita dal vacuum e che, se non e' possibile eseguire un hot update debbono essere aggiornati anche tutti gli indici. Dal punto di vista prestazionale c'e' molta differenza!
La funzionalita' di UPSERT e' disponibile in PostgreSQL dalla 9.5, con la nuova clausola e' possibile ottenere in modo efficiente i seguenti diversi comportamenti da uno statement di INSERT:

Action Note
ON CONFLICT Error on duplicates. Standard SQL INSERT behaviour.
DO NOTHING Fire-and-forget. Ignores duplicates.
DO UPDATE UPSERT. Always use newest values.
DO SELECT Get-or-create. Idempotent operations.

Fast Aggregate

Sono frequenti query complesse che effettuano molti join e operazioni di aggregazione. Il planner di PostgreSQL ha sembre trattato questi statement eseguendo in modo ordinato prima tutti i join ed aggregando al termine. Vediamo un semplice esempio:

SELECT d.category, count(*) FROM join_dim AS d, join_fact AS f WHERE d.id =f.dim_id GROUP BY d.category;

Con la versione 19 PostgreSQL il planner puo' utilizzare in alternativa la tecnica di aggregare i dati prima e solo al termine effettuare il lookup:

Aggregate Execution Plan PG19 Eager Aggregate Execution Plan

Nel caso visualizzato come esempio e' stato disabilitato il parallelismo (per rendere piu' facile il confronto) e sono state utilizzate due tabelle con 50 categorie, 5.000 dimensioni, e 2.000.000 fatti. Con la eager aggreate della versione 19 i tempi si sono ridotti del 50%. I vantaggi sono ancora maggiori quando vi sono piu' lookup o il raggruppamento ha una cardinalita' inferiore.

Certo con le precedenti versioni era possibile riscrivere la query utilizzando CTE o query annidate ottenendo tempi analoghi:

SELECT d.category, f.cnt FROM ( SELECT dim_id, COUNT(*) AS cnt FROM join_fact GROUP BY dim_id ) AS f JOIN join_dim AS d ON d.id = f.dim_id;

Ma su query complesse e' molto meglio che sia il Planner a determinare l'execution path migliore.
La funzionalita' e' attiva per default, si puo' disabilitare con set enable_eager_aggregate=off; ma nella maggior parte dei casi porta a vantaggi significativi.

Varie ed eventuali

Quelle riportate fino ad ora non sono le uniche variazioni importanti della versione 19 di PostgreSQL. Altre novita' interessanti sono:

Avro' dimenticato qualcosa? Certamente si!

Ecco il contenuto completo della matrice delle nuove funzionalita' di PostgreSQL 19: April the 1st is too early!

Il riferimento finale e' la documentazione ufficiale [NdE per quando ci sara'... al momento documentazione e' in sviluppo] e l'ottimo prospetto riassuntivo di pgPedia [NdA per i piu' audaci ci sono i Commitfest da seguire ed analizzare le feature committed].
A volte succede che qualche' funzionalita' prevista per una versione venga spostata piu' avanti nel tempo... l'obiettivo della comunita' e' quello di mantenere stabile e ben documentato PostgreSQL. Quindi saremo certi dell'elenco completo solo al rilascio ma alcuni commit sono gia' stati spostati al successivo CF, sigh!

PostgreSQL e' in costante evoluzione! Per il passato: le novita' della versione 18, le novita' della versione 17, le novita' della versione 16, le novita' della versione 15, le novita' della versione 14, evoluzione di PostgreSQL, ...
Il tuo server puzza e' il famigerato ma utile documento con la storia delle versioni di tutti i software che ritengo piu' significativi, ed ovviamente anche di PostgreSQL.

Per il futuro... in realta' il futuro di PostgreSQL e' gia' adesso perche' gli sviluppi per la versione 19 sono gia' iniziati [NdA 2025-07] ed ad aprile 2026 si arrivera' al feature freeze in cui vengono fissate le nuove funzionalita' che verranno aggiunte nella versione. Arriveranno quindi le prime Beta e quindi una o piu' RC (Release Candidate) a seconda delle necessita'. Indicativamente a settembre 2026 la nuova versione PG19 sara' disponibile come produzione.


Titolo: PostgreSQL 19 - Nuove funzionalita'
Livello: Avanzato (3/5)
Data: 1 Aprile 2026 🐟
Versione: 1.0.3 - 1 Maggio 2026
Autore: mail [AT] meo.bogliolo.name