Thread
-
xact_rollback spikes when logical walsender exits
Nikolay Samokhvalov <nik@postgres.ai> — 2026-04-17T15:15:00Z
Hi hackers, There is a bug on logical-replication publishers where every decoded committed transaction bumps pg_stat_database.xact_rollback. ReorderBufferProcessTXN() ends each decoded transaction with AbortCurrentTransaction() for catalog cleanup; in the walsender that is a top-level abort, so AtEOXact_PgStat_Database(isCommit=false) increments the backend-local pgStatXactRollback. The counts are flushed to shared stats on walsender exit, producing an acute spike. Result: for production systems with SREs on call and tight alerting on xact_rollback, this turns routine logical-replication operations (disabling a subscription, dropping a slot, walsender restart) into false-positive pages. Reported in [1]; also experienced at GitLab [2][3][4]. Attaching a simple patch that adds a backend-local flag pgStatXactSkipCounters in pgstat_database.c that AtEOXact_PgStat_Database() honors to skip the counter bump. Added TAP test that fails on master with 5/0 and passes with the patch. If there is agreement on this shape, happy to send patches for all supported branches. Let me know what you think. [1] https://postgr.es/m/CAG0ozMo_xWQn%2BAvv8jzbbhePGp5OnhdO%2BYWTkdg4faWSXz0Jzg%40mail.gmail.com [2] https://gitlab.com/gitlab-com/gl-infra/production/-/work_items/8290 [3] https://gitlab.com/postgres-ai/postgresql-consulting/tests-and-benchmarks/-/work_items/39 [4] https://gitlab.com/gitlab-org/orbit/knowledge-graph/-/work_items/406 Nik
-
Re: xact_rollback spikes when logical walsender exits
Fujii Masao <masao.fujii@gmail.com> — 2026-04-20T16:35:34Z
On Sat, Apr 18, 2026 at 12:15 AM Nikolay Samokhvalov <nik@postgres.ai> wrote: > > Hi hackers, > > There is a bug on logical-replication publishers where every decoded > committed transaction bumps pg_stat_database.xact_rollback. > ReorderBufferProcessTXN() ends each decoded transaction with > AbortCurrentTransaction() for catalog cleanup; in the walsender that > is a top-level abort, so AtEOXact_PgStat_Database(isCommit=false) > increments the backend-local pgStatXactRollback. > > The counts are flushed to shared stats on walsender exit, producing > an acute spike. Result: for production systems with SREs on call and tight > alerting on xact_rollback, this turns routine logical-replication operations > (disabling a subscription, dropping a slot, walsender restart) into > false-positive pages. > > Reported in [1]; also experienced at GitLab [2][3][4]. > > Attaching a simple patch that adds a backend-local flag pgStatXactSkipCounters > in pgstat_database.c that AtEOXact_PgStat_Database() honors to skip > the counter bump. > > Added TAP test that fails on master with 5/0 and passes with the patch. > > If there is agreement on this shape, happy to send patches for all > supported branches. Let me know what you think. Thanks for the report and patch! How to implement a solution depends on what xact_rollback in pg_stat_database is intended to mean. So at first we should consider which rollbacks should it count? The documentation does not currently give an explicit definition. At present, xact_rollback appears to count all rollbacks, explicit or implicit, by any process connected to the database, including regular backends, autovacuum workers, and logical walsenders. If that is the intended definition, then rollbacks implicitly performed by logical walsenders during logical replication should also be counted. Of course, even if we keep that definition, the sudden increase in xact_rollback might still be a problem, so we might need to call pgstat_report_stat() immediately after pgstat_flush_io() in walsender, so the counters continue to be updated periodically during logical replication. On the other hand, your patch seems to assume a different definition: that xact_rollback should count all explicit and implicit rollbacks, except those performed by logical walsenders during logical replication. That would be one possible approach, although it seems a bit odd to exclude only one subset of rollbacks. A third option would be to define xact_rollback more narrowly, counting only rollbacks by regular backends, and excluding rollbacks by processes such as autovacuum or walsender. At least in my view, xact_commit and xact_rollback in pg_stat_database are typically used by DBAs to check whether client transactions are committing or rolling back as expected. From that perspective, it seems intuitive for xact_rollback to count only rollbacks by regular backends. But others may reasonably see it differently. Regards, -- Fujii Masao
-
Re: xact_rollback spikes when logical walsender exits
vignesh C <vignesh21@gmail.com> — 2026-04-21T06:38:24Z
On Fri, 17 Apr 2026 at 20:45, Nikolay Samokhvalov <nik@postgres.ai> wrote: > > Hi hackers, > > There is a bug on logical-replication publishers where every decoded > committed transaction bumps pg_stat_database.xact_rollback. > ReorderBufferProcessTXN() ends each decoded transaction with > AbortCurrentTransaction() for catalog cleanup; in the walsender that > is a top-level abort, so AtEOXact_PgStat_Database(isCommit=false) > increments the backend-local pgStatXactRollback. > > The counts are flushed to shared stats on walsender exit, producing > an acute spike. Result: for production systems with SREs on call and tight > alerting on xact_rollback, this turns routine logical-replication operations > (disabling a subscription, dropping a slot, walsender restart) into > false-positive pages. > > Reported in [1]; also experienced at GitLab [2][3][4]. > > Attaching a simple patch that adds a backend-local flag pgStatXactSkipCounters > in pgstat_database.c that AtEOXact_PgStat_Database() honors to skip > the counter bump. > > Added TAP test that fails on master with 5/0 and passes with the patch. > > If there is agreement on this shape, happy to send patches for all > supported branches. Let me know what you think. Thanks for reporting this and for the patch the problem description matches what I've observed as well. The current behavior could be misleading, since these rollbacks correspond to internal decoding cleanup rather than actual user visible transaction aborts. Another approach could be to introduce a wrapper around AbortCurrentTransaction(), for example AbortCurrentTransactionWithoutUpdateStats(), that skips the AtEOXact_PgStat() call in this case. Thoughts? Regards, Vignesh
-
Re: xact_rollback spikes when logical walsender exits
Nikolay Samokhvalov <nik@postgres.ai> — 2026-05-13T00:06:40Z
On Mon, Apr 20, 2026 at 11:38 PM vignesh C <vignesh21@gmail.com> wrote: > Another approach could be to introduce a wrapper around > AbortCurrentTransaction(), for example > AbortCurrentTransactionWithoutUpdateStats(), that skips the > AtEOXact_PgStat() call in this case. > Thoughts? Thanks -- v2 attached, adopting your wrapper. One scope choice worth flagging: the wrapper suppresses only AtEOXact_PgStat_Database() (the DB-level xact_commit/xact_rollback counter), not all of AtEOXact_PgStat(). Per-relation and subxact stat handling still run, so nothing accumulated during the cleanup is lost. Renamed to AbortCurrentTransactionWithoutXactStats() to match. Nik
-
Re: xact_rollback spikes when logical walsender exits
Nikolay Samokhvalov <nik@postgres.ai> — 2026-07-08T17:37:11Z
Fujii-san, coming back to your questions in this thread, I should have addressed them before sending v2 -- apologies for jumping past them. On Mon, Apr 20, 2026 at 9:35 AM Fujii Masao <masao.fujii@gmail.com> wrote: > Thanks for the report and patch! > > How to implement a solution depends on what xact_rollback in > pg_stat_database > is intended to mean. So at first we should consider which rollbacks should > it count? The documentation does not currently give an explicit definition. > > At present, xact_rollback appears to count all rollbacks, explicit or > implicit, > by any process connected to the database, including regular backends, > autovacuum workers, and logical walsenders. If that is the intended > definition, > then rollbacks implicitly performed by logical walsenders during logical > replication should also be counted. Of course, even if we keep that > definition, > the sudden increase in xact_rollback might still be a problem, so we might > need to call pgstat_report_stat() immediately after pgstat_flush_io() in > walsender, so the counters continue to be updated periodically during > logical replication. > > On the other hand, your patch seems to assume a different definition: that > xact_rollback should count all explicit and implicit rollbacks, except > those > performed by logical walsenders during logical replication. That would be > one possible approach, although it seems a bit odd to exclude only one > subset > of rollbacks. > > A third option would be to define xact_rollback more narrowly, counting > only > rollbacks by regular backends, and excluding rollbacks by processes such as > autovacuum or walsender. At least in my view, xact_commit and xact_rollback > in pg_stat_database are typically used by DBAs to check whether > client transactions are committing or rolling back as expected. From > that perspective, it seems intuitive for xact_rollback to count only > rollbacks > by regular backends. But others may reasonably see it differently. > On the definitional question: I'd argue the walsender case isn't a "subset of rollbacks being excluded". The transactions being decoded actually committed; the abort in ReorderBufferProcessTXN() is internal cleanup of the catalog-snapshot transaction, not a transaction outcome. Notably, xact_commit is not incremented for these, so on any publisher the commit/rollback pair becomes internally inconsistent in proportion to decoded throughput. So counting these as rollbacks seems like a miscount under any of the three definitions. On option 1 (periodic pgstat_report_stat() in walsender): that would trade the exit spike for a steady inflated rollback rate, which seems worse for the metric's usefulness. Periodic flushing may still be worth doing independently for other stats; happy to send that as a separate patch. On option 3: I agree this matches how DBAs actually use these counters, and I'd support it. Since it changes observable behavior for autovacuum etc., it seems like master-only material, together with explicitly documenting the definition. Concretely, I propose splitting: 1) 0001 (v3, attached): narrow fix for the walsender miscount, intended as a back-patchable bug fix -- this is what's paging people in production today. Rebased on current master; the TAP test now also verifies that xact_commit does not change as a function of decoded transactions (walsender shutdown has a small fixed bookkeeping delta, handled via a control run). 2) 0002 (attached, draft): master-only implementation of option 3 -- count xact_commit/xact_rollback only for regular client backends -- plus a doc change defining both columns explicitly. It also removes the now-redundant parallel argument from AtEOXact_PgStat() and AtEOXact_PgStat_Database(), since parallel workers are background workers and are excluded by the backend-type check. 0002 has no test yet; if the definition is agreed on, I'll extend the new TAP test to cover the autovacuum/walsender exclusion. One consequence of option 3 worth stating explicitly: logical replication apply workers are background workers, so transactions they replay on a subscriber would no longer be counted in the subscriber's xact_commit. That's arguably correct under the "client transactions" definition, but it does change what subscriber-side monitoring sees, so I wanted to flag it rather than have it discovered later. Added to the July commitfest: https://commitfest.postgresql.org/patch/6992/ Looking forward to your thoughts. Nik