PostgreSQL 17� What's new?
Aychin Gasimov
Senior Partner SA EMEA DATA & AI
AWS
PostgreSQL release cycle��
Version | Current minor | Supported | First Release | Final Release |
17 | 17.0 | Yes | September 26, 2024 | November 8, 2029 |
16 | 16.4 | Yes | September 14, 2023 | November 9, 2028 |
15 | 15.8 | Yes | October 13, 2022 | November 11, 2027 |
14 | 14.13 | Yes | September 30, 2021 | November 12, 2026 |
13 | 13.16 | Yes | September 24, 2020 | November 13, 2025 |
12 | 12.20 | Yes | October 3, 2019 | November 14, 2024 |
Performance
VACUUM performance and optimization
1. Heap scan
2. Index cleanup
3. Heap cleanup
Memory array limited to 1GB
autovacuum_work_mem
Before
Tuple IDs
[prune, then freeze]
1. Heap scan
2. Index cleanup
3. Heap cleanup
TIDStore implemented using Adaptive Radix Tree (ART)
autovacuum_work_mem
PostgreSQL 17
~20x less mem
[freeze and prune]
VACUUM performance and optimization
1. Heap scan – Pruning – Marking items as LP_DEAD
2. Heap cleanup – Cleaning items and mark as LP_UNUSED
1. Heap scan – Pruning – Marking items as LP_UNUSED
2. Heap cleanup – Cleaning items no update of line pointers (We save one WAL generation here)
Old vacuum approach
PostgreSQL 17
pg_stat_progress_vacuum
VACUUM monitoring
pid |
datid |
datname |
relid |
phase |
heap_blks_total |
heap_blks_scanned |
heap_blks_vacuumed |
index_vacuum_count |
max_dead_tuple_bytes |
dead_tuple_bytes |
num_dead_item_ids |
indexes_total |
indexes_processed |
pid |
datid |
datname |
relid |
phase |
heap_blks_total |
heap_blks_scanned |
heap_blks_vacuumed |
index_vacuum_count |
max_dead_tuples |
num_dead_tuples |
Before
PostgreSQL 17
Streaming I/O Interface
Speeds up sequential scans and ANALYZE operations.
Improved Write Throughput
High concurrency workloads see up to 2x better write throughput.
SELECT * FROM products WHERE product_id IN (101, 102, 103, 104);
Other performance improvements
Other improvements and new features
summarize_wal = on
Incremental backups
$PGDATA/pg_wal/summaries
walsummarizer
Background process
Incremental backups
pg_basebackup –D Sunday
Sunday
Monday
Tuesday
Sunday
Monday
Tuesday
WALs
t
Restore Point
WALs
pg_basebackup –D Monday --incremental=Sunday/backup_manifest
pg_basebackup –D Tuesday --incremental=Monday/backup_manifest
pg_combinebackup Sunday/ Monday/ Tuesday/ -o Combined/
Logical replication
Physical
replication
Primary
Standby
Logical
replication
Publisher
Subscriber
pg_createsubscriber
Logical replication
CREATE SUBSCRIPTION my_subscription
CONNECTION 'host=primary_server dbname=mydb user=myuser password=mypassword'
PUBLICATION my_publication
WITH (
failover = true
);
ON LOGIN trigger
CREATE FUNCTION on_login() RETURNS event_trigger
AS $$
BEGIN
-- Setting up some session context
END;
$$ LANGUAGE plpgsql;
CREATE EVENT TRIGGER on_login ON LOGIN EXECUTE FUNCTION on_login();
MAINTAN permission
GRANT MAINTAIN ON sales.orders TO scott;
GRANT pg_maintain TO scott;
Safe search_path
CREATE FUNCTION my_function() RETURNS void AS $$
BEGIN
-- Function logic here
END;
$$ LANGUAGE plpgsql SET search_path TO myschema, public;
COPY command enhancements
New COPY options in PostgreSQL 17
COPY command enhancements
New COPY options in PostgreSQL 17
CREATE TABLE my_table (item_id int PRIMARY KEY);
COPY my_table FROM stdin (on_error ‘igrone’, log_verbosity verbose);
>>> 10
>>> xyz
>>> abc
>>> 40
>>> \.
NOTICE: skipping row due to data type incompatibility at line 2 for column item_id: "xyz"
NOTICE: skipping row due to data type incompatibility at line 3 for column item_id: "abc“
NOTICE: 2 rows were skipped due to data type incompatibility
COPY 2
SELECT * FROM my_table;
item_id
---------
10
40
(2 rows)
JSON_TABLE
Present JSON as a table
CREATE TABLE my_films ( js jsonb );
INSERT INTO my_films VALUES (
'{ "favorites" : [
{ "kind" : "comedy", "films" : [
{ "title" : "Bananas",
"director" : "Woody Allen"},
{ "title" : "The Dinner",
"director" : "Francis" } ] },
{ "kind" : "horror", "films" : [
{ "title" : "Psycho",
"director" : "Alfred Hitchcock" } ] },
{ "kind" : "thriller", "films" : [
{ "title" : "Vertigo",
"director" : "Alfred Hitchcock" } ] },
{ "kind" : "drama", "films" : [
{ "title" : "Yojimbo",
"director" : "Akira Kurosawa" } ] }
] }');
SELECT jt.* FROM
my_films,
JSON_TABLE (js, '$.favorites[*]' COLUMNS (
id FOR ORDINALITY,
kind text PATH '$.kind',
title text PATH '$.films[*].title' WITH WRAPPER,
director text PATH '$.films[*].director' WITH WRAPPER)) AS jt;
id | kind | title | director
----+----------+---------------------------+-----------------------------
1 | comedy | ["Bananas", "The Dinner"] | ["Woody Allen", "Francis"]
2 | horror | ["Psycho"] | ["Alfred Hitchcock"]
3 | thriller | ["Vertigo"] | ["Alfred Hitchcock"]
4 | drama | ["Yojimbo"] | ["Akira Kurosawa"]
(4 rows)
MERGE command enhancements
Now can do action if row exists in target but not in source (NOT MATCHED BY SOURCE)
MERGE INTO wines target
USING new_wine_list source
ON s.winename = target.winename
WHEN MATCHED AND target.stock != source.stock THEN
UPDATE SET stock = source.stock
WHEN NOT MATCHED BY TARGET THEN
INSERT VALUES(source.winename, source.stock)
WHEN NOT MATCHED BY SOURCE THEN
DELETE;
MATCHED
source
target
NOT MATHED BY TARGET
NOT MATHED BY SOURCE
WHEN NOT MATCHED BY SOURCE THEN DELETE | UPDATE | DO NOTHING
MERGE command enhancements
New RETURNING clause
MERGE INTO wines target
USING new_wine_list source
ON s.winename = target.winename
WHEN MATCHED AND target.stock != source.stock THEN
UPDATE SET stock = source.stock
WHEN NOT MATCHED BY TARGET THEN
INSERT VALUES(source.winename, source.stock)
WHEN NOT MATCHED BY SOURCE THEN
DELETE
RETURNING merge_action(), target.*;
merge_action | id | winename | stock
-------------+----+------------+-------
UPDATE | 1 | Merlot | 10
UPDATE | 2 | Chardonnay | 2
DELETE | 3 | Pinot Noir | 10
INSERT | 4 | Riesling | 31
Thank You!