Database SRE

PostgreSQL 16: New Features Every DBA Should Know

PostgreSQL 16 introduced logical replication from standbys, pg_stat_io, SQL/JSON constructors, COPY improvements, and pg_stat_checkpointer. Full DBA upgrade guide.

JusDB Team
March 5, 2026
10 min read
206 views
PostgreSQL 16: New Features Every DBA Should Know

PostgreSQL 16 landed in September 2023 and brought a set of changes that shift how DBAs approach replication, I/O diagnostics, and query performance at scale. The headline feature — logical replication from standby servers — fundamentally changes CDC and HA architectures that teams have worked around for years. But pg_stat_io, SQL/JSON constructors, COPY improvements, and a refactored monitoring stack deserve equal attention before you plan your next upgrade window. If you are running PG15 or earlier in production, this guide covers the changes that will have the most operational impact on your day-to-day work.

TL;DR — PostgreSQL 16 DBA Highlights
  • Standby servers can now act as logical replication sources, enabling CDC pipelines without touching the primary.
  • pg_stat_io is a new system view that exposes per-backend, per-context I/O statistics that were previously invisible without OS-level tooling.
  • SQL/JSON constructors (JSON_OBJECT, JSON_ARRAY, JSON_TABLE, IS JSON) land in PG16, closing the gap with MySQL and SQL Server.
  • COPY gains ON_ERROR ignore and DEFAULT keyword support, making bulk loads significantly more forgiving.
  • pg_stat_checkpointer splits checkpoint metrics out of pg_stat_bgwriter, giving cleaner separation between write and checkpoint monitoring.

Parallel Query Improvements

PostgreSQL 16 continues to refine the parallel query infrastructure that has been evolving since PG10. The most practical improvements for DBAs are in how parallel hash joins handle memory pressure and how the planner estimates costs for large aggregate operations.

Parallel Hash Join Batching

PG16 improves how parallel hash joins handle batching when the hash table does not fit in work_mem. Earlier versions would sometimes fall back to a single-process hash join when batch overflow occurred. PG16 keeps the parallel execution path alive through batching, which means workloads with large hash joins and moderate work_mem settings see fewer unexpected performance regressions during data growth.

Aggregate Parallelism Improvements

Partial aggregate pushdown has been improved for several aggregate types. If your OLAP queries combine GROUP BY with window functions over large scans, re-run EXPLAIN (ANALYZE, BUFFERS) after upgrading — you may see the planner choose a parallel partial aggregate path where it previously chose a serial one.

max_parallel_workers_per_gather

The default for max_parallel_workers_per_gather remains 2, but PG16 improves the cost model that decides when parallelism is worth activating. Queries that were borderline serial in PG15 may go parallel automatically in PG16 on the same hardware. Audit your slow query log after upgrade — parallel plan regressions are rare but do occur on heavily skewed data distributions.

Tip: After upgrading to PG16, run EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) on your top-10 slowest queries from PG15 and compare plan nodes. The improved cost model often eliminates unnecessary Gather Merge nodes on medium-sized result sets.

Logical Replication Enhancements

This is the biggest architectural change in PostgreSQL 16. Logical replication from standby servers, two-phase commit support, and the new pg_createsubscriber utility collectively remove three of the most common operational complaints about PG logical replication.

Logical Replication from Standby Servers

Before PG16, every logical replication subscriber had to connect directly to the primary server. This meant that CDC pipelines, downstream analytics replicas, and cross-region subscribers all added WAL sender load to the primary. In PG16, a physical standby can act as a logical replication source — it decodes WAL and publishes changes downstream just as the primary would.

This is a significant architecture change for teams running Debezium or similar CDC tools against high-traffic primaries. You can now offload the logical decoding work to a read replica without any change to how downstream consumers receive data.

Important: The logical replication from standby feature (PG16) requires the primary to have wal_level = logical. If you are upgrading from PG15 with wal_level = replica, you must change it and restart before standbys can act as logical publishers. This is a configuration restart — plan your maintenance window accordingly.
sql
-- PG16: Logical replication from standby
-- The standby can now publish changes to downstream subscribers
CREATE PUBLICATION standby_pub FOR ALL TABLES;

-- Create subscriber pointing to the standby
CREATE SUBSCRIPTION app_sub
  CONNECTION 'host=standby-host dbname=myapp user=repuser'
  PUBLICATION standby_pub;

-- pg_createsubscriber: convert physical replica to logical subscriber
pg_createsubscriber -d myapp -P standby_pub -s standby-host

Row Filtering in Initial Snapshot

PG15 introduced row filters for ongoing logical replication (only replicate rows matching a WHERE clause). PG16 extends this to the initial table sync snapshot. Previously, the initial copy phase would transfer all rows even if a row filter was defined, relying on the subscriber to discard unwanted rows. In PG16, the filter is applied during the initial snapshot, which reduces network transfer and subscriber-side processing for large partially-replicated tables.

Two-Phase Commit Support

PG16 adds two-phase commit (2PC) support for logical replication. When a subscriber is configured with two_phase = on, prepared transactions are replicated and held in the prepared state on the subscriber until the originating COMMIT PREPARED arrives. This matters for distributed transaction patterns where you need strict consistency guarantees across publisher and subscriber, and for integrations with XA transaction coordinators.

pg_createsubscriber Utility

The new pg_createsubscriber command-line utility automates the process of converting an existing physical standby into a logical replication subscriber. This was previously a multi-step manual process involving stopping the standby, reconfiguring recovery, and running CREATE SUBSCRIPTION. The utility handles the coordination automatically, significantly reducing the risk of misconfiguration during the transition.

pg_stat_io: New I/O Statistics View

PostgreSQL 16 introduces pg_stat_io, a system view that provides per-backend-type, per-object, per-context I/O statistics. Before this view existed, understanding whether autovacuum was competing with OLTP queries for I/O required either strace, iostat with process attribution, or commercial monitoring agents. pg_stat_io makes this a standard SQL query.

Tip: The pg_stat_io view in PG16 finally gives you per-context I/O visibility that was previously only available via strace or OS-level tools. Use it to identify whether your autovacuum workers are causing I/O contention with OLTP queries — filter on backend_type = 'autovacuum worker'.
sql
-- New in PG16: per-backend I/O statistics
SELECT backend_type, object, context,
       reads, read_time,
       writes, write_time,
       hits, evictions, reuses,
       fsyncs, fsync_time
FROM pg_stat_io
WHERE reads > 0 OR writes > 0
ORDER BY reads DESC;

-- Find processes doing the most disk reads
SELECT backend_type, reads, read_time,
  round(read_time / nullif(reads, 0), 3) AS avg_read_ms
FROM pg_stat_io
WHERE reads > 0
ORDER BY read_time DESC;

The context column distinguishes between normal (standard buffer pool operations), vacuum (autovacuum and manual VACUUM), and bulkread/bulkwrite (sequential scan and bulk operations that bypass the ring buffer differently). This lets you answer questions like "how many buffer pool evictions is autovacuum causing per minute?" without any external tooling.

The cumulative stats are reset by pg_stat_reset() along with other pg_stat views, so existing reset procedures work without modification.

SQL/JSON Improvements

PostgreSQL 16 adds the SQL standard JSON constructors and predicates that have been in MySQL and SQL Server for years. These are not merely convenience wrappers — JSON_TABLE in particular enables a class of ETL patterns that previously required PL/pgSQL or application-level processing.

sql
-- PG16: New JSON constructors and predicates
SELECT JSON_OBJECT('name': 'Alice', 'age': 30);
SELECT JSON_ARRAY(1, 2, 3, NULL ABSENT ON NULL);

-- JSON_TABLE for converting JSON to rows
SELECT *
FROM JSON_TABLE(
  '[{"name":"Alice","dept":"Engineering"},{"name":"Bob","dept":"Sales"}]',
  '$[*]'
  COLUMNS (
    name TEXT PATH '$.name',
    dept TEXT PATH '$.dept'
  )
) AS jt;

-- IS JSON predicate
SELECT '{"valid": true}' IS JSON OBJECT;  -- true
SELECT '[1,2,3]' IS JSON ARRAY;            -- true
SELECT 'not json' IS JSON;                 -- false

The IS JSON predicate family is particularly useful for CHECK constraints on text columns that store JSON, or for WHERE clause validation when processing unstructured input. The ABSENT ON NULL option in JSON_ARRAY and JSON_OBJECT gives you clean control over null handling without wrapper functions.

Tip: If you are using jsonb_build_object() and jsonb_build_array() throughout your codebase, consider migrating to the SQL/JSON constructors. They are more portable across databases, easier for new team members to read, and the IS JSON predicate replaces the common pattern of catching exceptions from jsonb casts in validation logic.

Security and Access Control Changes

pg_hba.conf Improvements

PostgreSQL 16 adds new connection-type keywords in pg_hba.conf that give administrators explicit control over encrypted vs. unencrypted connections at the rule level. The new hostssl and hostnossl entries have existed since PG9.2, but PG16 adds better error messaging when rules conflict and improves handling of clientcert options.

ini
# PG16: connection-type specific rules
# TYPE   DATABASE  USER      ADDRESS       METHOD
local    all       postgres                peer
host     all       all       127.0.0.1/32  scram-sha-256
# New: hostnossl rule to explicitly reject unencrypted
hostnossl  all     all       0.0.0.0/0     reject
hostssl    all     all       0.0.0.0/0     scram-sha-256

The pattern above — explicitly rejecting unencrypted connections from any address while permitting SSL connections with SCRAM-SHA-256 — is now the recommended baseline for internet-accessible PostgreSQL instances. Previously achieving this required firewall rules or careful ordering of host entries.

GRANT on Schemas and Sequences

PG16 adds pg_read_all_data and pg_write_all_data predefined roles that grant SELECT and INSERT/UPDATE/DELETE on all tables, views, and sequences without needing explicit per-object grants. This simplifies provisioning read-only reporting users and ETL service accounts that need broad access without superuser privileges.

Warning: The pg_read_all_data and pg_write_all_data roles bypass row security policies (RLS) by default. If your schema relies on RLS for data isolation between tenants, do not assign these roles to application users — use explicit table-level grants instead.

Performance and Configuration Changes

vacuum_buffer_usage_limit

PG16 introduces vacuum_buffer_usage_limit, a new parameter that directly controls how much of the buffer pool vacuum and analyze operations can consume. Previously, vacuum_cost_delay and vacuum_cost_limit throttled vacuum indirectly through I/O cost accounting, but did not prevent vacuum from evicting hot pages from shared_buffers. The new parameter caps the buffer footprint directly.

ini
# PG16: vacuum_buffer_usage_limit (replaces vacuum_cost_delay for buffer management)
vacuum_buffer_usage_limit = 256kB  # Default — limit buffer consumption during vacuum

On systems where autovacuum was evicting frequently-accessed index pages from the buffer pool, increasing this value allows vacuum to run more efficiently; decreasing it protects the buffer pool for OLTP. The default of 256kB is conservative — production systems with large tables often benefit from raising this to 2MB or 4MB to reduce vacuum runtime at the cost of slightly higher buffer churn.

COPY Improvements

Two COPY improvements in PG16 have significant operational value for data engineering teams.

sql
-- PG16: COPY with DEFAULT keyword support
COPY orders (id, status, amount) FROM STDIN WITH (DEFAULT '*');
-- Rows with '*' in a column use the column's DEFAULT value

-- ON_ERROR ignore option to skip bad rows
COPY orders FROM '/tmp/orders.csv' WITH (FORMAT csv, ON_ERROR ignore);
-- Previously would fail entire COPY on first bad row

ON_ERROR ignore is the change most data engineers have wanted for years. Before PG16, a single malformed row would abort an entire COPY operation, requiring pre-processing to validate input or catch-and-retry logic in application code. With ON_ERROR ignore, bad rows are skipped and the operation continues, with skipped row counts visible in the command output. The DEFAULT keyword support eliminates the need for sentinel values or pre-processing steps to inject default column values during bulk loads.

pg_stat_checkpointer

PG16 separates checkpoint statistics out of pg_stat_bgwriter into a dedicated pg_stat_checkpointer view. This makes dashboards and alerting rules cleaner — checkpoint lag and bgwriter buffer allocation are distinct operational concerns that were awkward to monitor from a single view.

sql
-- pg_stat_bgwriter: new stats in PG16
SELECT buffers_clean, maxwritten_clean, buffers_alloc,
       checkpoint_write_time, checkpoint_sync_time
FROM pg_stat_bgwriter;

-- pg_stat_checkpointer: new dedicated view (separated from bgwriter)
SELECT num_timed, num_requested, write_time, sync_time, buffers_written
FROM pg_stat_checkpointer;

If you have existing monitoring that reads checkpoint timing from pg_stat_bgwriter, update those queries to use pg_stat_checkpointer after upgrading. The old columns remain in pg_stat_bgwriter in PG16 for backward compatibility but are expected to be deprecated in a future version.

Key Takeaways

  • Logical replication from standbys is the most architecturally significant change in PG16. If you run CDC pipelines against your primary, evaluate moving them to a standby immediately after upgrading. Remember: wal_level = logical on the primary is a prerequisite.
  • pg_stat_io should be one of the first things you query after upgrading. Baseline your autovacuum and bgwriter I/O patterns before making any tuning changes.
  • SQL/JSON constructors (JSON_OBJECT, JSON_ARRAY, JSON_TABLE, IS JSON) are production-ready — migrate away from jsonb_build_object patterns in new code.
  • COPY ON_ERROR ignore and DEFAULT keyword support reduce the need for ETL pre-processing — update your data loading pipelines to leverage these.
  • vacuum_buffer_usage_limit and pg_stat_checkpointer give you better tools for vacuum tuning and checkpoint monitoring. Update dashboards and runbooks to use the new views.
  • Security baseline: Use hostnossl ... reject + hostssl ... scram-sha-256 in pg_hba.conf as the new standard for externally accessible instances.

Working with JusDB on PostgreSQL Upgrades

Upgrading a production PostgreSQL instance from PG15 to PG16 involves more than running pg_upgrade. The wal_level requirement for logical replication from standbys means you need to plan your upgrade sequence carefully: primary first, then standbys, with a maintenance window for the primary restart when changing wal_level. If you are running streaming replication, the new pg_createsubscriber utility can convert one of your physical standbys to a logical subscriber post-upgrade — enabling zero-downtime CDC migration without a second full base backup.

JusDB works with engineering teams to plan and execute major PostgreSQL version upgrades with minimal downtime. Our approach uses logical replication to cut over between versions without taking application offline — the same technique described in this post, applied to the upgrade process itself. We also help teams instrument pg_stat_io, pg_stat_checkpointer, and the new vacuum parameters into existing Prometheus/Grafana stacks so you leave the upgrade with better observability than you started with.

If you are planning a PG16 upgrade or want help evaluating whether logical replication from standbys fits your CDC architecture, get in touch or review our PostgreSQL services.

Share this article

JusDB Team

Official JusDB content team