From e3bd8b150af470ff3b2a7d9651350c5fa5e87d3d Mon Sep 17 00:00:00 2001 From: Manuel Reyes Bravo Date: Thu, 15 Oct 2026 08:47:11 +0300 Subject: [PATCH v2 2/2] Don't count pending inserts in ins_since_vacuum after VACUUM Like live_tuples and dead_tuples, VACUUM resets ins_since_vacuum while inserts committed earlier by the same backend may still be pending, and the next flush then added them back, so e.g. INSERT of N rows followed by VACUUM left n_ins_since_vacuum at N, which could trigger a needless insert-driven autovacuum. We can't simply forget the pending tuples_inserted, since it also feeds the cumulative tuples_inserted counter, so remember how many of them VACUUM has already covered and have the flush skip those for ins_since_vacuum. Author: Manuel Reyes Bravo Reviewed-by: Ilya Staroverov Tested-by: Sebastian Webber Discussion: https://postgr.es/m/1DC9D815-8754-4A76-B2B1-99C1A614A55A@localhost.localdomain --- src/backend/utils/activity/pgstat_relation.c | 10 +++++++++- src/include/pgstat.h | 2 ++ src/test/regress/expected/stats.out | 12 ++++++------ src/test/regress/sql/stats.sql | 2 +- 4 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index cfebd0cd6..9e6559596 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -279,6 +279,11 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples, * stats have not been flushed yet. Forget the pending deltas for those * counters, else they would be added on top of the new values at the * next flush. A pending truncation reset is superseded as well. + * + * Likewise, the pending inserts must not count towards ins_since_vacuum, + * which is reset below. We can't just zero tuples_inserted, which also + * feeds the cumulative tuples_inserted counter, so remember how many of + * them the flush has to skip. */ if (pgstat_should_count_relation(rel)) { @@ -288,6 +293,8 @@ pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples, pending->tab.counts_xact.delta_live_tuples = 0; pending->tab.counts_xact.delta_dead_tuples = 0; pending->tab.truncdropped = false; + pending->tab.ins_before_vacuum = + pending->tab.counts_xact.tuples_inserted; } /* Store the data in the table's hash table entry. */ @@ -993,7 +1000,8 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) * triggering for inserts more often than they maybe should, which is * probably not going to be common enough to be too concerned about here. */ - tabentry->ins_since_vacuum += lstats->tab.counts_xact.tuples_inserted; + tabentry->ins_since_vacuum += lstats->tab.counts_xact.tuples_inserted - + lstats->tab.ins_before_vacuum; tabentry->blocks_fetched += lstats->tab.counts.blocks_fetched; tabentry->blocks_hit += lstats->tab.counts.blocks_hit; diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 187d82c96..6b6a705ab 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -236,6 +236,8 @@ typedef struct PgStat_RelationStatus struct PgStat_TableXactStatus *trans; /* lowest subxact's counts */ PgStat_TableCounts counts; /* event counts to be sent */ PgStat_TableCountsXact counts_xact; /* transactional counts */ + /* pending tuples_inserted already covered by a VACUUM */ + PgStat_Counter ins_before_vacuum; } tab; /* index counters */ diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index ea6b260d4..ea7f47d63 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -334,14 +334,14 @@ SELECT pg_stat_force_next_flush(); (1 row) -SELECT relname, n_live_tup, n_dead_tup, n_mod_since_analyze +SELECT relname, n_live_tup, n_dead_tup, n_mod_since_analyze, n_ins_since_vacuum FROM pg_stat_user_tables WHERE relname LIKE 'stats_pending_%' ORDER BY relname; - relname | n_live_tup | n_dead_tup | n_mod_since_analyze ------------------------+------------+------------+--------------------- - stats_pending_analyze | 6 | 4 | 0 - stats_pending_trunc | 3 | 0 | 13 - stats_pending_vacuum | 10 | 0 | 10 + relname | n_live_tup | n_dead_tup | n_mod_since_analyze | n_ins_since_vacuum +-----------------------+------------+------------+---------------------+-------------------- + stats_pending_analyze | 6 | 4 | 0 | 10 + stats_pending_trunc | 3 | 0 | 13 | 3 + stats_pending_vacuum | 10 | 0 | 10 | 0 (3 rows) DROP PROCEDURE stats_pending_proc(); diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index d446a7fd9..fd30a09ce 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -185,7 +185,7 @@ CALL stats_pending_proc(); INSERT INTO stats_pending_vacuum SELECT generate_series(1, 10); VACUUM stats_pending_vacuum; SELECT pg_stat_force_next_flush(); -SELECT relname, n_live_tup, n_dead_tup, n_mod_since_analyze +SELECT relname, n_live_tup, n_dead_tup, n_mod_since_analyze, n_ins_since_vacuum FROM pg_stat_user_tables WHERE relname LIKE 'stats_pending_%' ORDER BY relname; DROP PROCEDURE stats_pending_proc(); -- 2.43.0