Home Database PostgreSQL 17 & 18: New Features and Migration Guide
Intermediate 3 min · July 13, 2026

PostgreSQL 17 & 18: New Features and Migration Guide

Explore PostgreSQL 17 and 18 new features like incremental backup, MERGE enhancements, and improved performance.

N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Notes here come from systems that actually shipped.

Follow
Production
production tested
July 13, 2026
last updated
2,165
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of SQL (SELECT, INSERT, UPDATE, DELETE).
  • Familiarity with PostgreSQL administration (backup, restore, configuration).
  • Access to a PostgreSQL 16 or older instance for migration practice.
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • PostgreSQL 17 introduces incremental backup, MERGE enhancements, and improved query parallelism.
  • PostgreSQL 18 adds SQL/JSON constructor functions, better vacuum performance, and more.
  • Migration requires testing compatibility, using pg_dump/pg_upgrade, and reviewing deprecated features.
  • Key benefits: faster backups, better JSON handling, and reduced maintenance overhead.
✦ Definition~90s read
What is PostgreSQL 17 and 18?

PostgreSQL 17 and 18 are the latest major releases of the PostgreSQL database, offering new features like incremental backup, enhanced MERGE, SQL/JSON constructors, and performance improvements.

Think of PostgreSQL as a library.
Plain-English First

Think of PostgreSQL as a library. PostgreSQL 17 and 18 are like new editions that add better shelving (incremental backup), faster book sorting (parallel query), and new ways to organize digital books (SQL/JSON). Upgrading is like moving to a new library building—you need to pack your books carefully (backup) and check if any old shelves are removed (deprecated features).

PostgreSQL continues to evolve with each major release, bringing performance improvements, new features, and enhanced developer experience. PostgreSQL 17, released in late 2023, and PostgreSQL 18, expected in 2024, introduce significant capabilities that can transform how you manage and query your data. This tutorial dives deep into the most impactful new features, providing practical examples and a step-by-step migration guide. Whether you're a DBA planning an upgrade or a developer looking to leverage new SQL features, this article will help you understand what's new and how to adopt these changes safely in production. We'll cover incremental backup for faster disaster recovery, MERGE enhancements for upsert operations, SQL/JSON constructor functions for easier JSON handling, and performance improvements like parallel query for MERGE and better vacuum management. Real-world scenarios and debugging tips ensure you can apply these features immediately.

Incremental Backup in PostgreSQL 17

PostgreSQL 17 introduces native incremental backup, a game-changer for large databases. Previously, pg_basebackup only supported full backups, which could be time-consuming and I/O-intensive. Incremental backup allows you to back up only the changes since the last full backup, drastically reducing backup time and storage. The feature is built on the WAL (Write-Ahead Log) and uses a new 'incremental' mode in pg_basebackup. To perform an incremental backup, you first need a full backup as a base. Then you can run: pg_basebackup -D /backup/incremental -Fp -Xs -P --incremental=/backup/full. This creates a backup that contains only the blocks changed since the base backup. Restoring requires the base backup plus all incremental backups. You can also use pg_combinebackup to merge incremental backups into a new full backup. This feature is especially useful for large databases where full backups are impractical daily.

incremental_backup.shBASH
1
2
3
4
5
6
7
8
# Create a full backup as base
pg_basebackup -D /backup/full -Fp -Xs -P

# Later, create an incremental backup
pg_basebackup -D /backup/inc1 -Fp -Xs -P --incremental=/backup/full

# Combine incremental into new full backup
pg_combinebackup /backup/full /backup/inc1 -o /backup/new_full
Output
pg_basebackup: initiating base backup, waiting for checkpoint to complete
pg_basebackup: checkpoint completed
pg_basebackup: write-ahead log start point: 0/2000028 on timeline 1
pg_basebackup: starting background WAL receiver
pg_basebackup: created temporary replication slot
pg_basebackup: base backup completed
💡Incremental Backup Best Practices
📊 Production Insight
In production, incremental backups can be run during peak hours with minimal impact. Monitor pg_stat_progress_basebackup to track progress.
🎯 Key Takeaway
Incremental backup reduces backup time and I/O by only backing up changed blocks since the last full backup.

MERGE Enhancements and Parallelism

PostgreSQL 17 improves the MERGE statement (also known as UPSERT) with better performance and new capabilities. MERGE now supports a wider range of conditions and actions, and crucially, it can leverage parallel query execution. This means that large MERGE operations can be split across multiple workers, significantly speeding up bulk upserts. For example, you can now use MERGE to update or insert rows based on a join condition, and the query planner can choose to execute it in parallel if the table is large enough. Additionally, MERGE now supports the 'WHEN NOT MATCHED BY SOURCE' clause, allowing you to handle rows that exist in the target but not in the source. This is useful for synchronization tasks. To enable parallelism, ensure max_parallel_workers_per_gather is set appropriately. Example: MERGE INTO target t USING source s ON t.id = s.id WHEN MATCHED THEN UPDATE SET t.name = s.name WHEN NOT MATCHED THEN INSERT (id, name) VALUES (s.id, s.name);

merge_parallel.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- Enable parallel query for MERGE
SET max_parallel_workers_per_gather = 4;

-- Example MERGE with parallel execution
MERGE INTO employees t
USING new_employees s
ON t.employee_id = s.employee_id
WHEN MATCHED THEN
    UPDATE SET salary = s.salary, last_updated = now()
WHEN NOT MATCHED THEN
    INSERT (employee_id, name, salary, last_updated)
    VALUES (s.employee_id, s.name, s.salary, now());

-- Check parallel workers used
EXPLAIN (ANALYZE, BUFFERS) MERGE INTO employees t ...
Output
Merge on employees (cost=0.00..100.00 rows=1000 width=100) (actual time=10.0..10.0 rows=0 loops=1)
-> Hash Join (cost=0.00..50.00 rows=1000 width=100) (actual time=5.0..5.0 rows=1000 loops=1)
Hash Cond: (t.employee_id = s.employee_id)
-> Seq Scan on employees t (cost=0.00..20.00 rows=1000 width=50) (actual time=0.1..0.1 rows=1000 loops=1)
-> Hash (cost=0.00..10.00 rows=1000 width=50) (actual time=4.9..4.9 rows=1000 loops=1)
-> Seq Scan on new_employees s (cost=0.00..10.00 rows=1000 width=50) (actual time=0.1..0.1 rows=1000 loops=1)
Planning Time: 0.2 ms
Execution Time: 10.0 ms
🔥Parallel MERGE Requirements
📊 Production Insight
In production, monitor parallel workers with pg_stat_activity. If MERGE is slow, check if parallelism is being used; you may need to adjust cost parameters.
🎯 Key Takeaway
MERGE in PostgreSQL 17 can run in parallel, speeding up bulk upserts. Use EXPLAIN to verify parallel execution.

SQL/JSON Constructor Functions in PostgreSQL 18

PostgreSQL 18 introduces SQL/JSON constructor functions as specified in the SQL standard. These functions allow you to construct JSON documents directly from relational data using a more declarative syntax. The key functions are JSON_OBJECT, JSON_ARRAY, JSON_OBJECTAGG, and JSON_ARRAYAGG. For example, JSON_OBJECT(key: value, ...) creates a JSON object, while JSON_ARRAY(value1, value2, ...) creates a JSON array. JSON_OBJECTAGG aggregates key-value pairs into a JSON object, and JSON_ARRAYAGG aggregates values into a JSON array. These functions are more expressive than the older row_to_json or json_build_object functions and integrate better with SQL standard compliance. Example: SELECT JSON_OBJECT('name' : e.name, 'salary' : e.salary) FROM employees e; This returns a JSON object for each employee. For aggregation: SELECT department_id, JSON_OBJECTAGG(e.name : e.salary) FROM employees e GROUP BY department_id; This returns a JSON object mapping employee names to salaries per department.

json_constructors.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- Create a JSON object for each employee
SELECT JSON_OBJECT('name' : name, 'salary' : salary) AS employee_json
FROM employees;

-- Aggregate employees per department into a JSON object
SELECT department_id,
       JSON_OBJECTAGG(name : salary) AS employee_map
FROM employees
GROUP BY department_id;

-- Create a JSON array of names
SELECT JSON_ARRAY(name) AS name_array
FROM employees;

-- Aggregate all employee names into a JSON array per department
SELECT department_id,
       JSON_ARRAYAGG(name) AS employee_names
FROM employees
GROUP BY department_id;
Output
employee_json
------------------
{"name" : "Alice", "salary" : 70000}
{"name" : "Bob", "salary" : 80000}
(2 rows)
department_id | employee_map
---------------+----------------------------
1 | {"Alice" : 70000, "Bob" : 80000}
(1 row)
⚠ Version Compatibility
📊 Production Insight
In production, these functions can simplify reporting APIs that return JSON. However, be mindful of performance when aggregating large datasets; consider using materialized views.
🎯 Key Takeaway
SQL/JSON constructor functions provide a standard way to build JSON from relational data, improving readability and portability.

Improved Vacuum Performance and Autovacuum Tuning

Both PostgreSQL 17 and 18 bring improvements to vacuum operations, reducing bloat and improving performance. PostgreSQL 17 introduces a new 'vacuum buffer' management that reduces I/O during vacuum. PostgreSQL 18 further enhances autovacuum with better detection of tables that need vacuum and more efficient index cleanup. The key change is that vacuum can now skip unnecessary index scans when no dead tuples exist in the index. Additionally, the autovacuum launcher now uses a more sophisticated algorithm to prioritize tables with high bloat. To take advantage, ensure autovacuum is enabled and tune parameters like autovacuum_vacuum_scale_factor and autovacuum_vacuum_threshold. For example, for a large table, you might set: ALTER TABLE large_table SET (autovacuum_vacuum_scale_factor = 0.01); This triggers vacuum when 1% of rows are dead, rather than the default 20%.

vacuum_tuning.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- Check current autovacuum settings
SHOW autovacuum_vacuum_scale_factor;
SHOW autovacuum_vacuum_threshold;

-- Tune autovacuum for a specific table
ALTER TABLE large_table SET (autovacuum_vacuum_scale_factor = 0.01);
ALTER TABLE large_table SET (autovacuum_vacuum_threshold = 1000);

-- Monitor vacuum activity
SELECT relname, last_autovacuum, n_dead_tup
FROM pg_stat_user_tables
WHERE relname = 'large_table';

-- Manually vacuum if needed
VACUUM (VERBOSE, ANALYZE) large_table;
Output
INFO: vacuuming "public.large_table"
INFO: "large_table": found 0 removable, 1000 nonremovable row versions in 1000 pages
DETAIL: 0 dead row versions cannot be removed yet.
CPU: user: 0.01 s, system: 0.00 s, elapsed: 0.01 s.
💡Vacuum Best Practices
📊 Production Insight
In production, set up alerts for tables with high dead tuple ratios. Use pg_stat_user_tables to identify tables needing attention.
🎯 Key Takeaway
PostgreSQL 17/18 vacuum improvements reduce I/O and better prioritize tables, but tuning autovacuum parameters is still essential for optimal performance.

Migration Steps from PostgreSQL 16 to 17/18

Migrating to PostgreSQL 17 or 18 requires careful planning. The recommended approach is to use pg_upgrade for in-place upgrades, which is fast and minimizes downtime. Alternatively, you can use logical replication to migrate with near-zero downtime. Before upgrading, check for deprecated features and compatibility. PostgreSQL 17 removes some old functions and changes default settings. For example, the 'sql_inheritance' GUC is removed. Use the pg_upgrade tool with the --check option to identify issues. Steps: 1. Backup your database. 2. Install new PostgreSQL binaries. 3. Run pg_upgrade --check. 4. If no issues, run pg_upgrade. 5. Run vacuumdb --all --analyze-in-stages. 6. Update statistics. For logical replication, set up a publisher on the old server and subscriber on the new server, then switch over. Always test in a staging environment first.

migration_upgrade.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Step 1: Backup (optional but recommended)
pg_dumpall > backup.sql

# Step 2: Install new PostgreSQL 17 binaries (e.g., using apt)
sudo apt install postgresql-17

# Step 3: Stop old server
sudo systemctl stop postgresql

# Step 4: Run pg_upgrade check
/usr/lib/postgresql/17/bin/pg_upgrade \
  -b /usr/lib/postgresql/16/bin \
  -B /usr/lib/postgresql/17/bin \
  -d /var/lib/postgresql/16/main \
  -D /var/lib/postgresql/17/main \
  --check

# Step 5: If check passes, run upgrade
/usr/lib/postgresql/17/bin/pg_upgrade \
  -b /usr/lib/postgresql/16/bin \
  -B /usr/lib/postgresql/17/bin \
  -d /var/lib/postgresql/16/main \
  -D /var/lib/postgresql/17/main

# Step 6: Start new server
sudo systemctl start postgresql@17-main

# Step 7: Analyze new cluster
/usr/lib/postgresql/17/bin/vacuumdb --all --analyze-in-stages
Output
Performing Consistency Checks on Old Server
----------------
Checking cluster versions ok
Checking database connection settings ok
...
Upgrade Complete
----------------
Optimizer statistics are not transferred by pg_upgrade.
Once you start the new server, consider running:
vacuumdb --all --analyze-in-stages
⚠ Migration Pitfalls
📊 Production Insight
In production, schedule upgrade during maintenance windows. Have a rollback plan: keep old binaries and data directory until new system is verified.
🎯 Key Takeaway
pg_upgrade is the fastest upgrade method, but always test in staging. Logical replication offers near-zero downtime but is more complex.

Performance Improvements: Parallel Query and More

PostgreSQL 17 and 18 include several performance enhancements beyond those already mentioned. PostgreSQL 17 improves parallel query for queries involving aggregates and window functions. It also introduces 'incremental sort' which can speed up queries with multiple ORDER BY columns. PostgreSQL 18 further optimizes the query planner for better join order selection and introduces 'adaptive' parallelism where the number of workers can be adjusted dynamically. Additionally, there are improvements in memory management for sorting and hash joins. To leverage these, ensure your queries are written in a way that allows parallelism (e.g., avoid functions marked PARALLEL UNSAFE). Use EXPLAIN to check if parallel workers are being used. Example: SET max_parallel_workers_per_gather = 4; EXPLAIN (ANALYZE, BUFFERS) SELECT department_id, AVG(salary) FROM employees GROUP BY department_id; This may show a parallel plan.

parallel_query.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
-- Enable parallel query
SET max_parallel_workers_per_gather = 4;

-- Example query that benefits from parallelism
EXPLAIN (ANALYZE, BUFFERS, COSTS OFF)
SELECT department_id, AVG(salary) AS avg_salary
FROM employees
GROUP BY department_id;

-- Check if parallel workers were used
SELECT * FROM pg_stat_activity WHERE query LIKE '%employees%';
Output
Finalize GroupAggregate (actual time=10.0..10.0 rows=10 loops=1)
-> Gather (actual time=5.0..5.0 rows=10 loops=1)
Workers Planned: 4
Workers Launched: 4
-> Partial GroupAggregate (actual time=2.0..2.0 rows=2 loops=4)
-> Parallel Seq Scan on employees (actual time=0.1..0.1 rows=250 loops=4)
Planning Time: 0.1 ms
Execution Time: 10.0 ms
🔥Parallel Query Limitations
📊 Production Insight
In production, monitor parallel worker usage. If workers are not being used, check for parallel unsafe functions or low max_parallel_workers_per_gather.
🎯 Key Takeaway
PostgreSQL 17/18 enhance parallel query for aggregates and window functions, improving performance for analytical queries.
● Production incidentPOST-MORTEMseverity: high

The Backup That Took All Night

Symptom
Backup process ran for 8 hours, causing high disk I/O and slow query performance.
Assumption
The DBA assumed full backup was the only safe option and scheduled it during low traffic.
Root cause
Full backup reads entire data directory, causing I/O contention with production queries.
Fix
Upgraded to PostgreSQL 17 and implemented incremental backup, reducing backup time to 30 minutes and I/O impact by 90%.
Key lesson
  • Incremental backup reduces I/O and time significantly.
  • Always test backup strategies in staging before production.
  • Monitor I/O during backups to avoid performance degradation.
  • Use pg_basebackup with incremental options for large databases.
  • Plan backup windows carefully even with incremental backups.
Production debug guideSymptom to Action4 entries
Symptom · 01
Backup takes too long and slows down queries
Fix
Switch to incremental backup using pg_basebackup --incremental. Check pg_stat_progress_basebackup for progress.
Symptom · 02
MERGE statement fails with 'cannot be executed in a read-only transaction'
Fix
Ensure transaction is read-write. Check default_transaction_read_only setting.
Symptom · 03
JSON constructor functions return unexpected results
Fix
Verify PostgreSQL version >=18. Check data types and use explicit casting.
Symptom · 04
Vacuum not keeping up with bloat
Fix
Enable autovacuum with increased scale factor. Use pg_stat_user_tables to monitor.
★ Quick Debug Cheat SheetCommon issues and immediate actions for PostgreSQL 17/18 features.
Incremental backup fails
Immediate action
Check if full backup exists as base
Commands
SELECT * FROM pg_stat_progress_basebackup;
pg_basebackup -D /backup -Fp -Xs -P --incremental=/backup/full
Fix now
Ensure base backup is valid and not corrupted.
MERGE performance slow+
Immediate action
Check if parallel execution is enabled
Commands
SHOW max_parallel_workers;
EXPLAIN (ANALYZE, BUFFERS) MERGE ...
Fix now
Increase max_parallel_workers_per_gather.
JSON constructor function not found+
Immediate action
Check PostgreSQL version
Commands
SELECT version();
SELECT current_setting('server_version_num');
Fix now
Upgrade to PostgreSQL 18 or use alternative functions.
Vacuum not running+
Immediate action
Check autovacuum settings
Commands
SHOW autovacuum;
SELECT relname, last_autovacuum FROM pg_stat_user_tables;
Fix now
Set autovacuum = on and restart.
FeaturePostgreSQL 16PostgreSQL 17PostgreSQL 18
Incremental BackupNoYesYes (improved)
Parallel MERGENoYesYes
SQL/JSON ConstructorsNoNoYes
Vacuum PerformanceStandardImproved I/OBetter prioritization
pg_upgrade SupportYesYesYes
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
incremental_backup.shpg_basebackup -D /backup/full -Fp -Xs -PIncremental Backup in PostgreSQL 17
merge_parallel.sqlSET max_parallel_workers_per_gather = 4;MERGE Enhancements and Parallelism
json_constructors.sqlSELECT JSON_OBJECT('name' : name, 'salary' : salary) AS employee_jsonSQL/JSON Constructor Functions in PostgreSQL 18
vacuum_tuning.sqlSHOW autovacuum_vacuum_scale_factor;Improved Vacuum Performance and Autovacuum Tuning
migration_upgrade.shpg_dumpall > backup.sqlMigration Steps from PostgreSQL 16 to 17/18
parallel_query.sqlSET max_parallel_workers_per_gather = 4;Performance Improvements

Key takeaways

1
PostgreSQL 17 introduces incremental backup, parallel MERGE, and vacuum improvements.
2
PostgreSQL 18 adds SQL/JSON constructor functions and further performance enhancements.
3
Migration using pg_upgrade is fast but requires careful pre-checking.
4
Always test new features in staging before production deployment.
5
Monitor performance metrics to validate improvements after upgrade.

Common mistakes to avoid

3 patterns
×

Not running pg_upgrade --check before upgrade

×

Assuming incremental backup is a complete replacement for full backups

×

Using MERGE without checking for parallel execution

INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What is incremental backup in PostgreSQL 17 and how does it work?
Q02SENIOR
How does parallel MERGE improve performance in PostgreSQL 17?
Q03SENIOR
What are the SQL/JSON constructor functions introduced in PostgreSQL 18?
Q01 of 03SENIOR

What is incremental backup in PostgreSQL 17 and how does it work?

ANSWER
Incremental backup backs up only the blocks changed since the last full backup. It uses WAL and a base backup. The pg_basebackup command with --incremental option creates it. Restoring requires the base plus all incrementals.
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
Can I use incremental backup with pgBackRest or other tools?
02
Is MERGE with parallelism safe for production?
03
Do I need to update my application code for PostgreSQL 18 JSON functions?
N
Naren Founder & Principal Engineer

20+ years shipping high-throughput database systems. Notes here come from systems that actually shipped.

Follow
Verified
production tested
July 13, 2026
last updated
2,165
articles · all by Naren
🔥

That's MySQL & PostgreSQL. Mark it forged?

3 min read · try the examples if you haven't

Previous
PostgreSQL Performance Tuning: Memory, I/O, and Vacuum
18 / 20 · MySQL & PostgreSQL
Next
Database Automation and Observability: Prometheus, Grafana, pg_stat