From 13bbf24d172d44be5e4b002dc4c6eed90ae60d07 Mon Sep 17 00:00:00 2001 From: Sami Imseih Date: Thu, 20 Aug 2026 19:08:01 +0000 Subject: [PATCH v8 2/3] pgstat: Split table stat counters into transactional and nontransactional groups PgStat_TableCounts currently stores all per table pending counters in one struct. Split it into two nested structs. PgStat_TableCountsNonTxn holds counters that are recorded whether the transaction commits or aborts, such as scans. PgStat_TableCountsTxn holds counters whose effect depends on transaction outcome, such as tuple modification counts. Move truncdropped out of PgStat_TableCountsTxn and into PgStat_RelationStatus so the transactional and nontransactional substructs contain only counters. Future work will compare these substructs directly to detect changes between flushes, so enforce that they contain no padding with StaticAssertDecl checks. This follows the same pattern used for PgStat_HashKey in 3cd3a039da7f3. Because truncdropped no longer lives inside PgStat_TableCounts, teach the flush path to treat a pending truncate as work even when the counter substructs are otherwise all zero. Add regression coverage for that case so moving truncdropped preserves truncate reset behavior. This preserves existing behavior and prepares for a later patch that flushes stats that are safe to flush during a transaction. Author: Sami Imseih Reviewed-by: Bertrand Drouvot Discussion: https://postgr.es/m/aoU32VTkUtpdUewB%40paquier.xyz --- src/backend/utils/activity/pgstat_relation.c | 124 +++++++++--------- src/backend/utils/adt/pgstatfuncs.c | 24 ++-- src/include/pgstat.h | 48 +++++-- .../test_custom_stats/test_custom_var_stats.c | 11 +- src/test/regress/expected/stats.out | 25 +++- src/test/regress/sql/stats.sql | 14 ++ src/tools/pgindent/typedefs.list | 2 + 7 files changed, 164 insertions(+), 84 deletions(-) diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index 346730ac062..0b66c925f4f 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -369,7 +369,7 @@ pgstat_report_analyze(Relation rel, deadtuples -= trans->tuples_updated + trans->tuples_deleted; } /* count stuff inserted by already-aborted subxacts, too */ - deadtuples -= rel->pgstat_info->tab.counts.delta_dead_tuples; + deadtuples -= rel->pgstat_info->tab.counts.txn.delta_dead_tuples; /* Since ANALYZE's counts are estimates, we could have underflowed */ livetuples = Max(livetuples, 0); deadtuples = Max(deadtuples, 0); @@ -459,9 +459,9 @@ pgstat_count_heap_update(Relation rel, bool hot, bool newpage) * nontransactional, so just advance them */ if (hot) - pgstat_info->tab.counts.tuples_hot_updated++; + pgstat_info->tab.counts.txn.tuples_hot_updated++; else if (newpage) - pgstat_info->tab.counts.tuples_newpage_updated++; + pgstat_info->tab.counts.txn.tuples_newpage_updated++; } } @@ -519,7 +519,7 @@ pgstat_update_heap_dead_tuples(Relation rel, int delta) Assert(pgstat_info->kind == PGSTAT_KIND_RELATION); - pgstat_info->tab.counts.delta_dead_tuples -= delta; + pgstat_info->tab.counts.txn.delta_dead_tuples -= delta; } } @@ -603,9 +603,9 @@ find_relstat_entry_kind(PgStat_Kind kind, Oid rel_id) */ for (trans = relentry->tab.trans; trans != NULL; trans = trans->upper) { - relstatus->tab.counts.tuples_inserted += trans->tuples_inserted; - relstatus->tab.counts.tuples_updated += trans->tuples_updated; - relstatus->tab.counts.tuples_deleted += trans->tuples_deleted; + relstatus->tab.counts.txn.tuples_inserted += trans->tuples_inserted; + relstatus->tab.counts.txn.tuples_updated += trans->tuples_updated; + relstatus->tab.counts.txn.tuples_deleted += trans->tuples_deleted; } return relstatus; @@ -636,33 +636,33 @@ AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit) if (!isCommit) restore_truncdrop_counters(trans); /* count attempted actions regardless of commit/abort */ - relstat->tab.counts.tuples_inserted += trans->tuples_inserted; - relstat->tab.counts.tuples_updated += trans->tuples_updated; - relstat->tab.counts.tuples_deleted += trans->tuples_deleted; + relstat->tab.counts.txn.tuples_inserted += trans->tuples_inserted; + relstat->tab.counts.txn.tuples_updated += trans->tuples_updated; + relstat->tab.counts.txn.tuples_deleted += trans->tuples_deleted; if (isCommit) { - relstat->tab.counts.truncdropped = trans->truncdropped; + relstat->tab.truncdropped = trans->truncdropped; if (trans->truncdropped) { /* forget live/dead stats seen by backend thus far */ - relstat->tab.counts.delta_live_tuples = 0; - relstat->tab.counts.delta_dead_tuples = 0; + relstat->tab.counts.txn.delta_live_tuples = 0; + relstat->tab.counts.txn.delta_dead_tuples = 0; } /* insert adds a live tuple, delete removes one */ - relstat->tab.counts.delta_live_tuples += + relstat->tab.counts.txn.delta_live_tuples += trans->tuples_inserted - trans->tuples_deleted; /* update and delete each create a dead tuple */ - relstat->tab.counts.delta_dead_tuples += + relstat->tab.counts.txn.delta_dead_tuples += trans->tuples_updated + trans->tuples_deleted; /* insert, update, delete each count as one change event */ - relstat->tab.counts.changed_tuples += + relstat->tab.counts.txn.changed_tuples += trans->tuples_inserted + trans->tuples_updated + trans->tuples_deleted; } else { /* inserted tuples are dead, deleted tuples are unaffected */ - relstat->tab.counts.delta_dead_tuples += + relstat->tab.counts.txn.delta_dead_tuples += trans->tuples_inserted + trans->tuples_updated; /* an aborted xact generates no changed_tuple events */ } @@ -742,11 +742,11 @@ AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, in /* first restore values obliterated by truncate/drop */ restore_truncdrop_counters(trans); /* count attempted actions regardless of commit/abort */ - relstat->tab.counts.tuples_inserted += trans->tuples_inserted; - relstat->tab.counts.tuples_updated += trans->tuples_updated; - relstat->tab.counts.tuples_deleted += trans->tuples_deleted; + relstat->tab.counts.txn.tuples_inserted += trans->tuples_inserted; + relstat->tab.counts.txn.tuples_updated += trans->tuples_updated; + relstat->tab.counts.txn.tuples_deleted += trans->tuples_deleted; /* inserted tuples are dead, deleted tuples are unaffected */ - relstat->tab.counts.delta_dead_tuples += + relstat->tab.counts.txn.delta_dead_tuples += trans->tuples_inserted + trans->tuples_updated; relstat->tab.trans = trans->upper; pfree(trans); @@ -826,21 +826,21 @@ pgstat_twophase_postcommit(FullTransactionId fxid, uint16 info, pgstat_info = pgstat_prep_relation_pending(PGSTAT_KIND_RELATION, rec->id, rec->shared); /* Same math as in AtEOXact_PgStat, commit case */ - pgstat_info->tab.counts.tuples_inserted += rec->tuples_inserted; - pgstat_info->tab.counts.tuples_updated += rec->tuples_updated; - pgstat_info->tab.counts.tuples_deleted += rec->tuples_deleted; - pgstat_info->tab.counts.truncdropped = rec->truncdropped; + pgstat_info->tab.counts.txn.tuples_inserted += rec->tuples_inserted; + pgstat_info->tab.counts.txn.tuples_updated += rec->tuples_updated; + pgstat_info->tab.counts.txn.tuples_deleted += rec->tuples_deleted; + pgstat_info->tab.truncdropped = rec->truncdropped; if (rec->truncdropped) { /* forget live/dead stats seen by backend thus far */ - pgstat_info->tab.counts.delta_live_tuples = 0; - pgstat_info->tab.counts.delta_dead_tuples = 0; + pgstat_info->tab.counts.txn.delta_live_tuples = 0; + pgstat_info->tab.counts.txn.delta_dead_tuples = 0; } - pgstat_info->tab.counts.delta_live_tuples += + pgstat_info->tab.counts.txn.delta_live_tuples += rec->tuples_inserted - rec->tuples_deleted; - pgstat_info->tab.counts.delta_dead_tuples += + pgstat_info->tab.counts.txn.delta_dead_tuples += rec->tuples_updated + rec->tuples_deleted; - pgstat_info->tab.counts.changed_tuples += + pgstat_info->tab.counts.txn.changed_tuples += rec->tuples_inserted + rec->tuples_updated + rec->tuples_deleted; } @@ -868,10 +868,10 @@ pgstat_twophase_postabort(FullTransactionId fxid, uint16 info, rec->tuples_updated = rec->updated_pre_truncdrop; rec->tuples_deleted = rec->deleted_pre_truncdrop; } - pgstat_info->tab.counts.tuples_inserted += rec->tuples_inserted; - pgstat_info->tab.counts.tuples_updated += rec->tuples_updated; - pgstat_info->tab.counts.tuples_deleted += rec->tuples_deleted; - pgstat_info->tab.counts.delta_dead_tuples += + pgstat_info->tab.counts.txn.tuples_inserted += rec->tuples_inserted; + pgstat_info->tab.counts.txn.tuples_updated += rec->tuples_updated; + pgstat_info->tab.counts.txn.tuples_deleted += rec->tuples_deleted; + pgstat_info->tab.counts.txn.delta_dead_tuples += rec->tuples_inserted + rec->tuples_updated; } @@ -897,8 +897,14 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) lstats = (PgStat_RelationStatus *) entry_ref->pending; shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats; - /* ignore entries that didn't accumulate any actual counts */ - if (pg_memory_is_all_zeros(&lstats->tab.counts, + /* + * Ignore entries that didn't accumulate any actual counts. If the table + * was truncated, we still need to flush the entry to reset the live/dead + * counters and ins_since_vacuum even when no other counts were + * accumulated. + */ + if (!lstats->tab.truncdropped && + pg_memory_is_all_zeros(&lstats->tab.counts, sizeof(struct PgStat_TableCounts))) return true; @@ -914,35 +920,35 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) /* add the values to the shared entry. */ tabentry = &shtabstats->stats; - tabentry->numscans += lstats->tab.counts.numscans; - if (lstats->tab.counts.numscans) + tabentry->numscans += lstats->tab.counts.nontxn.numscans; + if (lstats->tab.counts.nontxn.numscans) { TimestampTz t = GetCurrentTransactionStopTimestamp(); if (t > tabentry->lastscan) tabentry->lastscan = t; } - tabentry->tuples_returned += lstats->tab.counts.tuples_returned; - tabentry->tuples_fetched += lstats->tab.counts.tuples_fetched; - tabentry->tuples_inserted += lstats->tab.counts.tuples_inserted; - tabentry->tuples_updated += lstats->tab.counts.tuples_updated; - tabentry->tuples_deleted += lstats->tab.counts.tuples_deleted; - tabentry->tuples_hot_updated += lstats->tab.counts.tuples_hot_updated; - tabentry->tuples_newpage_updated += lstats->tab.counts.tuples_newpage_updated; + tabentry->tuples_returned += lstats->tab.counts.nontxn.tuples_returned; + tabentry->tuples_fetched += lstats->tab.counts.nontxn.tuples_fetched; + tabentry->tuples_inserted += lstats->tab.counts.txn.tuples_inserted; + tabentry->tuples_updated += lstats->tab.counts.txn.tuples_updated; + tabentry->tuples_deleted += lstats->tab.counts.txn.tuples_deleted; + tabentry->tuples_hot_updated += lstats->tab.counts.txn.tuples_hot_updated; + tabentry->tuples_newpage_updated += lstats->tab.counts.txn.tuples_newpage_updated; /* * If table was truncated/dropped, first reset the live/dead counters. */ - if (lstats->tab.counts.truncdropped) + if (lstats->tab.truncdropped) { tabentry->live_tuples = 0; tabentry->dead_tuples = 0; tabentry->ins_since_vacuum = 0; } - tabentry->live_tuples += lstats->tab.counts.delta_live_tuples; - tabentry->dead_tuples += lstats->tab.counts.delta_dead_tuples; - tabentry->mod_since_analyze += lstats->tab.counts.changed_tuples; + tabentry->live_tuples += lstats->tab.counts.txn.delta_live_tuples; + tabentry->dead_tuples += lstats->tab.counts.txn.delta_dead_tuples; + tabentry->mod_since_analyze += lstats->tab.counts.txn.changed_tuples; /* * Using tuples_inserted to update ins_since_vacuum does mean that we'll @@ -951,10 +957,10 @@ 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.tuples_inserted; + tabentry->ins_since_vacuum += lstats->tab.counts.txn.tuples_inserted; - tabentry->blocks_fetched += lstats->tab.counts.blocks_fetched; - tabentry->blocks_hit += lstats->tab.counts.blocks_hit; + tabentry->blocks_fetched += lstats->tab.counts.nontxn.blocks_fetched; + tabentry->blocks_hit += lstats->tab.counts.nontxn.blocks_hit; /* Clamp live_tuples in case of negative delta_live_tuples */ tabentry->live_tuples = Max(tabentry->live_tuples, 0); @@ -964,13 +970,13 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) pgstat_unlock_entry(entry_ref); /* The entry was successfully flushed, add the same to database stats */ - dbentry->tuples_returned += lstats->tab.counts.tuples_returned; - dbentry->tuples_fetched += lstats->tab.counts.tuples_fetched; - dbentry->tuples_inserted += lstats->tab.counts.tuples_inserted; - dbentry->tuples_updated += lstats->tab.counts.tuples_updated; - dbentry->tuples_deleted += lstats->tab.counts.tuples_deleted; - dbentry->blocks_fetched += lstats->tab.counts.blocks_fetched; - dbentry->blocks_hit += lstats->tab.counts.blocks_hit; + dbentry->tuples_returned += lstats->tab.counts.nontxn.tuples_returned; + dbentry->tuples_fetched += lstats->tab.counts.nontxn.tuples_fetched; + dbentry->tuples_inserted += lstats->tab.counts.txn.tuples_inserted; + dbentry->tuples_updated += lstats->tab.counts.txn.tuples_updated; + dbentry->tuples_deleted += lstats->tab.counts.txn.tuples_deleted; + dbentry->blocks_fetched += lstats->tab.counts.nontxn.blocks_fetched; + dbentry->blocks_hit += lstats->tab.counts.nontxn.blocks_hit; return true; } diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 0d47d745c18..5c3b1d51091 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -1922,7 +1922,7 @@ pg_stat_get_slru(PG_FUNCTION_ARGS) return (Datum) 0; } -#define PG_STAT_GET_XACT_RELENTRY_INT64(stat) \ +#define PG_STAT_GET_XACT_RELENTRY_INT64(group, stat) \ Datum \ CppConcat(pg_stat_get_xact_,stat)(PG_FUNCTION_ARGS) \ { \ @@ -1934,40 +1934,40 @@ CppConcat(pg_stat_get_xact_,stat)(PG_FUNCTION_ARGS) \ relid)) == NULL) \ result = 0; \ else \ - result = (int64) (tabentry->tab.counts.stat); \ + result = (int64) (tabentry->tab.counts.group.stat); \ \ PG_RETURN_INT64(result); \ } /* pg_stat_get_xact_numscans */ -PG_STAT_GET_XACT_RELENTRY_INT64(numscans) +PG_STAT_GET_XACT_RELENTRY_INT64(nontxn, numscans) /* pg_stat_get_xact_tuples_returned */ -PG_STAT_GET_XACT_RELENTRY_INT64(tuples_returned) +PG_STAT_GET_XACT_RELENTRY_INT64(nontxn, tuples_returned) /* pg_stat_get_xact_tuples_fetched */ -PG_STAT_GET_XACT_RELENTRY_INT64(tuples_fetched) +PG_STAT_GET_XACT_RELENTRY_INT64(nontxn, tuples_fetched) /* pg_stat_get_xact_tuples_hot_updated */ -PG_STAT_GET_XACT_RELENTRY_INT64(tuples_hot_updated) +PG_STAT_GET_XACT_RELENTRY_INT64(txn, tuples_hot_updated) /* pg_stat_get_xact_tuples_newpage_updated */ -PG_STAT_GET_XACT_RELENTRY_INT64(tuples_newpage_updated) +PG_STAT_GET_XACT_RELENTRY_INT64(txn, tuples_newpage_updated) /* pg_stat_get_xact_blocks_fetched */ -PG_STAT_GET_XACT_RELENTRY_INT64(blocks_fetched) +PG_STAT_GET_XACT_RELENTRY_INT64(nontxn, blocks_fetched) /* pg_stat_get_xact_blocks_hit */ -PG_STAT_GET_XACT_RELENTRY_INT64(blocks_hit) +PG_STAT_GET_XACT_RELENTRY_INT64(nontxn, blocks_hit) /* pg_stat_get_xact_tuples_inserted */ -PG_STAT_GET_XACT_RELENTRY_INT64(tuples_inserted) +PG_STAT_GET_XACT_RELENTRY_INT64(txn, tuples_inserted) /* pg_stat_get_xact_tuples_updated */ -PG_STAT_GET_XACT_RELENTRY_INT64(tuples_updated) +PG_STAT_GET_XACT_RELENTRY_INT64(txn, tuples_updated) /* pg_stat_get_xact_tuples_deleted */ -PG_STAT_GET_XACT_RELENTRY_INT64(tuples_deleted) +PG_STAT_GET_XACT_RELENTRY_INT64(txn, tuples_deleted) /* * Accessor macro for in-transaction index stats. diff --git a/src/include/pgstat.h b/src/include/pgstat.h index 204782fd630..990d6371e5a 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -121,9 +121,15 @@ typedef struct PgStat_BackendSubEntry /* ---------- * PgStat_TableCounts The actual per-table counts kept by a backend * - * This struct should contain only actual event counters, because we make use + * The counters are split into two structs. PgStat_TableCountsNonTxn holds + * counters that are recorded whether the transaction commits or aborts, while + * PgStat_TableCountsTxn holds counters whose effect depends on the transaction + * outcome. Both are combined in PgStat_TableCounts. + * + * These structs should contain only actual event counters, because we make use * of pg_memory_is_all_zeros() to detect whether there are any stats updates - * to apply. + * to apply. StaticAssertDecl() checks below ensure that these substructs + * contain no padding. * * It is a component of PgStat_RelationStatus (within-backend state, for * table data). @@ -138,26 +144,45 @@ typedef struct PgStat_BackendSubEntry * Note that delta_live_tuples and delta_dead_tuples can be negative! * ---------- */ -typedef struct PgStat_TableCounts +typedef struct PgStat_TableCountsNonTxn { PgStat_Counter numscans; PgStat_Counter tuples_returned; PgStat_Counter tuples_fetched; + PgStat_Counter blocks_fetched; + PgStat_Counter blocks_hit; +} PgStat_TableCountsNonTxn; + +typedef struct PgStat_TableCountsTxn +{ PgStat_Counter tuples_inserted; PgStat_Counter tuples_updated; PgStat_Counter tuples_deleted; PgStat_Counter tuples_hot_updated; PgStat_Counter tuples_newpage_updated; - bool truncdropped; PgStat_Counter delta_live_tuples; PgStat_Counter delta_dead_tuples; PgStat_Counter changed_tuples; +} PgStat_TableCountsTxn; - PgStat_Counter blocks_fetched; - PgStat_Counter blocks_hit; +/* Keep these in sync with the fields in the corresponding counter structs. */ +#define PGSTAT_TABLECOUNTS_NON_TXN_NUM_COUNTERS 5 +#define PGSTAT_TABLECOUNTS_TXN_NUM_COUNTERS 8 + +StaticAssertDecl(sizeof(PgStat_TableCountsNonTxn) == + PGSTAT_TABLECOUNTS_NON_TXN_NUM_COUNTERS * sizeof(PgStat_Counter), + "PgStat_TableCountsNonTxn has padding"); +StaticAssertDecl(sizeof(PgStat_TableCountsTxn) == + PGSTAT_TABLECOUNTS_TXN_NUM_COUNTERS * sizeof(PgStat_Counter), + "PgStat_TableCountsTxn has padding"); + +typedef struct PgStat_TableCounts +{ + PgStat_TableCountsNonTxn nontxn; + PgStat_TableCountsTxn txn; } PgStat_TableCounts; /* ---------- @@ -210,6 +235,7 @@ typedef struct PgStat_RelationStatus { Oid id; /* table's OID */ bool shared; /* is it a shared catalog? */ + bool truncdropped; /* pending truncate/drop reset */ struct PgStat_TableXactStatus *trans; /* lowest subxact's counts */ PgStat_TableCounts counts; /* event counts to be sent */ } tab; @@ -779,7 +805,7 @@ extern void pgstat_report_analyze(Relation rel, if (pgstat_should_count_relation(rel)) \ { \ Assert((rel)->pgstat_info->kind == PGSTAT_KIND_RELATION); \ - (rel)->pgstat_info->tab.counts.numscans++; \ + (rel)->pgstat_info->tab.counts.nontxn.numscans++; \ } \ } while (0) #define pgstat_count_heap_getnext(rel) \ @@ -787,7 +813,7 @@ extern void pgstat_report_analyze(Relation rel, if (pgstat_should_count_relation(rel)) \ { \ Assert((rel)->pgstat_info->kind == PGSTAT_KIND_RELATION); \ - (rel)->pgstat_info->tab.counts.tuples_returned++; \ + (rel)->pgstat_info->tab.counts.nontxn.tuples_returned++; \ } \ } while (0) #define pgstat_count_heap_fetch(rel) \ @@ -797,7 +823,7 @@ extern void pgstat_report_analyze(Relation rel, if ((rel)->pgstat_info->kind == PGSTAT_KIND_INDEX) \ (rel)->pgstat_info->idx.tuples_fetched++; \ else \ - (rel)->pgstat_info->tab.counts.tuples_fetched++; \ + (rel)->pgstat_info->tab.counts.nontxn.tuples_fetched++; \ } \ } while (0) #define pgstat_count_index_scan(rel) \ @@ -823,7 +849,7 @@ extern void pgstat_report_analyze(Relation rel, if ((rel)->pgstat_info->kind == PGSTAT_KIND_INDEX) \ (rel)->pgstat_info->idx.blocks_fetched++; \ else \ - (rel)->pgstat_info->tab.counts.blocks_fetched++; \ + (rel)->pgstat_info->tab.counts.nontxn.blocks_fetched++; \ } \ } while (0) #define pgstat_count_buffer_hit(rel) \ @@ -833,7 +859,7 @@ extern void pgstat_report_analyze(Relation rel, if ((rel)->pgstat_info->kind == PGSTAT_KIND_INDEX) \ (rel)->pgstat_info->idx.blocks_hit++; \ else \ - (rel)->pgstat_info->tab.counts.blocks_hit++; \ + (rel)->pgstat_info->tab.counts.nontxn.blocks_hit++; \ } \ } while (0) diff --git a/src/test/modules/test_custom_stats/test_custom_var_stats.c b/src/test/modules/test_custom_stats/test_custom_var_stats.c index a39ada0b67c..bf4c8fa69c3 100644 --- a/src/test/modules/test_custom_stats/test_custom_var_stats.c +++ b/src/test/modules/test_custom_stats/test_custom_var_stats.c @@ -56,10 +56,19 @@ PG_MODULE_MAGIC_EXT( *-------------------------------------------------------------------------- */ -/* Backend-local pending statistics before flush to shared memory */ +/* + * Backend-local pending statistics before flush to shared memory. + * + * numcalls is non-transactional and is flushed on any flush, including an + * in-transaction one. numcalls_txn is transactional and becomes visible in + * shared memory only at a transaction boundary; this demonstrates how a custom + * kind can defer transaction-dependent counters (see the flush callback). + */ typedef struct PgStat_StatCustomVarEntry { PgStat_Counter numcalls; /* times statistic was incremented */ + PgStat_Counter numcalls_txn; /* likewise, but flushed only at a + * transaction boundary */ } PgStat_StatCustomVarEntry; /* Shared memory statistics entry visible to all backends */ diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index c682a9ed60a..cbe0e793f22 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -157,6 +157,7 @@ CREATE TABLE trunc_stats_test1(id serial, stuff text); CREATE TABLE trunc_stats_test2(id serial); CREATE TABLE trunc_stats_test3(id serial, stuff text); CREATE TABLE trunc_stats_test4(id serial); +CREATE TABLE trunc_stats_test5(id serial); -- check that n_live_tup is reset to 0 after truncate INSERT INTO trunc_stats_test DEFAULT VALUES; INSERT INTO trunc_stats_test DEFAULT VALUES; @@ -202,6 +203,19 @@ INSERT INTO trunc_stats_test4 DEFAULT VALUES; TRUNCATE trunc_stats_test4; INSERT INTO trunc_stats_test4 DEFAULT VALUES; ROLLBACK; +-- ensure a truncate-only pending entry still flushes +INSERT INTO trunc_stats_test5 DEFAULT VALUES; +INSERT INTO trunc_stats_test5 DEFAULT VALUES; +INSERT INTO trunc_stats_test5 DEFAULT VALUES; +UPDATE trunc_stats_test5 SET id = id + 10 WHERE id = 1; +DELETE FROM trunc_stats_test5 WHERE id = 2; +SELECT pg_stat_force_next_flush(); + pg_stat_force_next_flush +-------------------------- + +(1 row) + +TRUNCATE trunc_stats_test5; -- do a seqscan SELECT count(*) FROM tenk2; count @@ -239,7 +253,16 @@ SELECT relname, n_tup_ins, n_tup_upd, n_tup_del, n_live_tup, n_dead_tup trunc_stats_test2 | 1 | 0 | 0 | 1 | 0 trunc_stats_test3 | 4 | 0 | 0 | 2 | 2 trunc_stats_test4 | 2 | 0 | 0 | 0 | 2 -(5 rows) + trunc_stats_test5 | 3 | 1 | 1 | 0 | 0 +(6 rows) + +SELECT relname, n_ins_since_vacuum + FROM pg_stat_user_tables + WHERE relname = 'trunc_stats_test5'; + relname | n_ins_since_vacuum +-------------------+-------------------- + trunc_stats_test5 | 0 +(1 row) SELECT st.seq_scan >= pr.seq_scan + 1, st.seq_tup_read >= pr.seq_tup_read + cl.reltuples, diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index d4623c32cd3..0e83218c437 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -51,6 +51,7 @@ CREATE TABLE trunc_stats_test1(id serial, stuff text); CREATE TABLE trunc_stats_test2(id serial); CREATE TABLE trunc_stats_test3(id serial, stuff text); CREATE TABLE trunc_stats_test4(id serial); +CREATE TABLE trunc_stats_test5(id serial); -- check that n_live_tup is reset to 0 after truncate INSERT INTO trunc_stats_test DEFAULT VALUES; @@ -103,6 +104,15 @@ TRUNCATE trunc_stats_test4; INSERT INTO trunc_stats_test4 DEFAULT VALUES; ROLLBACK; +-- ensure a truncate-only pending entry still flushes +INSERT INTO trunc_stats_test5 DEFAULT VALUES; +INSERT INTO trunc_stats_test5 DEFAULT VALUES; +INSERT INTO trunc_stats_test5 DEFAULT VALUES; +UPDATE trunc_stats_test5 SET id = id + 10 WHERE id = 1; +DELETE FROM trunc_stats_test5 WHERE id = 2; +SELECT pg_stat_force_next_flush(); +TRUNCATE trunc_stats_test5; + -- do a seqscan SELECT count(*) FROM tenk2; -- do an indexscan @@ -122,6 +132,10 @@ SELECT relname, n_tup_ins, n_tup_upd, n_tup_del, n_live_tup, n_dead_tup FROM pg_stat_user_tables WHERE relname like 'trunc_stats_test%' order by relname; +SELECT relname, n_ins_since_vacuum + FROM pg_stat_user_tables + WHERE relname = 'trunc_stats_test5'; + SELECT st.seq_scan >= pr.seq_scan + 1, st.seq_tup_read >= pr.seq_tup_read + cl.reltuples, st.idx_scan >= pr.idx_scan + 1, diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index 6c366d3a523..0ecb22a8f2d 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -2371,6 +2371,8 @@ PgStat_StatTabEntry PgStat_StatsFileOp PgStat_SubXactStatus PgStat_TableCounts +PgStat_TableCountsNonTxn +PgStat_TableCountsTxn PgStat_TableXactStatus PgStat_WalCounters PgStat_WalStats -- 2.47.3