What's New with Monitoring in PostgreSQL 19
\->Scroll to top
<-Back
- Blog
- /
- Engineering
View as Markdown Open this page in Markdown
Open in ChatGPT Ask questions about this page
Open in Claude Ask questions about this page
Open in v0 Ask questions about this page
Copy pageCopied!More actions
What's New with Monitoring in PostgreSQL 19

Aug 18, 2026 · 11 minutes read
PostgreSQL 19 is around the corner, and I will be talking about observability improvements at the upcoming PostgreSQL Conference Europe. I am also co-organizing the PostgreSQL Observability Summit as part of Community Events Day on the last day of the conference, Friday, October 23. So, I thought this was a good time to organize my thoughts in a blog post.
Disclaimer: PostgreSQL 19 is still in beta as I write this, so some of the details below may change before the final release. The release notes will be the final word once 19.0 goes GA.
With that out of the way, let's look at the PostgreSQL 19 monitoring and observability improvements that I find most useful and interesting.
##
`log_lock_waits` controls whether to log a message when a session waits longer than `deadlock_timeout` (1 second by default) to acquire a lock. Checking for deadlock is relatively expensive, so you’re advised to set an amount of time to wait before that check happens, ideally longer than your regular transaction time. This is the relationship between `deadlock_timeout` and `log_lock_waits` (Robert Haas even suggested separating the two). In short, enabling `log_lock_waits` provides a cheap lock contention detector for Postgres, yet it defaulted to off up until now.
PostgreSQL 19 flips the default to `on` (commit 2aac62be8, Laurenz Albe). I love the reasoning in the commit message:
If someone is stuck behind a lock for more than a second, that is almost always a problem that is worth a log entry.
##
`log_min_messages` has always been a single global knob. If you wanted `DEBUG2` output from the checkpointer, you got `DEBUG2` from everything and good luck finding the line you care about.
Starting with PostgreSQL 19, `log_min_messages` accepts a comma-separated list of `process type:level` pairs, plus one mandatory level that applies to every process type not listed. A bare level is still valid, so the old syntax keeps working.
1-- DEBUG2 for the checkpointer, DEBUG1 for autovacuum, WARNING for everything else
2ALTER SYSTEM SET log_min_messages = 'warning, checkpointer:debug2, autovacuum:debug1';Copy command
The recognized process types are `archiver`, `autovacuum`, `backend`, `bgworker`, `bgwriter`, `checkpointer`, `checksums`, `ioworker`, `postmaster`, `slotsyncworker`, `startup`, `syslogger`, `walreceiver`, `walsender`, `walsummarizer` and `walwriter`. Note that `autovacuum` covers both the launcher and the workers.
##
Until now, `log_autovacuum_min_duration` controlled log output for both VACUUM and ANALYZE runs by autovacuum. These are very different operations: autoanalyze runs are typically much shorter, so a threshold tuned to catch slow vacuums silently discards almost all ANALYZE activity.
PostgreSQL 19 adds `log_autoanalyze_min_duration`, which controls log output for ANALYZE. `log_autovacuum_min_duration` now only controls VACUUM logging. Both default to `10min`, accept `0` (log everything) and `-1` (disable). You can also set per-table overrides:
1-- For this table log every autoanalyze, regardless of the global setting
2ALTER TABLE events SET (log_autoanalyze_min_duration = 0);Copy command
Upgrade notes: If your tooling parses autovacuum logs, remember that after the upgrade `log_autovacuum_min_duration` alone no longer reports analyze runs. And if you’re displaying log parameters in a UI, consider making `log_autoanalyze_min_duration` visible as well.
##
Full-page images are often the dominant part of WAL volume and VACUUM generates plenty of them. If you’re not familiar with why, I’ll try to explain a bit. The first time a page is modified after a checkpoint, Postgres writes the entire 8kB page into the WAL instead of just the small change so crash recovery never has to trust a possibly torn page on disk. That means one tiny update of a 50-byte row can produce \~8kB of WAL if it happens to be the first touch of that page since the last checkpoint. VACUUM crawls through and dirties lots of cold pages, so it triggers plenty of these, which is why vacuuming a big, cold table can generate far more WAL than the amount of data it actually changed.
Postgres reported full-page image counts (`wal_fpi`) but not their size, so if you wanted the actual bytes, you had to dig through WAL with `pg_waldump` or `pg_walinspect`.
PostgreSQL 19 adds a new `wal_fpi_bytes` counter, added through a series of patches by Shinya Kato. You can see it cluster-wide in `pg_stat_wal` (what your metrics collector polls), per-session via `pg_stat_get_backend_wal()` (handy to join against `pg_stat_activity` when you're wondering which connection is producing all that WAL), per-query in `EXPLAIN (ANALYZE, WAL)`, and per-operation in VACUUM and ANALYZE log output, which now looks like this:
1WAL usage: 2841 records, 1904 full page images, 15735621 bytes,
2 15242880 full page image bytes, 3 buffers fullCopy command
Comparing `wal_fpi_bytes` against `wal_bytes` tells you what fraction of your WAL is full-page images and whether `wal_compression`, checkpoint spacing or maintenance scheduling is where the win is.
##
Postgres 19 adds a few new wait events that make previously hard-to-see waits visible in `pg_stat_activity`.
As part of the infrastructure behind the new `WAIT FOR` command (which I plan to explain more in another blog post), PostgreSQL can now report when a session is waiting for WAL to reach a specific Log Sequence Number (LSN). A new `WaitForWalWrite` wait event covers waiting for WAL to be written and the existing `WaitForWalFlush` (previously primary-only) now also covers standbys. Together with `WaitForWalReplay` (as the name suggests, waiting for WAL to be replayed), they make it possible to tell which stage of WAL processing a session is waiting on (write → flush → replay).
Postgres 19 also closes a similar observability gap around `COPY`. Previously, `COPY FROM/TO` operations on a file, pipe or program used to do their reads and writes with no wait event at all, so we had no visibility when a backend got stuck. Now, the new `CopyFromRead` and `CopyToWrite` wait events make these paths observable. `COPY` over the wire protocol was already covered by `ClientRead`/`ClientWrite`, so this closes the file, pipe and program paths commonly used by bulk loads and ETL pipelines.
##
This one is subtle, but anyone running logical replication or FDW-heavy setups will appreciate it. NOTICE, WARNING and similar messages from a remote server (over replication, `postgres_fdw` or `dblink` connections) used to be printed directly to the local server's `stderr`. So, they did not get the usual log formatting, such as `log_line_prefix`, which made them harder to correlate with nearby log entries.
PostgreSQL 19 routes these messages through `ereport()` like local server messages. They get the normal log formatting and are prefixed with their origin: `received message via replication` or `received message via remote connection`. This makes cross-server log correlation significantly easier.
##
Multixacts, which Postgres creates when multiple transactions lock the same tuple (think foreign key checks and `SELECT ... FOR SHARE`), have long been an observability blind spot. When `pg_multixact/members/` started eating disk, your options were filesystem inspection and educated guessing.
PostgreSQL 19 adds `pg_get_multixact_stats()`:
1SELECT *, pg_size_pretty(members_size) AS members_size_pretty
2FROM pg_get_multixact_stats();
3
4 num_mxids | num_members | members_size | oldest_multixact | members_size_pretty
5-----------+-------------+--------------+------------------+---------------------
6 311740299 | 2785241176 | 13926205880 | 2 | 13 GBCopy command
This gives us direct visibility into multixact activity: `num_mxids` shows how many multixact IDs are currently in use, `num_members` shows how many entries they contain and `members_size` shows how much disk space those members consume. `oldest_multixact` helps track how far back the oldest multixact still needed by the cluster goes.
A spike in `num_mxids` points at concurrent row-locking workloads; `oldest_multixact` holding still while `num_members` grows suggests a long-running transaction is blocking cleanup. The function requires `pg_read_all_stats`, so it slots straight into monitoring roles.
##
Finally, the wraparound warning threshold for both transaction IDs and multixact IDs has been raised from 40 million to 100 million transactions before wraparound. The 40 million figure dated from an era of much lower transaction rates: a system burning 10,000 XIDs per second gets through it in about an hour, which is not much runway to notice, react and let an aggressive vacuum finish.
Importantly, this does not change when wraparound happens; it simply makes Postgres start warning you earlier. If your alerting on `age(datfrozenxid)` uses thresholds derived from the old warning point, revisit them.
##
I know this is a lot of information to go through, so I decided to end this blog with a list of actionable items. So if you maintain observability tooling, check these before upgrading (or give this list to your agents 🙂):
Disclaimer: This checklist is not theoretical for me either: we maintain pg\_stat\_ch, an open-source extension that streams query statistics from Postgres into ClickHouse, and supporting PostgreSQL 19 means walking through exactly this list: new WAL fields, new wait events and all.
Log parsers
Metric collectors
Wait event dictionaries
Config validation and UIs
Alerts
PostgreSQL 19 also introduces new system views (`pg_stat_lock`, `pg_stat_recovery`, `pg_stat_autovacuum_scores`) and improves several existing ones. I plan to cover these in a separate blog post, but it is not a bad idea to familiarize yourself with them already.
##
On a personal note, observability is where my Postgres world and my day job happen to meet. At ClickHouse, we are building a managed Postgres service, while ClickHouse itself is what many engineering teams use to store and analyze the logs, metrics, and traces that systems like Postgres produce. So, I get to think about both sides of observability: the database emitting the signals and the database storing and analyzing them.
If you want to talk about any of this, come find me in Valencia at PGConf.EU on October 20-23!
Get started with ClickHouse Managed Postgres today
Interested in seeing how ClickHouse Managed Postgres works on your data? Get started with ClickHouse Cloud in minutes and receive $300 in free credits.
---
Share this post
Subscribe to our newsletter
Stay informed on feature releases, product roadmap, support, and cloud offerings!
Recent posts

Engineering
What is direct I/O, and why does ClickHouse Managed Postgres use it for backups?
Kaushik Iska · Oct 2, 2026

User stories
How LinkedIn extended ClickHouse from distributed tracing to metric discovery and analytics
ClickHouse · Oct 1, 2026

Product
Introducing SCIM Provisioning in ClickHouse Cloud
Raymond Lee · Sep 30, 2026

Product
pg\_clickhouse & chdb updates: Encoding, nesting, and types
David Wheeler · Sep 30, 2026
