From 375eaa7ef8bef182629861d35d798101a7e7fce6 Mon Sep 17 00:00:00 2001 From: Alena Rybakina Date: Mon, 30 Mar 2026 09:07:24 +0300 Subject: [PATCH v43 9/9] Track table VM stability. Add visible_page_marks_cleared and frozen_page_marks_cleared counters to pg_stat_all_tables tracking the number of times the all-visible and all-frozen bits are cleared in the visibility map. These bits are cleared by backend processes during regular DML operations. Hence, the counters are placed in table statistic entry. A high visible_page_marks_cleared rate relative to DML volume indicates that modifications are scattered across previously-clean pages rather than concentrated on already-dirty ones, causing index-only scans to fall back to heap fetches. A high frozen_page_marks_cleared rate indicates that vacuum's freezing work is being frequently undone by concurrent DML. Authors: Alena Rybakina , Andrei Lepikhov , Andrei Zubkov Reviewed-by: Dilip Kumar , Masahiko Sawada , Ilia Evdokimov , Jian He , Kirill Reshke , Alexander Korotkov , Jim Nasby , Sami Imseih , Karina Litskevich , Andrey Borodin --- doc/src/sgml/monitoring.sgml | 32 +++ src/backend/access/heap/heapam.c | 16 +- src/backend/access/heap/pruneheap.c | 2 +- src/backend/access/heap/visibilitymap.c | 45 +++++ src/backend/catalog/system_views.sql | 4 +- src/backend/utils/activity/pgstat_relation.c | 2 + src/backend/utils/adt/pgstatfuncs.c | 6 + src/include/access/visibilitymap.h | 2 + src/include/catalog/pg_proc.dat | 10 + src/include/pgstat.h | 33 +++- .../expected/vacuum-extending-freeze.out | 185 ++++++++++++++++++ src/test/isolation/isolation_schedule | 1 + .../specs/vacuum-extending-freeze.spec | 117 +++++++++++ src/test/regress/expected/rules.out | 12 +- 14 files changed, 453 insertions(+), 14 deletions(-) create mode 100644 src/test/isolation/expected/vacuum-extending-freeze.out create mode 100644 src/test/isolation/specs/vacuum-extending-freeze.spec diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 28630e3d67a..d39659811ef 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -4775,6 +4775,38 @@ description | Waiting for a newly initialized WAL file to reach durable storage + + + visible_page_marks_cleared bigint + + + Number of times the all-visible mark in the + visibility map was cleared for + pages of this table. The all-visible mark of a heap page is + cleared whenever a backend process modifies a page that was + previously marked all-visible by vacuum activity (whether manual + VACUUM or autovacuum). The page must then be + processed again by vacuum on a subsequent run. A high rate of + change in this counter means that vacuum has to repeatedly + re-process pages of this table. + + + + + + frozen_page_marks_cleared bigint + + + Number of times the all-frozen mark in the + visibility map was cleared for + pages of this table. The all-frozen mark of a heap page is cleared + whenever a backend process modifies a page that was previously + marked all-frozen by vacuum activity (manual VACUUM + or autovacuum). The page must then be processed again by vacuum on + the next freeze run for this table. + + + last_vacuum timestamp with time zone diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 9ebb1b35d37..8d9407b5665 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -2071,7 +2071,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, if (clear_all_visible) { /* It's possible the VM bits were already clear */ - if (visibilitymap_clear(relation->rd_locator, + if (visibilitymap_clear_rel(relation, ItemPointerGetBlockNumber(&(heaptup->t_self)), vmbuffer, VISIBILITYMAP_VALID_BITS)) vmbuffer_modified = true; @@ -2473,7 +2473,7 @@ heap_multi_insert(Relation relation, TupleTableSlot **slots, int ntuples, { Assert(!(options & HEAP_INSERT_FROZEN)); /* It's possible the VM bits were already clear */ - if (visibilitymap_clear(relation->rd_locator, + if (visibilitymap_clear_rel(relation, BufferGetBlockNumber(buffer), vmbuffer, VISIBILITYMAP_VALID_BITS)) vmbuffer_modified = true; @@ -3047,7 +3047,7 @@ l1: if (clear_all_visible) { /* It's possible the VM bits were already clear */ - if (visibilitymap_clear(relation->rd_locator, BufferGetBlockNumber(buffer), + if (visibilitymap_clear_rel(relation, BufferGetBlockNumber(buffer), vmbuffer, VISIBILITYMAP_VALID_BITS)) vmbuffer_modified = true; @@ -3910,7 +3910,7 @@ l2: if (PageIsAllVisible(page)) { /* It's possible all-frozen was already clear */ - if (visibilitymap_clear(relation->rd_locator, block, vmbuffer, + if (visibilitymap_clear_rel(relation, block, vmbuffer, VISIBILITYMAP_ALL_FROZEN)) cleared_all_frozen = true; } @@ -4224,7 +4224,7 @@ l2: */ if (clear_all_visible) { - if (visibilitymap_clear(relation->rd_locator, block, + if (visibilitymap_clear_rel(relation, block, vmbuffer, VISIBILITYMAP_VALID_BITS)) { /* @@ -4248,7 +4248,7 @@ l2: * If both heap blocks' VM bits are on the same VM buffer, this will * clear the new heap block's VM bits from the shared vmbuffer. */ - if (visibilitymap_clear(relation->rd_locator, BufferGetBlockNumber(newbuf), + if (visibilitymap_clear_rel(relation, BufferGetBlockNumber(newbuf), vmbuffer_new, VISIBILITYMAP_VALID_BITS)) vmbuffer_new_modified = true; @@ -5360,7 +5360,7 @@ failed: /* Clear only the all-frozen bit on visibility map if needed */ if (PageIsAllVisible(page)) { - if (visibilitymap_clear(relation->rd_locator, block, vmbuffer, + if (visibilitymap_clear_rel(relation, block, vmbuffer, VISIBILITYMAP_ALL_FROZEN)) cleared_all_frozen = true; } @@ -6153,7 +6153,7 @@ l4: if (PageIsAllVisible(page)) { /* It's possible all-frozen was already clear */ - if (visibilitymap_clear(rel->rd_locator, block, vmbuffer, + if (visibilitymap_clear_rel(rel, block, vmbuffer, VISIBILITYMAP_ALL_FROZEN)) cleared_all_frozen = true; } diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index 50f810c8830..7232590ab3c 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -954,7 +954,7 @@ heap_page_fix_vm_corruption(PruneState *prstate, OffsetNumber offnum, { LockBuffer(prstate->vmbuffer, BUFFER_LOCK_EXCLUSIVE); /* This VM clear is not WAL-logged, so its return value is not needed. */ - (void) visibilitymap_clear(prstate->relation->rd_locator, + (void) visibilitymap_clear_rel(prstate->relation, prstate->block, prstate->vmbuffer, VISIBILITYMAP_VALID_BITS); LockBuffer(prstate->vmbuffer, BUFFER_LOCK_UNLOCK); diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c index fe5ce437e1b..d45578462b7 100644 --- a/src/backend/access/heap/visibilitymap.c +++ b/src/backend/access/heap/visibilitymap.c @@ -102,6 +102,7 @@ #include "access/xloginsert.h" #include "access/xlogutils.h" #include "miscadmin.h" +#include "pgstat.h" #include "port/pg_bitutils.h" #include "storage/bufmgr.h" #include "storage/smgr.h" @@ -136,6 +137,8 @@ #define FROZEN_MASK8 (0xaa) /* The upper bit of each bit pair */ /* prototypes for internal routines */ +static bool vm_do_clear(Relation rel, RelFileLocator rlocator, + BlockNumber heapBlk, Buffer vmbuf, uint8 flags); static Buffer vm_readbuf(Relation rel, BlockNumber blkno, bool extend); static Buffer vm_extend(Relation rel, BlockNumber vm_nblocks); @@ -147,10 +150,40 @@ static Buffer vm_extend(Relation rel, BlockNumber vm_nblocks); * * This function doesn't do any I/O. Returns true if any bits have been * cleared and false otherwise. + * + * This variant is for callers that only know the relation's file locator, + * such as recovery, and hence cannot count the clears in the relation's + * statistics; callers holding a Relation should use + * visibilitymap_clear_rel() instead. */ bool visibilitymap_clear(RelFileLocator rlocator, BlockNumber heapBlk, Buffer vmbuf, uint8 flags) +{ + return vm_do_clear(NULL, rlocator, heapBlk, vmbuf, flags); +} + +/* + * visibilitymap_clear_rel - visibilitymap_clear() for an open relation + * + * Same as visibilitymap_clear(), but additionally counts the cleared + * all-visible and all-frozen marks in the relation's statistics, which + * makes the stability of its visibility map observable. + */ +bool +visibilitymap_clear_rel(Relation rel, BlockNumber heapBlk, Buffer vmbuf, + uint8 flags) +{ + return vm_do_clear(rel, rel->rd_locator, heapBlk, vmbuf, flags); +} + +/* + * Workhorse of visibilitymap_clear() and visibilitymap_clear_rel(). 'rel' + * is NULL when the caller has no relcache entry at hand. + */ +static bool +vm_do_clear(Relation rel, RelFileLocator rlocator, BlockNumber heapBlk, + Buffer vmbuf, uint8 flags) { int mapByte = HEAPBLK_TO_MAPBYTE(heapBlk); int mapOffset = HEAPBLK_TO_OFFSET(heapBlk); @@ -180,6 +213,18 @@ visibilitymap_clear(RelFileLocator rlocator, BlockNumber heapBlk, if (map[mapByte] & mask) { + /* + * Track how often all-visible or all-frozen bits are cleared in the + * visibility map. + */ + if (rel != NULL) + { + if (map[mapByte] & ((flags & VISIBILITYMAP_ALL_VISIBLE) << mapOffset)) + pgstat_count_visible_page_marks_cleared(rel); + if (map[mapByte] & ((flags & VISIBILITYMAP_ALL_FROZEN) << mapOffset)) + pgstat_count_frozen_page_marks_cleared(rel); + } + map[mapByte] &= ~mask; MarkBufferDirty(vmbuf); diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 6130fdae3f8..046dec96f3a 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -750,7 +750,9 @@ CREATE VIEW pg_stat_all_tables AS pg_stat_get_total_vacuum_delay_time(C.oid) AS total_vacuum_delay_time, pg_stat_get_total_autovacuum_delay_time(C.oid) AS total_autovacuum_delay_time, pg_stat_get_vacuum_failsafe_count(C.oid) AS vacuum_failsafe_count, - pg_stat_get_stat_reset_time(C.oid) AS stats_reset + pg_stat_get_stat_reset_time(C.oid) AS stats_reset, + pg_stat_get_visible_page_marks_cleared(C.oid) AS visible_page_marks_cleared, + pg_stat_get_frozen_page_marks_cleared(C.oid) AS frozen_page_marks_cleared FROM pg_class C LEFT JOIN pg_index I ON C.oid = I.indrelid LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) diff --git a/src/backend/utils/activity/pgstat_relation.c b/src/backend/utils/activity/pgstat_relation.c index ea8776ad6f7..067adbde212 100644 --- a/src/backend/utils/activity/pgstat_relation.c +++ b/src/backend/utils/activity/pgstat_relation.c @@ -1016,6 +1016,8 @@ pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait) tabentry->blocks_fetched += lstats->tab.counts.blocks_fetched; tabentry->blocks_hit += lstats->tab.counts.blocks_hit; + tabentry->visible_page_marks_cleared += lstats->tab.counts.visible_page_marks_cleared; + tabentry->frozen_page_marks_cleared += lstats->tab.counts.frozen_page_marks_cleared; /* Clamp live_tuples in case of negative delta_live_tuples */ tabentry->live_tuples = Max(tabentry->live_tuples, 0); diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 1af522c4d8a..2fb57113b10 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -111,6 +111,12 @@ PG_STAT_GET_RELENTRY_INT64(vacuum_count) /* pg_stat_get_vacuum_failsafe_count */ PG_STAT_GET_RELENTRY_INT64(vacuum_failsafe_count) +/* pg_stat_get_visible_page_marks_cleared */ +PG_STAT_GET_RELENTRY_INT64(visible_page_marks_cleared) + +/* pg_stat_get_frozen_page_marks_cleared */ +PG_STAT_GET_RELENTRY_INT64(frozen_page_marks_cleared) + /* * Accessor macro for index stats entries (PgStat_StatIdxEntry). */ diff --git a/src/include/access/visibilitymap.h b/src/include/access/visibilitymap.h index 165efd1c00e..21e190f544d 100644 --- a/src/include/access/visibilitymap.h +++ b/src/include/access/visibilitymap.h @@ -26,6 +26,8 @@ #define VM_ALL_FROZEN(r, b, v) \ ((visibilitymap_get_status((r), (b), (v)) & VISIBILITYMAP_ALL_FROZEN) != 0) +extern bool visibilitymap_clear_rel(Relation rel, BlockNumber heapBlk, + Buffer vmbuf, uint8 flags); extern bool visibilitymap_clear(RelFileLocator rlocator, BlockNumber heapBlk, Buffer vmbuf, uint8 flags); extern void visibilitymap_pin(Relation rel, BlockNumber heapBlk, diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index a482ed97a43..c2eaceda14e 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -12836,4 +12836,14 @@ proname => 'hashoid8extended', prorettype => 'int8', proargtypes => 'oid8 int8', prosrc => 'hashoid8extended' }, +{ oid => '8002', + descr => 'statistics: number of times the all-visible marks in the visibility map were cleared for pages of this table', + proname => 'pg_stat_get_visible_page_marks_cleared', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_visible_page_marks_cleared' }, +{ oid => '8003', + descr => 'statistics: number of times the all-frozen marks in the visibility map were cleared for pages of this table', + proname => 'pg_stat_get_frozen_page_marks_cleared', provolatile => 's', + proparallel => 'r', prorettype => 'int8', proargtypes => 'oid', + prosrc => 'pg_stat_get_frozen_page_marks_cleared' }, ] diff --git a/src/include/pgstat.h b/src/include/pgstat.h index c03a5caa492..221856d18c3 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -196,9 +196,17 @@ typedef struct PgStat_TableCounts PgStat_Counter blocks_fetched; PgStat_Counter blocks_hit; + + /* + * Revocations of all-visible and all-frozen marks in the visibility map. + * The bits are cleared by the page modification itself, so these count + * whether or not the transaction commits. + */ + PgStat_Counter visible_page_marks_cleared; + PgStat_Counter frozen_page_marks_cleared; } PgStat_TableCounts; -StaticAssertDecl(sizeof(PgStat_TableCounts) == 5 * sizeof(PgStat_Counter), +StaticAssertDecl(sizeof(PgStat_TableCounts) == 7 * sizeof(PgStat_Counter), "PgStat_TableCounts has no padding"); /* ---------- @@ -596,6 +604,8 @@ typedef struct PgStat_StatTabEntry PgStat_Counter blocks_fetched; PgStat_Counter blocks_hit; + PgStat_Counter visible_page_marks_cleared; + PgStat_Counter frozen_page_marks_cleared; TimestampTz last_vacuum_time; /* user initiated vacuum */ PgStat_Counter vacuum_count; @@ -980,6 +990,27 @@ extern void pgstat_report_analyze(Relation rel, (rel)->pgstat_info->tab.counts.blocks_hit++; \ } \ } while (0) +/* + * Count revocations of all-visible and all-frozen marks in the visibility + * map. Only tables have a visibility map, so these always work on the + * relation part of the pending entry. + */ +#define pgstat_count_visible_page_marks_cleared(rel) \ + do { \ + if (pgstat_should_count_relation(rel)) \ + { \ + Assert((rel)->pgstat_info->kind == PGSTAT_KIND_RELATION); \ + (rel)->pgstat_info->tab.counts.visible_page_marks_cleared++; \ + } \ + } while (0) +#define pgstat_count_frozen_page_marks_cleared(rel) \ + do { \ + if (pgstat_should_count_relation(rel)) \ + { \ + Assert((rel)->pgstat_info->kind == PGSTAT_KIND_RELATION); \ + (rel)->pgstat_info->tab.counts.frozen_page_marks_cleared++; \ + } \ + } while (0) extern void pgstat_count_heap_insert(Relation rel, PgStat_Counter n); extern void pgstat_count_heap_update(Relation rel, bool hot, bool newpage); diff --git a/src/test/isolation/expected/vacuum-extending-freeze.out b/src/test/isolation/expected/vacuum-extending-freeze.out new file mode 100644 index 00000000000..994a8df56df --- /dev/null +++ b/src/test/isolation/expected/vacuum-extending-freeze.out @@ -0,0 +1,185 @@ +Parsed test spec with 2 sessions + +starting permutation: s2_vacuum_freeze s1_get_set_vm_flags_stats s1_update_table s1_get_cleared_vm_flags_stats s2_vacuum_freeze s1_get_set_vm_flags_stats s2_vacuum_freeze s1_select_from_index s2_delete_from_table s1_get_cleared_vm_flags_stats s2_vacuum_freeze s1_get_set_vm_flags_stats s1_commit s1_get_cleared_vm_flags_stats +pg_stat_force_next_flush +------------------------ + +(1 row) + +step s2_vacuum_freeze: + VACUUM FREEZE vestat; + +step s1_get_set_vm_flags_stats: + SELECT pg_stat_force_next_flush(); + + SELECT c.relallfrozen > frozen_flag_count as relallfrozen, c.relallvisible > all_visibile_flag_count as relallvisible + FROM pg_class c, stats_state + WHERE c.relname = 'vestat'; + + UPDATE stats_state + SET frozen_flag_count = c.relallfrozen, + all_visibile_flag_count = c.relallvisible + FROM pg_class c + WHERE c.relname = 'vestat'; + +pg_stat_force_next_flush +------------------------ + +(1 row) + +relallfrozen|relallvisible +------------+------------- +t |t +(1 row) + +step s1_update_table: + UPDATE vestat SET x = x + 1001 where x >= 2500; + SELECT pg_stat_force_next_flush(); + +pg_stat_force_next_flush +------------------------ + +(1 row) + +step s1_get_cleared_vm_flags_stats: + SELECT pg_stat_force_next_flush(); + + SELECT v.visible_page_marks_cleared > cleared_all_visibile_flag_count as visible_page_marks_cleared, + v.frozen_page_marks_cleared > cleared_frozen_flag_count as frozen_page_marks_cleared + FROM pg_stat_all_tables v, stats_state + WHERE v.relname = 'vestat'; + + UPDATE stats_state + SET cleared_all_visibile_flag_count = v.visible_page_marks_cleared, + cleared_frozen_flag_count = v.frozen_page_marks_cleared + FROM pg_stat_all_tables v + WHERE v.relname = 'vestat'; + +pg_stat_force_next_flush +------------------------ + +(1 row) + +visible_page_marks_cleared|frozen_page_marks_cleared +--------------------------+------------------------- +t |t +(1 row) + +step s2_vacuum_freeze: + VACUUM FREEZE vestat; + +step s1_get_set_vm_flags_stats: + SELECT pg_stat_force_next_flush(); + + SELECT c.relallfrozen > frozen_flag_count as relallfrozen, c.relallvisible > all_visibile_flag_count as relallvisible + FROM pg_class c, stats_state + WHERE c.relname = 'vestat'; + + UPDATE stats_state + SET frozen_flag_count = c.relallfrozen, + all_visibile_flag_count = c.relallvisible + FROM pg_class c + WHERE c.relname = 'vestat'; + +pg_stat_force_next_flush +------------------------ + +(1 row) + +relallfrozen|relallvisible +------------+------------- +t |t +(1 row) + +step s2_vacuum_freeze: + VACUUM FREEZE vestat; + +step s1_select_from_index: + BEGIN; + SELECT count(x) FROM vestat WHERE x > 2000; + +count +----- + 3000 +(1 row) + +step s2_delete_from_table: + DELETE FROM vestat WHERE x > 4930; + +step s1_get_cleared_vm_flags_stats: + SELECT pg_stat_force_next_flush(); + + SELECT v.visible_page_marks_cleared > cleared_all_visibile_flag_count as visible_page_marks_cleared, + v.frozen_page_marks_cleared > cleared_frozen_flag_count as frozen_page_marks_cleared + FROM pg_stat_all_tables v, stats_state + WHERE v.relname = 'vestat'; + + UPDATE stats_state + SET cleared_all_visibile_flag_count = v.visible_page_marks_cleared, + cleared_frozen_flag_count = v.frozen_page_marks_cleared + FROM pg_stat_all_tables v + WHERE v.relname = 'vestat'; + +pg_stat_force_next_flush +------------------------ + +(1 row) + +visible_page_marks_cleared|frozen_page_marks_cleared +--------------------------+------------------------- +f |f +(1 row) + +step s2_vacuum_freeze: + VACUUM FREEZE vestat; + +step s1_get_set_vm_flags_stats: + SELECT pg_stat_force_next_flush(); + + SELECT c.relallfrozen > frozen_flag_count as relallfrozen, c.relallvisible > all_visibile_flag_count as relallvisible + FROM pg_class c, stats_state + WHERE c.relname = 'vestat'; + + UPDATE stats_state + SET frozen_flag_count = c.relallfrozen, + all_visibile_flag_count = c.relallvisible + FROM pg_class c + WHERE c.relname = 'vestat'; + +pg_stat_force_next_flush +------------------------ + +(1 row) + +relallfrozen|relallvisible +------------+------------- +f |f +(1 row) + +step s1_commit: + COMMIT; + +step s1_get_cleared_vm_flags_stats: + SELECT pg_stat_force_next_flush(); + + SELECT v.visible_page_marks_cleared > cleared_all_visibile_flag_count as visible_page_marks_cleared, + v.frozen_page_marks_cleared > cleared_frozen_flag_count as frozen_page_marks_cleared + FROM pg_stat_all_tables v, stats_state + WHERE v.relname = 'vestat'; + + UPDATE stats_state + SET cleared_all_visibile_flag_count = v.visible_page_marks_cleared, + cleared_frozen_flag_count = v.frozen_page_marks_cleared + FROM pg_stat_all_tables v + WHERE v.relname = 'vestat'; + +pg_stat_force_next_flush +------------------------ + +(1 row) + +visible_page_marks_cleared|frozen_page_marks_cleared +--------------------------+------------------------- +t |t +(1 row) + diff --git a/src/test/isolation/isolation_schedule b/src/test/isolation/isolation_schedule index fc45d504d2b..dfb0573f782 100644 --- a/src/test/isolation/isolation_schedule +++ b/src/test/isolation/isolation_schedule @@ -131,3 +131,4 @@ test: ddl-dependency-locking test: tablespace-dependency-locking test: pub-concurrent-drop test: drop-owned-grant +test: vacuum-extending-freeze diff --git a/src/test/isolation/specs/vacuum-extending-freeze.spec b/src/test/isolation/specs/vacuum-extending-freeze.spec new file mode 100644 index 00000000000..17c204e2326 --- /dev/null +++ b/src/test/isolation/specs/vacuum-extending-freeze.spec @@ -0,0 +1,117 @@ +# In short, this test validates the correctness and stability of cumulative +# vacuum statistics accounting around freezing, visibility, and revision +# tracking across VACUUM and backend operations. +# In addition, the test provides a scenario where one process holds a +# transaction open while another process deletes tuples. We expect that +# a backend clears the all-frozen and all-visible flags, which were set +# by VACUUM earlier, only after the committing transaction makes the +# deletions visible. + +setup +{ + CREATE TABLE vestat (x int, y int) + WITH (autovacuum_enabled = off, fillfactor = 70); + + INSERT INTO vestat + SELECT i, i FROM generate_series(1, 5000) AS g(i); + + CREATE INDEX vestat_idx ON vestat (x); + + CREATE TABLE stats_state (frozen_flag_count int, all_visibile_flag_count int, + cleared_frozen_flag_count int, cleared_all_visibile_flag_count int); + INSERT INTO stats_state VALUES (0,0,0,0); + ANALYZE vestat; + + -- Ensure stats are flushed before starting the scenario + SELECT pg_stat_force_next_flush(); +} + +teardown +{ + DROP TABLE IF EXISTS vestat; + RESET vacuum_freeze_min_age; + RESET vacuum_freeze_table_age; + +} + +session s1 + +step s1_get_set_vm_flags_stats +{ + SELECT pg_stat_force_next_flush(); + + SELECT c.relallfrozen > frozen_flag_count as relallfrozen, c.relallvisible > all_visibile_flag_count as relallvisible + FROM pg_class c, stats_state + WHERE c.relname = 'vestat'; + + UPDATE stats_state + SET frozen_flag_count = c.relallfrozen, + all_visibile_flag_count = c.relallvisible + FROM pg_class c + WHERE c.relname = 'vestat'; +} + +step s1_get_cleared_vm_flags_stats +{ + SELECT pg_stat_force_next_flush(); + + SELECT v.visible_page_marks_cleared > cleared_all_visibile_flag_count as visible_page_marks_cleared, + v.frozen_page_marks_cleared > cleared_frozen_flag_count as frozen_page_marks_cleared + FROM pg_stat_all_tables v, stats_state + WHERE v.relname = 'vestat'; + + UPDATE stats_state + SET cleared_all_visibile_flag_count = v.visible_page_marks_cleared, + cleared_frozen_flag_count = v.frozen_page_marks_cleared + FROM pg_stat_all_tables v + WHERE v.relname = 'vestat'; +} + +step s1_select_from_index +{ + BEGIN; + SELECT count(x) FROM vestat WHERE x > 2000; +} + +step s1_commit +{ + COMMIT; +} + +session s2 +setup +{ + -- Configure aggressive freezing vacuum behavior + SET vacuum_freeze_min_age = 0; + SET vacuum_freeze_table_age = 0; +} +step s2_delete_from_table +{ + DELETE FROM vestat WHERE x > 4930; +} +step s2_vacuum_freeze +{ + VACUUM FREEZE vestat; +} + +step s1_update_table +{ + UPDATE vestat SET x = x + 1001 where x >= 2500; + SELECT pg_stat_force_next_flush(); +} + +permutation + s2_vacuum_freeze + s1_get_set_vm_flags_stats + s1_update_table + s1_get_cleared_vm_flags_stats + s2_vacuum_freeze + s1_get_set_vm_flags_stats + s2_vacuum_freeze + s1_select_from_index + s2_delete_from_table + s1_get_cleared_vm_flags_stats + s2_vacuum_freeze + s1_get_set_vm_flags_stats + s1_commit + s1_get_cleared_vm_flags_stats \ No newline at end of file diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 54ded539bba..a7cf533085e 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1853,7 +1853,9 @@ pg_stat_all_tables| SELECT c.oid AS relid, pg_stat_get_total_vacuum_delay_time(c.oid) AS total_vacuum_delay_time, pg_stat_get_total_autovacuum_delay_time(c.oid) AS total_autovacuum_delay_time, pg_stat_get_vacuum_failsafe_count(c.oid) AS vacuum_failsafe_count, - pg_stat_get_stat_reset_time(c.oid) AS stats_reset + pg_stat_get_stat_reset_time(c.oid) AS stats_reset, + pg_stat_get_visible_page_marks_cleared(c.oid) AS visible_page_marks_cleared, + pg_stat_get_frozen_page_marks_cleared(c.oid) AS frozen_page_marks_cleared FROM ((pg_class c LEFT JOIN pg_index i ON ((c.oid = i.indrelid))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) @@ -2389,7 +2391,9 @@ pg_stat_sys_tables| SELECT relid, total_vacuum_delay_time, total_autovacuum_delay_time, vacuum_failsafe_count, - stats_reset + stats_reset, + visible_page_marks_cleared, + frozen_page_marks_cleared FROM pg_stat_all_tables WHERE ((schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (schemaname ~ '^pg_toast'::text)); pg_stat_user_functions| SELECT p.oid AS funcid, @@ -2451,7 +2455,9 @@ pg_stat_user_tables| SELECT relid, total_vacuum_delay_time, total_autovacuum_delay_time, vacuum_failsafe_count, - stats_reset + stats_reset, + visible_page_marks_cleared, + frozen_page_marks_cleared FROM pg_stat_all_tables WHERE ((schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (schemaname !~ '^pg_toast'::text)); pg_stat_wal| SELECT wal_records, -- 2.50.1 (Apple Git-155)