eliminate xl_heap_visible to reduce WAL (and eventually set VM on-access)
Hi,
The attached patch set eliminates xl_heap_visible, the WAL record
emitted when a block of the heap is set all-visible/frozen in the
visibility map. Instead, it includes the information needed to update
the VM in the WAL record already emitted by the operation modifying
the heap page.
Currently COPY FREEZE and vacuum are the only operations that set the
VM. So, this patch modifies the xl_heap_multi_insert and xl_heap_prune
records.
The result is a dramatic reduction in WAL volume for these operations.
I've included numbers below.
I also think that it makes more sense to include changes to the VM in
the same WAL record as the changes that rendered the page all-visible.
In some cases, we will only set the page all-visible, but that is in
the context of the operation on the heap page which discovered that it
was all-visible. Therefore, I find this to be a clarity as well as a
performance improvement.
This project is also the first step toward setting the VM on-access
for queries which do not modify the page. There are a few design
issues that must be sorted out for that project which I will detail
separately. Note that this patch set currently does not implement
setting the VM on-access.
The attached patch set isn't 100% polished. I think some of the
variable names and comments could use work, but I'd like to validate
the idea of doing this before doing a full polish. This is a summary
of what is in the set:
Patches:
0001 - 0002: cleanup
0003 - 0004: refactoring
0005: COPY FREEZE changes
0006: refactoring
0007: vacuum phase III changes
0008: vacuum phase I empty page changes
0009 - 0012: refactoring
0013: vacuum phase I normal page changes
0014: cleanup
Performance benefits of eliminating xl_heap_visible:
vacuum of table with index (DDL at bottom of email)
--
master -> patch
WAL bytes: 405346 -> 303088 = 25% reduction
WAL records: 6682 -> 4459 = 33% reduction
vacuum of table without index
--
master -> patch
WAL records: 4452 -> 2231 = 50% reduction
WAL bytes: 289016 -> 177978 = 38% reduction
COPY FREEZE of table without index
--
master -> patch
WAL records: 3672777 -> 1854589 = 50% reduction
WAL bytes: 841340339 -> 748545732 = 11% reduction (new pages need a
copy of the whole page)
table for vacuum example:
--
create table foo(a int, b numeric, c numeric) with (autovacuum_enabled= false);
insert into foo select i % 18, repeat('1', 400)::numeric, repeat('2',
400)::numeric from generate_series(1,40000)i;
-- don't make index for no-index case
create index on foo(a);
delete from foo where a = 1;
vacuum (verbose, process_toast false) foo;
copy freeze example:
--
-- create a data file
create table large(a int, b int) with (autovacuum_enabled = false,
fillfactor = 10);
insert into large SELECT generate_series(1,40000000)i, 1;
copy large to 'large.data';
-- example
BEGIN;
create table large(a int, b int) with (autovacuum_enabled = false,
fillfactor = 10);
COPY large FROM 'large.data' WITH (FREEZE);
COMMIT;
- Melanie
Attachments:
v1-0011-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchtext/x-patch; charset=US-ASCII; name=v1-0011-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchDownload+96-74
v1-0013-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchtext/x-patch; charset=US-ASCII; name=v1-0013-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchDownload+223-207
v1-0002-Simplify-vacuum-VM-update-logging-counters.patchtext/x-patch; charset=US-ASCII; name=v1-0002-Simplify-vacuum-VM-update-logging-counters.patchDownload+12-21
v1-0004-Introduce-unlogged-versions-of-VM-functions.patchtext/x-patch; charset=US-ASCII; name=v1-0004-Introduce-unlogged-versions-of-VM-functions.patchDownload+94-1
v1-0001-Remove-unused-check-in-heap_xlog_insert.patchtext/x-patch; charset=US-ASCII; name=v1-0001-Remove-unused-check-in-heap_xlog_insert.patchDownload+2-4
v1-0003-Introduce-heap-specific-wrapper-for-visibilitymap.patchtext/x-patch; charset=US-ASCII; name=v1-0003-Introduce-heap-specific-wrapper-for-visibilitymap.patchDownload+73-76
v1-0005-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchtext/x-patch; charset=US-ASCII; name=v1-0005-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchDownload+69-16
v1-0009-Combine-lazy_scan_prune-VM-corruption-cases.patchtext/x-patch; charset=US-ASCII; name=v1-0009-Combine-lazy_scan_prune-VM-corruption-cases.patchDownload+74-42
v1-0006-Make-heap_page_is_all_visible-independent-of-LVRe.patchtext/x-patch; charset=US-ASCII; name=v1-0006-Make-heap_page_is_all_visible-independent-of-LVRe.patchDownload+29-17
v1-0007-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchtext/x-patch; charset=US-ASCII; name=v1-0007-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchDownload+308-78
v1-0008-Use-xl_heap_prune-record-for-setting-empty-pages-.patchtext/x-patch; charset=US-ASCII; name=v1-0008-Use-xl_heap_prune-record-for-setting-empty-pages-.patchDownload+54-26
v1-0010-Combine-vacuum-phase-I-VM-update-cases.patchtext/x-patch; charset=US-ASCII; name=v1-0010-Combine-vacuum-phase-I-VM-update-cases.patchDownload+22-50
v1-0012-Update-VM-in-pruneheap.c.patchtext/x-patch; charset=US-ASCII; name=v1-0012-Update-VM-in-pruneheap.c.patchDownload+106-90
v1-0014-Remove-xl_heap_visible-entirely.patchtext/x-patch; charset=US-ASCII; name=v1-0014-Remove-xl_heap_visible-entirely.patchDownload+12-355
On Mon, Jun 23, 2025 at 4:25 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:
The attached patch set eliminates xl_heap_visible, the WAL record
emitted when a block of the heap is set all-visible/frozen in the
visibility map. Instead, it includes the information needed to update
the VM in the WAL record already emitted by the operation modifying
the heap page.
Rebased in light of recent changes on master:
0001: cleanup
0002: preparatory work
0003: eliminate xl_heap_visible for COPY FREEZE
0004 - 0005: eliminate xl_heap_visible for vacuum's phase III
0006: eliminate xl_heap_visible for vacuum phase I empty pages
0007 - 0010: preparatory refactoring
0011: eliminate xl_heap_visible from vacuum phase I prune/freeze
0012: remove xl_heap_visible
- Melanie
Attachments:
v2-0002-Introduce-unlogged-versions-of-VM-functions.patchtext/x-patch; charset=US-ASCII; name=v2-0002-Introduce-unlogged-versions-of-VM-functions.patchDownload+94-1
v2-0001-Introduce-heap-specific-wrapper-for-visibilitymap.patchtext/x-patch; charset=US-ASCII; name=v2-0001-Introduce-heap-specific-wrapper-for-visibilitymap.patchDownload+117-107
v2-0004-Make-heap_page_is_all_visible-independent-of-LVRe.patchtext/x-patch; charset=US-ASCII; name=v2-0004-Make-heap_page_is_all_visible-independent-of-LVRe.patchDownload+29-17
v2-0005-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchtext/x-patch; charset=US-ASCII; name=v2-0005-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchDownload+301-67
v2-0003-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchtext/x-patch; charset=US-ASCII; name=v2-0003-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchDownload+65-16
v2-0008-Combine-vacuum-phase-I-VM-update-cases.patchtext/x-patch; charset=US-ASCII; name=v2-0008-Combine-vacuum-phase-I-VM-update-cases.patchDownload+22-50
v2-0007-Combine-lazy_scan_prune-VM-corruption-cases.patchtext/x-patch; charset=US-ASCII; name=v2-0007-Combine-lazy_scan_prune-VM-corruption-cases.patchDownload+74-42
v2-0009-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchtext/x-patch; charset=US-ASCII; name=v2-0009-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchDownload+96-74
v2-0010-Update-VM-in-pruneheap.c.patchtext/x-patch; charset=US-ASCII; name=v2-0010-Update-VM-in-pruneheap.c.patchDownload+106-90
v2-0006-Use-xl_heap_prune-record-for-setting-empty-pages-.patchtext/x-patch; charset=US-ASCII; name=v2-0006-Use-xl_heap_prune-record-for-setting-empty-pages-.patchDownload+48-22
v2-0011-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchtext/x-patch; charset=US-ASCII; name=v2-0011-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchDownload+223-207
v2-0012-Remove-xl_heap_visible-entirely.patchtext/x-patch; charset=US-ASCII; name=v2-0012-Remove-xl_heap_visible-entirely.patchDownload+14-371
On Thu, Jun 26, 2025 at 6:04 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:
Rebased in light of recent changes on master:
This needed another rebase, and, in light of the discussion in [1]/messages/by-id/CAAKRu_Yj=yrL+gGGsqfYVQcYn7rDp6hDeoF1vN453JDp8dEY+w@mail.gmail.com,
I've also removed the patch to add heap wrappers for setting pages
all-visible.
More notably, the final patch (0012) in attached v3 allows on-access
pruning to set the VM.
To do this, it plumbs some information down from the executor to the
table scan about whether or not the table is modified by the query. We
don't want to set the VM only to clear it while scanning pages for an
UPDATE or while locking rows in a SELECT FOR UPDATE.
Because we only do on-access pruning when pd_prune_xid is valid, we
shouldn't need much of a heuristic for deciding when to set the VM
on-access -- but I've included one anyway: we only do it if we are
actually pruning or if the page is already dirty and no FPI would be
emitted.
You can see it in action with the following:
create extension pg_visibility;
create table foo (a int, b int) with (autovacuum_enabled=false, fillfactor=90);
insert into foo select generate_series(1,300), generate_series(1,300);
create index on foo (a);
update foo set b = 51 where b = 50;
select * from foo where a = 50;
select * from pg_visibility_map_summary('foo');
The SELECT will set a page all-visible in the VM.
In this patch set, on-access pruning is enabled for sequential scans
and the underlying heap relation in index scans and bitmap heap scans.
This example can exercise any of the three if you toggle
enable_indexscan and enable_bitmapscan appropriately.
From a performance perspective, If you run a trivial pgbench, you can
see far more all-visible pages set in the pgbench_[x] relations with
no noticeable overhead. But, I'm planning to do some performance
experiments to show how this affects our ability to choose index only
scan plans in realistic workloads.
- Melanie
[1]: /messages/by-id/CAAKRu_Yj=yrL+gGGsqfYVQcYn7rDp6hDeoF1vN453JDp8dEY+w@mail.gmail.com
Attachments:
v3-0001-Add-assert-to-heap_prune_record_unchanged_lp_norm.patchtext/x-patch; charset=US-ASCII; name=v3-0001-Add-assert-to-heap_prune_record_unchanged_lp_norm.patchDownload+1-1
v3-0005-Use-xl_heap_prune-record-for-setting-empty-pages-.patchtext/x-patch; charset=US-ASCII; name=v3-0005-Use-xl_heap_prune-record-for-setting-empty-pages-.patchDownload+47-24
v3-0002-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchtext/x-patch; charset=US-ASCII; name=v3-0002-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchDownload+130-23
v3-0003-Make-heap_page_is_all_visible-independent-of-LVRe.patchtext/x-patch; charset=US-ASCII; name=v3-0003-Make-heap_page_is_all_visible-independent-of-LVRe.patchDownload+29-17
v3-0004-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchtext/x-patch; charset=US-ASCII; name=v3-0004-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchDownload+296-69
v3-0007-Combine-vacuum-phase-I-VM-update-cases.patchtext/x-patch; charset=US-ASCII; name=v3-0007-Combine-vacuum-phase-I-VM-update-cases.patchDownload+31-70
v3-0010-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchtext/x-patch; charset=US-ASCII; name=v3-0010-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchDownload+237-211
v3-0006-Combine-lazy_scan_prune-VM-corruption-cases.patchtext/x-patch; charset=US-ASCII; name=v3-0006-Combine-lazy_scan_prune-VM-corruption-cases.patchDownload+73-42
v3-0008-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchtext/x-patch; charset=US-ASCII; name=v3-0008-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchDownload+96-73
v3-0009-Update-VM-in-pruneheap.c.patchtext/x-patch; charset=US-ASCII; name=v3-0009-Update-VM-in-pruneheap.c.patchDownload+106-107
v3-0011-Remove-xl_heap_visible-entirely.patchtext/x-patch; charset=US-ASCII; name=v3-0011-Remove-xl_heap_visible-entirely.patchDownload+23-329
v3-0012-Allow-on-access-pruning-to-set-pages-all-visible.patchtext/x-patch; charset=US-ASCII; name=v3-0012-Allow-on-access-pruning-to-set-pages-all-visible.patchDownload+285-39
On Wed, Jul 9, 2025 at 5:59 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:
On Thu, Jun 26, 2025 at 6:04 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:Rebased in light of recent changes on master:
This needed another rebase, and, in light of the discussion in [1],
I've also removed the patch to add heap wrappers for setting pages
all-visible.
Andrey Borodin made the excellent point off-list that I forgot to
remove the xl_heap_visible struct itself -- which is rather important
to a patch set purporting to eliminate xl_heap_visible! New version
attached.
- Melanie
Attachments:
v4-0002-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchapplication/x-patch; name=v4-0002-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchDownload+130-23
v4-0003-Make-heap_page_is_all_visible-independent-of-LVRe.patchapplication/x-patch; name=v4-0003-Make-heap_page_is_all_visible-independent-of-LVRe.patchDownload+29-17
v4-0005-Use-xl_heap_prune-record-for-setting-empty-pages-.patchapplication/x-patch; name=v4-0005-Use-xl_heap_prune-record-for-setting-empty-pages-.patchDownload+47-24
v4-0001-Add-assert-to-heap_prune_record_unchanged_lp_norm.patchapplication/x-patch; name=v4-0001-Add-assert-to-heap_prune_record_unchanged_lp_norm.patchDownload+1-1
v4-0004-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchapplication/x-patch; name=v4-0004-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchDownload+296-69
v4-0010-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchapplication/x-patch; name=v4-0010-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchDownload+237-211
v4-0009-Update-VM-in-pruneheap.c.patchapplication/x-patch; name=v4-0009-Update-VM-in-pruneheap.c.patchDownload+106-107
v4-0006-Combine-lazy_scan_prune-VM-corruption-cases.patchapplication/x-patch; name=v4-0006-Combine-lazy_scan_prune-VM-corruption-cases.patchDownload+73-42
v4-0008-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchapplication/x-patch; name=v4-0008-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchDownload+96-73
v4-0007-Combine-vacuum-phase-I-VM-update-cases.patchapplication/x-patch; name=v4-0007-Combine-vacuum-phase-I-VM-update-cases.patchDownload+31-70
v4-0011-Remove-xl_heap_visible-entirely.patchapplication/x-patch; name=v4-0011-Remove-xl_heap_visible-entirely.patchDownload+23-358
v4-0012-Allow-on-access-pruning-to-set-pages-all-visible.patchapplication/x-patch; name=v4-0012-Allow-on-access-pruning-to-set-pages-all-visible.patchDownload+285-39
On 12 Jul 2025, at 03:19, Melanie Plageman <melanieplageman@gmail.com> wrote:
remove the xl_heap_visible struct
Same goes for VISIBILITYMAP_XLOG_CATALOG_REL and XLOG_HEAP2_VISIBLE. But please do not rush to remove it, perhaps I will have a more exhaustive list later. Currently the patch set is expected to be unpolished.
I just need to absorb all effects to have a high-level evaluation of the patch set effect.
I'm still trying to grasp connection of first patch with Assert(prstate->cutoffs) to other patches;
Also, I'd prefer "page is not marked all-visible but visibility map bit is set in relation" to emit XX001 for monitoring reasons, but again, this is small note, while I need a broader picture.
So far I do not see any general problems in delegating redo work from xl_heap_visible to other record. FWIW I observed several cases of VM corruptions that might be connected to the fact that we log VM changes independently of data changes that caused VM to change. But I have no real evidence or understanding what happened.
Best regards, Andrey Borodin.
On Sun, Jul 13, 2025 at 2:34 PM Andrey Borodin <x4mmm@yandex-team.ru> wrote:
On 12 Jul 2025, at 03:19, Melanie Plageman <melanieplageman@gmail.com> wrote:
remove the xl_heap_visible struct
Same goes for VISIBILITYMAP_XLOG_CATALOG_REL and XLOG_HEAP2_VISIBLE. But please do not rush to remove it, perhaps I will have a more exhaustive list later. Currently the patch set is expected to be unpolished.
I just need to absorb all effects to have a high-level evaluation of the patch set effect.
I actually did remove those if you check the last version posted. I
did notice there is one remaining comment referring to
XLOG_HEAP2_VISIBLE I missed somehow, but the actual enums/macros were
removed already.
I'm still trying to grasp connection of first patch with Assert(prstate->cutoffs) to other patches;
I added this because I noticed that it was used without validating it
was provided in that location. The last patch in the set which sets
the VM on access changes where cutoffs are used, so I noticed what I
felt was a missing assert in master while developing that page.
Also, I'd prefer "page is not marked all-visible but visibility map bit is set in relation" to emit XX001 for monitoring reasons, but again, this is small note, while I need a broader picture.
Could you clarify what you mean by this? Are you talking about the
string representation of the visibility map bits in the WAL record
representations in heapdesc.c?
- Melanie
On 14 Jul 2025, at 00:15, Melanie Plageman <melanieplageman@gmail.com> wrote:
Also, I'd prefer "page is not marked all-visible but visibility map bit is set in relation" to emit XX001 for monitoring reasons, but again, this is small note, while I need a broader picture.
Could you clarify what you mean by this? Are you talking about the
string representation of the visibility map bits in the WAL record
representations in heapdesc.c?
This might be a bit off-topic for this thread, but as long as the patch touches that code we can look into this too.
If VM bit all-visible is set while page is not all-visible IndexOnlyScan will show incorrect results. I observed this inconsistency few times on production.
Two persistent subsystems (VM and heap) contradict each other, that's why I think this is a data corruption. Yes, we can repair the VM by assuming heap to be the source of truth in this case. But we must also emit ERRCODE_DATA_CORRUPTED XX001 code into the logs. In many cases this will alert on-call SRE.
To do so I propose to replace elog(WARNING,...) with ereport(WARNING,(errcode(ERRCODE_DATA_CORRUPTED),..).
Best regards, Andrey Borodin.
Thanks for continuing to take a look, Andrey.
On Mon, Jul 14, 2025 at 2:37 AM Andrey Borodin <x4mmm@yandex-team.ru> wrote:
This might be a bit off-topic for this thread, but as long as the patch touches that code we can look into this too.
If VM bit all-visible is set while page is not all-visible IndexOnlyScan will show incorrect results. I observed this inconsistency few times on production.
That's very unfortunate. I wonder what could be causing this. Do you
suspect a bug in Postgres? Or something wrong with the disk, etc?
Two persistent subsystems (VM and heap) contradict each other, that's why I think this is a data corruption. Yes, we can repair the VM by assuming heap to be the source of truth in this case. But we must also emit ERRCODE_DATA_CORRUPTED XX001 code into the logs. In many cases this will alert on-call SRE.
To do so I propose to replace elog(WARNING,...) with ereport(WARNING,(errcode(ERRCODE_DATA_CORRUPTED),..).
Ah, you mean the warnings currently in lazy_scan_prune(). To me this
suggestion makes sense. I see at least one other example with
ERRCODE_DATA_CORRUPTED that is an error level below ERROR.
I have attached a cleaned up and updated version of the patch set (it
doesn't yet include your suggested error message change).
What's new in this version
-----
In addition to general code, comment, and commit message improvements,
notable changes are as follows:
- I have used the GlobalVisState for determining if the whole page is
visible in a more natural way.
- I micro-benchmarked and identified some sources of regression in the
additional code SELECT queries would do to set the VM. So, there are
several new commits addressing these (for example inlining several
functions and unsetting all-visible when we see a dead tuple if we
won't attempt freezing).
- Because heap_page_prune_and_freeze() was getting long, I added some
helper functions.
Performance impact of setting the VM on-access
-------
I found that with the patch set applied, we set many pages all-visible
in the VM on access, resulting in a higher overall number of pages set
all-visible, reducing load for vacuum, and dramatically decreasing
heap fetches by index-only scans.
I devised a simple benchmark -- with 8 workers inserting 20 rows at a
time into a table with a few columns and updating a single row that
they just inserted. Another worker queries the table 1x second using
an index.
After running the benchmark for a few minutes, though the table was
autovacuumed several times in both cases, with the patchset applied,
15% more blocks were all-visible at the end of the benchmark.
And with my patch applied, index-only scans did far fewer heap
fetches. A SELECT count(*) of the table at the same point in the
benchmark did 10,000 heap fetches on master and 500 with the patch
applied (I used auto_explain to determine this).
With my patch applied, autovacuum workers write half as much WAL as on
master. Some of this is courtesy of other patches in the set which
eliminate separate WAL records for setting the page all-visible. But,
vacuum is also scanning fewer pages and dirtying fewer buffers because
they are being set all-visible on-access.
There are more details about the benchmark at the end of the email.
Setting pd_prune_xid on insert
------
The patch "Set-pd_prune_xid-on-insert.txt" can be applied as the last
patch in the set. It sets pd_prune_xid on insert (so pages filled by
COPY or insert can also be set all-visible in the VM before they are
vacuumed). I gave it a .txt extension because it currently fails
035_standby_logical_decoding due to a recovery conflict. I need to
investigate more to see if this is a bug in my patch set or elsewhere
in Postgres.
Besides the failing test, I have a feeling that my current heuristic
for whether or not to set the VM on-access is not quite right for
pages that have only been inserted to -- and if we get it wrong, we've
wasted those CPU cycles because we didn't otherwise need to prune the
page.
- Melanie
Benchmark
-------
psql -c "
DROP TABLE IF EXISTS simple_table;
CREATE TABLE simple_table (
id SERIAL PRIMARY KEY,
group_id INT NOT NULL,
data TEXT,
created_at TIMESTAMPTZ DEFAULT now()
);
create index on simple_table(group_id);
"
pgbench \
--no-vacuum \
--random-seed=0 \
-c 8 \
-j 8 \
-M prepared \
-T 200 \
"pgbench_run_summary_update_${version}" \
-f- <<EOF &
\set gid random(1,1000)
INSERT INTO simple_table (group_id, data)
SELECT :gid, 'inserted'
RETURNING id \gset
update simple_table set data = 'updated' where id = :id;
insert into simple_table (group_id, data)
select :gid, 'inserted'
from generate_series(1,20);
EOF
insert_pid=$!
pgbench \
--no-vacuum \
--random-seed=0 \
-c 1 \
-j 1 \
--rate=1 \
-M prepared \
-T 200 \
"pgbench_run_summary_select_${version}" \
-f- <<EOF
\set gid random(1, 1000)
select max(created_at) from simple_table where group_id = :gid;
select count(*) from simple_table where group_id = :gid;
EOF
wait $insert_pid
Attachments:
Set-pd_prune_xid-on-insert.txttext/plain; charset=US-ASCII; name=Set-pd_prune_xid-on-insert.txtDownload+31-10
v5-0003-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchtext/x-patch; charset=US-ASCII; name=v5-0003-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchDownload+296-69
v5-0004-Use-xl_heap_prune-record-for-setting-empty-pages-.patchtext/x-patch; charset=US-ASCII; name=v5-0004-Use-xl_heap_prune-record-for-setting-empty-pages-.patchDownload+47-24
v5-0001-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchtext/x-patch; charset=US-ASCII; name=v5-0001-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchDownload+132-24
v5-0002-Make-heap_page_is_all_visible-independent-of-LVRe.patchtext/x-patch; charset=US-ASCII; name=v5-0002-Make-heap_page_is_all_visible-independent-of-LVRe.patchDownload+29-17
v5-0006-Combine-vacuum-phase-I-VM-update-cases.patchtext/x-patch; charset=US-ASCII; name=v5-0006-Combine-vacuum-phase-I-VM-update-cases.patchDownload+32-70
v5-0007-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchtext/x-patch; charset=US-ASCII; name=v5-0007-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchDownload+96-73
v5-0005-Combine-lazy_scan_prune-VM-corruption-cases.patchtext/x-patch; charset=US-ASCII; name=v5-0005-Combine-lazy_scan_prune-VM-corruption-cases.patchDownload+73-42
v5-0009-Update-VM-in-pruneheap.c.patchtext/x-patch; charset=US-ASCII; name=v5-0009-Update-VM-in-pruneheap.c.patchDownload+106-108
v5-0008-Keep-all_frozen-updated-too-in-heap_page_prune_an.patchtext/x-patch; charset=US-ASCII; name=v5-0008-Keep-all_frozen-updated-too-in-heap_page_prune_an.patchDownload+6-10
v5-0010-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchtext/x-patch; charset=US-ASCII; name=v5-0010-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchDownload+278-222
v5-0011-Rename-PruneState.freeze-to-attempt_freeze.patchtext/x-patch; charset=US-ASCII; name=v5-0011-Rename-PruneState.freeze-to-attempt_freeze.patchDownload+10-11
v5-0014-Use-GlobalVisState-to-determine-page-level-visibi.patchtext/x-patch; charset=US-ASCII; name=v5-0014-Use-GlobalVisState-to-determine-page-level-visibi.patchDownload+59-39
v5-0013-Rename-GlobalVisTestIsRemovableXid-to-GlobalVisXi.patchtext/x-patch; charset=US-ASCII; name=v5-0013-Rename-GlobalVisTestIsRemovableXid-to-GlobalVisXi.patchDownload+19-21
v5-0012-Remove-xl_heap_visible-entirely.patchtext/x-patch; charset=US-ASCII; name=v5-0012-Remove-xl_heap_visible-entirely.patchDownload+30-366
v5-0015-Inline-TransactionIdFollows-Precedes.patchtext/x-patch; charset=US-ASCII; name=v5-0015-Inline-TransactionIdFollows-Precedes.patchDownload+66-69
v5-0016-Unset-all-visible-sooner-if-not-freezing.patchtext/x-patch; charset=US-ASCII; name=v5-0016-Unset-all-visible-sooner-if-not-freezing.patchDownload+8-3
v5-0017-Allow-on-access-pruning-to-set-pages-all-visible.patchtext/x-patch; charset=US-ASCII; name=v5-0017-Allow-on-access-pruning-to-set-pages-all-visible.patchDownload+273-38
v5-0018-Add-helper-functions-to-heap_page_prune_and_freez.patchtext/x-patch; charset=US-ASCII; name=v5-0018-Add-helper-functions-to-heap_page_prune_and_freez.patchDownload+295-177
v5-0019-Reorder-heap_page_prune_and_freeze-parameters.patchtext/x-patch; charset=US-ASCII; name=v5-0019-Reorder-heap_page_prune_and_freeze-parameters.patchDownload+24-25
On Thu, Jul 31, 2025 at 6:58 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:
The patch "Set-pd_prune_xid-on-insert.txt" can be applied as the last
patch in the set. It sets pd_prune_xid on insert (so pages filled by
COPY or insert can also be set all-visible in the VM before they are
vacuumed). I gave it a .txt extension because it currently fails
035_standby_logical_decoding due to a recovery conflict. I need to
investigate more to see if this is a bug in my patch set or elsewhere
in Postgres.
I figured out that if we set the VM on-access, we need to enable
hot_standby_feedback in more places in 035_standby_logical_decoding.pl
to avoid recovery conflicts. I've done that in the attached updated
version 6. There are a few other issues in
035_standby_logical_decoding.pl that I reported here [1]/messages/by-id/CAAKRu_YO2mEm=ZWZKPjTMU=gW5Y83_KMi_1cr51JwavH0ctd7w@mail.gmail.com. With these
changes, setting pd_prune_xid on insert passes tests. Whether or not
we want to do it (and what the heuristic should be for deciding when
to do it) is another question.
- Melanie
[1]: /messages/by-id/CAAKRu_YO2mEm=ZWZKPjTMU=gW5Y83_KMi_1cr51JwavH0ctd7w@mail.gmail.com
Attachments:
v6-0001-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchtext/x-patch; charset=US-ASCII; name=v6-0001-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchDownload+132-24
v6-0003-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchtext/x-patch; charset=US-ASCII; name=v6-0003-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchDownload+296-69
v6-0005-Combine-lazy_scan_prune-VM-corruption-cases.patchtext/x-patch; charset=US-ASCII; name=v6-0005-Combine-lazy_scan_prune-VM-corruption-cases.patchDownload+73-42
v6-0002-Make-heap_page_is_all_visible-independent-of-LVRe.patchtext/x-patch; charset=US-ASCII; name=v6-0002-Make-heap_page_is_all_visible-independent-of-LVRe.patchDownload+29-17
v6-0004-Use-xl_heap_prune-record-for-setting-empty-pages-.patchtext/x-patch; charset=US-ASCII; name=v6-0004-Use-xl_heap_prune-record-for-setting-empty-pages-.patchDownload+47-24
v6-0007-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchtext/x-patch; charset=US-ASCII; name=v6-0007-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchDownload+96-73
v6-0008-Keep-all_frozen-updated-too-in-heap_page_prune_an.patchtext/x-patch; charset=US-ASCII; name=v6-0008-Keep-all_frozen-updated-too-in-heap_page_prune_an.patchDownload+6-10
v6-0006-Combine-vacuum-phase-I-VM-update-cases.patchtext/x-patch; charset=US-ASCII; name=v6-0006-Combine-vacuum-phase-I-VM-update-cases.patchDownload+32-70
v6-0009-Update-VM-in-pruneheap.c.patchtext/x-patch; charset=US-ASCII; name=v6-0009-Update-VM-in-pruneheap.c.patchDownload+106-108
v6-0010-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchtext/x-patch; charset=US-ASCII; name=v6-0010-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchDownload+278-222
v6-0013-Rename-GlobalVisTestIsRemovableXid-to-GlobalVisXi.patchtext/x-patch; charset=US-ASCII; name=v6-0013-Rename-GlobalVisTestIsRemovableXid-to-GlobalVisXi.patchDownload+19-21
v6-0014-Use-GlobalVisState-to-determine-page-level-visibi.patchtext/x-patch; charset=US-ASCII; name=v6-0014-Use-GlobalVisState-to-determine-page-level-visibi.patchDownload+59-39
v6-0012-Remove-xl_heap_visible-entirely.patchtext/x-patch; charset=US-ASCII; name=v6-0012-Remove-xl_heap_visible-entirely.patchDownload+30-366
v6-0011-Rename-PruneState.freeze-to-attempt_freeze.patchtext/x-patch; charset=US-ASCII; name=v6-0011-Rename-PruneState.freeze-to-attempt_freeze.patchDownload+10-11
v6-0015-Inline-TransactionIdFollows-Precedes.patchtext/x-patch; charset=US-ASCII; name=v6-0015-Inline-TransactionIdFollows-Precedes.patchDownload+66-69
v6-0017-Allow-on-access-pruning-to-set-pages-all-visible.patchtext/x-patch; charset=US-ASCII; name=v6-0017-Allow-on-access-pruning-to-set-pages-all-visible.patchDownload+278-41
v6-0019-Reorder-heap_page_prune_and_freeze-parameters.patchtext/x-patch; charset=US-ASCII; name=v6-0019-Reorder-heap_page_prune_and_freeze-parameters.patchDownload+24-25
v6-0016-Unset-all-visible-sooner-if-not-freezing.patchtext/x-patch; charset=US-ASCII; name=v6-0016-Unset-all-visible-sooner-if-not-freezing.patchDownload+8-3
v6-0018-Add-helper-functions-to-heap_page_prune_and_freez.patchtext/x-patch; charset=US-ASCII; name=v6-0018-Add-helper-functions-to-heap_page_prune_and_freez.patchDownload+295-177
v6-0020-Set-pd_prune_xid-on-insert.patchtext/x-patch; charset=US-ASCII; name=v6-0020-Set-pd_prune_xid-on-insert.patchDownload+31-10
On Sat, 2 Aug 2025 at 02:36, Melanie Plageman <melanieplageman@gmail.com> wrote:
On Thu, Jul 31, 2025 at 6:58 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:The patch "Set-pd_prune_xid-on-insert.txt" can be applied as the last
patch in the set. It sets pd_prune_xid on insert (so pages filled by
COPY or insert can also be set all-visible in the VM before they are
vacuumed). I gave it a .txt extension because it currently fails
035_standby_logical_decoding due to a recovery conflict. I need to
investigate more to see if this is a bug in my patch set or elsewhere
in Postgres.I figured out that if we set the VM on-access, we need to enable
hot_standby_feedback in more places in 035_standby_logical_decoding.pl
to avoid recovery conflicts. I've done that in the attached updated
version 6. There are a few other issues in
035_standby_logical_decoding.pl that I reported here [1]. With these
changes, setting pd_prune_xid on insert passes tests. Whether or not
we want to do it (and what the heuristic should be for deciding when
to do it) is another question.- Melanie
[1] /messages/by-id/CAAKRu_YO2mEm=ZWZKPjTMU=gW5Y83_KMi_1cr51JwavH0ctd7w@mail.gmail.com
Hi!
Andrey told me off-list about this thread and I decided to take a look.
I tried to play with each patch in this patchset and find a
corruption, but I was unsuccessful. I will conduct further tests
later. I am not implying that I suspect this patchset causes any
corruption; I am merely attempting to verify it.
I also have few comments and questions. Here is my (very limited)
review of 0001, because I believe that removing xl_heap_visible from
COPY FREEZE is pure win, so this patch can be very beneficial by
itself.
visibilitymap_set_vmbyte is introduced in 0001 and removed in 0012.
This is strange to me, maybe we can avoid visibilitymap_set_vmbyte in
first place?
In 0001:
1)
should we add "Assert(LWLockHeldByMeInMode(BufferDescriptorGetContentLock(bufHdr),
LW_EXCLUSIVE));" in visibilitymap_set_vmbyte?
Also here `Assert(visibilitymap_pin_ok(BufferGetBlockNumber(buffer),
vmbuffer));` can be beneficial:
/* + * If we're only adding already frozen rows to a previously empty + * page, mark it as all-frozen and update the visibility map. We're + * already holding a pin on the vmbuffer. + */ else if (all_frozen_set) + { PageSetAllVisible(page); + LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE); + visibilitymap_set_vmbyte(relation, + BufferGetBlockNumber(buffer), + vmbuffer, + VISIBILITYMAP_ALL_VISIBLE | + VISIBILITYMAP_ALL_FROZEN); + }
2)
in heap_xlog_multi_insert:
+
+ visibilitymap_pin(reln, blkno, &vmbuffer);
+ visibilitymap_set_vmbyte(....)
Do we need to pin vmbuffer here? Looks like
XLogReadBufferForRedoExtended already pins vmbuffer. I verified this
with CheckBufferIsPinnedOnce(vmbuffer) just before visibilitymap_pin
and COPY ... WITH (FREEZE true) test.
3)
+ +#ifdef TRACE_VISIBILITYMAP + elog(DEBUG1, "vm_set %s %d", RelationGetRelationName(rel), heapBlk); +#endif
I can see this merely copy-pasted from visibilitymap_set, but maybe
display "flags" also?
4) visibilitymap_set receives XLogRecPtr recptr parameters, which is
set to WAL record lsn during recovery and to InvalidXLogRecPtr
otherwise. visibilitymap_set manages VM page LSN bases on this recptr
value (inside function logic). visibilitymap_set_vmbyte behaves
vise-versa and makes its caller responsible for page LSN management.
Maybe we should keep these two functions akin to each other?
--
Best regards,
Kirill Reshke
On Sat, 2 Aug 2025 at 02:36, Melanie Plageman <melanieplageman@gmail.com> wrote:
On Thu, Jul 31, 2025 at 6:58 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:The patch "Set-pd_prune_xid-on-insert.txt" can be applied as the last
patch in the set. It sets pd_prune_xid on insert (so pages filled by
COPY or insert can also be set all-visible in the VM before they are
vacuumed). I gave it a .txt extension because it currently fails
035_standby_logical_decoding due to a recovery conflict. I need to
investigate more to see if this is a bug in my patch set or elsewhere
in Postgres.I figured out that if we set the VM on-access, we need to enable
hot_standby_feedback in more places in 035_standby_logical_decoding.pl
to avoid recovery conflicts. I've done that in the attached updated
version 6. There are a few other issues in
035_standby_logical_decoding.pl that I reported here [1]. With these
changes, setting pd_prune_xid on insert passes tests. Whether or not
we want to do it (and what the heuristic should be for deciding when
to do it) is another question.- Melanie
[1] /messages/by-id/CAAKRu_YO2mEm=ZWZKPjTMU=gW5Y83_KMi_1cr51JwavH0ctd7w@mail.gmail.com
0002 No comments from me. Looks straightforward.
Few comments on 0003.
1) This patch introduces XLHP_HAS_VMFLAGS. However it lacks some
helpful comments about this new status bit.
a) In heapam_xlog.h, in xl_heap_prune struct definition:
/*
* If XLHP_HAS_CONFLICT_HORIZON is set, the conflict horizon XID follows,
* unaligned
*/
+ /* If XLHP_HAS_VMFLAGS is set, newly set visibility map bits comes,
unaligned */
b)
we can add here comment why we use memcpy assignment, akin to /*
memcpy() because snapshot_conflict_horizon is stored unaligned */
+ /* Next are the optionally included vmflags. Copy them out for later use. */
+ if ((xlrec.flags & XLHP_HAS_VMFLAGS) != 0)
+ {
+ memcpy(&vmflags, maindataptr, sizeof(uint8));
+ maindataptr += sizeof(uint8);
2) Should we move conflict_xid = visibility_cutoff_xid; assignment
just after heap_page_is_all_visible_except_lpdead call in
lazy_vacuum_heap_page?
3) Looking at this diff, do not comprehend one bit: how are we
protected from passing an all-visible page to lazy_vacuum_heap_page. I
did not manage to reproduce such behaviour though.
+ if ((vmflags & VISIBILITYMAP_VALID_BITS) != 0)
+ {
+ Assert(!PageIsAllVisible(page));
+ set_pd_all_vis = true;
+ LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE);
+ PageSetAllVisible(page);
+ visibilitymap_set_vmbyte(vacrel->rel,
+ blkno,
+
--
Best regards,
Kirill Reshke
On Sat, 2 Aug 2025 at 02:36, Melanie Plageman <melanieplageman@gmail.com> wrote:
On Thu, Jul 31, 2025 at 6:58 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:The patch "Set-pd_prune_xid-on-insert.txt" can be applied as the last
patch in the set. It sets pd_prune_xid on insert (so pages filled by
COPY or insert can also be set all-visible in the VM before they are
vacuumed). I gave it a .txt extension because it currently fails
035_standby_logical_decoding due to a recovery conflict. I need to
investigate more to see if this is a bug in my patch set or elsewhere
in Postgres.I figured out that if we set the VM on-access, we need to enable
hot_standby_feedback in more places in 035_standby_logical_decoding.pl
to avoid recovery conflicts. I've done that in the attached updated
version 6. There are a few other issues in
035_standby_logical_decoding.pl that I reported here [1]. With these
changes, setting pd_prune_xid on insert passes tests. Whether or not
we want to do it (and what the heuristic should be for deciding when
to do it) is another question.- Melanie
[1] /messages/by-id/CAAKRu_YO2mEm=ZWZKPjTMU=gW5Y83_KMi_1cr51JwavH0ctd7w@mail.gmail.com
v6-0015:
I chose to verify whether this single modification would be beneficial
on the HEAD.
Benchmark I did:
```
\timing
CREATE TABLE zz(i int);
alter table zz set (autovacuum_enabled = false);
TRUNCATE zz;
copy zz from program 'yes 2 | head -n 180000000';
copy zz from program 'yes 2 | head -n 180000000';
delete from zz where (REPLACE(REPLACE(ctid::text, '(', '{'), ')',
'}')::int[])[2] = 7 ;
VACUUM FREEZE zz;
```
And I checked perf top footprint for last statement (vacuum). My
detailed results are attached. It is a HEAD vs HEAD+v6-0015 benchmark.
TLDR: function inlining is indeed beneficial, TransactionIdPrecedes
function disappears from perf top footprint, though query runtime is
not changed much. So, while not resulting in query speedup, this can
save CPU.
Maybe we can derive an artificial benchmark, which will show query
speed up, but for now I dont have one.
--
Best regards,
Kirill Reshke
Attachments:
attach.txttext/plain; charset=US-ASCII; name=attach.txtDownload
Thanks for all the reviews. I'm working on responding to your previous
mails with a new version.
On Wed, Aug 27, 2025 at 8:55 AM Kirill Reshke <reshkekirill@gmail.com> wrote:
v6-0015:
I chose to verify whether this single modification would be beneficial
on the HEAD.Benchmark I did:
```
\timing
CREATE TABLE zz(i int);
alter table zz set (autovacuum_enabled = false);
TRUNCATE zz;
copy zz from program 'yes 2 | head -n 180000000';
copy zz from program 'yes 2 | head -n 180000000';delete from zz where (REPLACE(REPLACE(ctid::text, '(', '{'), ')',
'}')::int[])[2] = 7 ;VACUUM FREEZE zz;
```And I checked perf top footprint for last statement (vacuum). My
detailed results are attached. It is a HEAD vs HEAD+v6-0015 benchmark.TLDR: function inlining is indeed beneficial, TransactionIdPrecedes
function disappears from perf top footprint, though query runtime is
not changed much. So, while not resulting in query speedup, this can
save CPU.
Maybe we can derive an artificial benchmark, which will show query
speed up, but for now I dont have one.
I'm not surprised that vacuum freeze does not show a speed up from the
function inlining.
This patch was key for avoiding a regression in the most contrived
worst case scenario example of setting the VM on-access. That is, if
you are pruning only a single tuple on the page as part of a SELECT
query that returns no tuples (think SELECT * FROM foo OFFSET N where N
is greater than the number of rows in the table), and I add
determining if the page is all visible, then the overhead of these
extra function calls in heap_prune_record_unchanged_lp_normal() is
noticeable.
We might be able to come up with a similar example in vacuum without
freeze since it will try to determine if the page is all-visible. Your
example is still running on my machine, though, so I haven't verified
this yet :)
- Melanie
Thanks for the review! Updates are in attached v7.
One note on 0022 in the set, which sets pd_prune_xid on insert: the
recently added index-killtuples isolation test was failing with this
patch applied. With the patch, the "access" step reports more heap
page hits than before. After some analysis, it seems one of the heap
pages in kill_prior_tuples table is now being pruned in an earlier
step. Somehow this leads to 4 hits counted instead of 3 (even though
there are only 4 blocks in the relation). I recall Bertrand mentioning
something in some other thread about hits being double counted with
AIO reads, so I'm going to try and go dig that up. But, for now, I've
modified the test -- I believe the patch is only revealing an issue
with instrumentation, not causing a bug.
On Tue, Aug 26, 2025 at 5:58 AM Kirill Reshke <reshkekirill@gmail.com> wrote:
visibilitymap_set_vmbyte is introduced in 0001 and removed in 0012.
This is strange to me, maybe we can avoid visibilitymap_set_vmbyte in
first place?
The reason for this is that in the earlier patch I introduce
visibilitymap_set_vmbyte() for one user while other users still use
visibilitymap_set(). But, by the end of the set, all users use
visibillitymap_set_vmbyte(). So I think it makes most sense for it to
be named visibilitymap_set() at that point. Until all users are
committed, the two functions both have to exist and need different
names.
In 0001:
should we add "Assert(LWLockHeldByMeInMode(BufferDescriptorGetContentLock(bufHdr),
LW_EXCLUSIVE));" in visibilitymap_set_vmbyte?
I don't want any operations on the heap buffer (including asserts) in
visibilitymap_set_vmbyte(). The heap block is only provided to look up
the VM bits.
I think your idea is a good one for the existing visibilitymap_set(),
though, so I've added a new patch to the set (0002) which does this. I
also added a similar assertion for the vmbuffer to
visibilitymap_set_vmbyte().
Also here `Assert(visibilitymap_pin_ok(BufferGetBlockNumber(buffer),
vmbuffer));` can be beneficial:
I had omitted this because the same logic is checked inside of
visiblitymap_set_vmbyte() with an error occurring if conditions are
not met. However, since the same is true in visibilitymap_set() and
heap_multi_insert() still asserted visiblitymap_pin_ok(), I've added
it back to my patch set.
in heap_xlog_multi_insert: + + visibilitymap_pin(reln, blkno, &vmbuffer); + visibilitymap_set_vmbyte(....)Do we need to pin vmbuffer here? Looks like
XLogReadBufferForRedoExtended already pins vmbuffer. I verified this
with CheckBufferIsPinnedOnce(vmbuffer) just before visibilitymap_pin
and COPY ... WITH (FREEZE true) test.
I thought the reason visibilitymap_set() did it was that it was
possible for the block of the VM corresponding to the block of the
heap to be different during recovery than it was when emitting the
record, and thus we needed the part of visiblitymap_pin() that
released the old vmbuffer and got the new one corresponding to the
heap block.
I can't quite think of how this could happen though.
Assuming it can't happen, then we can get rid of visiblitymap_pin()
(and add visibilitymap_pin_ok()) in both visiblitymap_set_vmbyte() and
visibilitymap_set(). I've done this to visibilitymap_set() in a
separate patch 0001. I would like other opinions/confirmation that the
block of the VM corresponding to the heap block cannot differ during
recovery from that what it was when the record was emitted during
normal operation, though.
+#ifdef TRACE_VISIBILITYMAP + elog(DEBUG1, "vm_set %s %d", RelationGetRelationName(rel), heapBlk); +#endifI can see this merely copy-pasted from visibilitymap_set, but maybe
display "flags" also?
Done in attached.
4) visibilitymap_set receives XLogRecPtr recptr parameters, which is
set to WAL record lsn during recovery and to InvalidXLogRecPtr
otherwise. visibilitymap_set manages VM page LSN bases on this recptr
value (inside function logic). visibilitymap_set_vmbyte behaves
vise-versa and makes its caller responsible for page LSN management.
Maybe we should keep these two functions akin to each other?
So, with visibilitymap_set_vmbyte(), the whole idea is to just update
the VM and then leave the WAL logging and other changes to the caller
(like marking the buffer dirty, setting the page LSN, etc). The series
of operations needed to make a persistent change are up to the caller.
visibilitymap_set() is meant to just make sure that the correct bits
in the VM are set for the given heap block.
I looked at ways of making the current visibilitymap_set() API cleaner
-- like setting the heap page LSN with the VM recptr in the caller of
visibilitymap_set() instead. There wasn't a way of doing it that
seemed like enough of an improvement to merit the change.
Not to mention, the goal of the patchset is to remove the current
visibilitymap_set(), so I'm not too worried about parity between the
two functions. They may coexist for awhile, but hopefully today's
visibilitymap_set() will eventually be removed.
- Melanie
Attachments:
v7-0005-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchtext/x-patch; charset=US-ASCII; name=v7-0005-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchDownload+300-70
v7-0004-Make-heap_page_is_all_visible-independent-of-LVRe.patchtext/x-patch; charset=US-ASCII; name=v7-0004-Make-heap_page_is_all_visible-independent-of-LVRe.patchDownload+29-17
v7-0002-Add-assert-and-log-message-to-visibilitymap_set.patchtext/x-patch; charset=US-ASCII; name=v7-0002-Add-assert-and-log-message-to-visibilitymap_set.patchDownload+4-2
v7-0001-Remove-unneeded-VM-pin-from-VM-replay.patchtext/x-patch; charset=US-ASCII; name=v7-0001-Remove-unneeded-VM-pin-from-VM-replay.patchDownload+1-2
v7-0003-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchtext/x-patch; charset=US-ASCII; name=v7-0003-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchDownload+138-24
v7-0006-Use-xl_heap_prune-record-for-setting-empty-pages-.patchtext/x-patch; charset=US-ASCII; name=v7-0006-Use-xl_heap_prune-record-for-setting-empty-pages-.patchDownload+47-24
v7-0007-Combine-lazy_scan_prune-VM-corruption-cases.patchtext/x-patch; charset=US-ASCII; name=v7-0007-Combine-lazy_scan_prune-VM-corruption-cases.patchDownload+73-42
v7-0008-Combine-vacuum-phase-I-VM-update-cases.patchtext/x-patch; charset=US-ASCII; name=v7-0008-Combine-vacuum-phase-I-VM-update-cases.patchDownload+32-70
v7-0009-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchtext/x-patch; charset=US-ASCII; name=v7-0009-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchDownload+96-73
v7-0011-Update-VM-in-pruneheap.c.patchtext/x-patch; charset=US-ASCII; name=v7-0011-Update-VM-in-pruneheap.c.patchDownload+106-108
v7-0010-Keep-all_frozen-updated-too-in-heap_page_prune_an.patchtext/x-patch; charset=US-ASCII; name=v7-0010-Keep-all_frozen-updated-too-in-heap_page_prune_an.patchDownload+6-10
v7-0012-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchtext/x-patch; charset=US-ASCII; name=v7-0012-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchDownload+278-222
v7-0014-Remove-xl_heap_visible-entirely.patchtext/x-patch; charset=US-ASCII; name=v7-0014-Remove-xl_heap_visible-entirely.patchDownload+30-369
v7-0015-Rename-GlobalVisTestIsRemovableXid-to-GlobalVisXi.patchtext/x-patch; charset=US-ASCII; name=v7-0015-Rename-GlobalVisTestIsRemovableXid-to-GlobalVisXi.patchDownload+19-21
v7-0013-Rename-PruneState.freeze-to-attempt_freeze.patchtext/x-patch; charset=US-ASCII; name=v7-0013-Rename-PruneState.freeze-to-attempt_freeze.patchDownload+10-11
v7-0016-Use-GlobalVisState-to-determine-page-level-visibi.patchtext/x-patch; charset=US-ASCII; name=v7-0016-Use-GlobalVisState-to-determine-page-level-visibi.patchDownload+59-39
v7-0017-Inline-TransactionIdFollows-Precedes.patchtext/x-patch; charset=US-ASCII; name=v7-0017-Inline-TransactionIdFollows-Precedes.patchDownload+66-69
v7-0019-Allow-on-access-pruning-to-set-pages-all-visible.patchtext/x-patch; charset=US-ASCII; name=v7-0019-Allow-on-access-pruning-to-set-pages-all-visible.patchDownload+276-39
v7-0018-Unset-all-visible-sooner-if-not-freezing.patchtext/x-patch; charset=US-ASCII; name=v7-0018-Unset-all-visible-sooner-if-not-freezing.patchDownload+8-3
v7-0021-Reorder-heap_page_prune_and_freeze-parameters.patchtext/x-patch; charset=US-ASCII; name=v7-0021-Reorder-heap_page_prune_and_freeze-parameters.patchDownload+24-25
v7-0022-Set-pd_prune_xid-on-insert.patchtext/x-patch; charset=US-ASCII; name=v7-0022-Set-pd_prune_xid-on-insert.patchDownload+34-13
v7-0020-Add-helper-functions-to-heap_page_prune_and_freez.patchtext/x-patch; charset=US-ASCII; name=v7-0020-Add-helper-functions-to-heap_page_prune_and_freez.patchDownload+295-177
On Tue, Aug 26, 2025 at 4:01 PM Kirill Reshke <reshkekirill@gmail.com> wrote:
Few comments on 0003.
1) This patch introduces XLHP_HAS_VMFLAGS. However it lacks some
helpful comments about this new status bit.
I added the ones you suggested in my v7 posted here [1]/messages/by-id/CAAKRu_YD0ecXeAh+DmJpzQOJwcRzmMyGdcc5W_0pEF78rYSJkQ@mail.gmail.com.
2) Should we move conflict_xid = visibility_cutoff_xid; assignment
just after heap_page_is_all_visible_except_lpdead call in
lazy_vacuum_heap_page?
Why would we want to do that? We only want to set it if the page is
all visible, so we would have to guard it similarly.
3) Looking at this diff, do not comprehend one bit: how are we
protected from passing an all-visible page to lazy_vacuum_heap_page. I
did not manage to reproduce such behaviour though.+ if ((vmflags & VISIBILITYMAP_VALID_BITS) != 0) + { + Assert(!PageIsAllVisible(page)); + set_pd_all_vis = true; + LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE); + PageSetAllVisible(page); + visibilitymap_set_vmbyte(vacrel->rel, + blkno,
So, for one, there is an assert just above this code in
lazy_vacuum_heap_page() that nunused > 0 -- so we know that the page
couldn't have been all-visible already because it had unused line
pointers.
Otherwise, if it was possible for an already all-visible page to get
here, the same thing would happen that happens on master --
heap_page_is_all_visible[_except_lpdead()] would return true and we
would try to set the VM which would end up being a no-op.
- Melanie
[1]: /messages/by-id/CAAKRu_YD0ecXeAh+DmJpzQOJwcRzmMyGdcc5W_0pEF78rYSJkQ@mail.gmail.com
On Thu, 28 Aug 2025 at 00:02, Melanie Plageman
<melanieplageman@gmail.com> wrote:
Do we need to pin vmbuffer here? Looks like
XLogReadBufferForRedoExtended already pins vmbuffer. I verified this
with CheckBufferIsPinnedOnce(vmbuffer) just before visibilitymap_pin
and COPY ... WITH (FREEZE true) test.I thought the reason visibilitymap_set() did it was that it was
possible for the block of the VM corresponding to the block of the
heap to be different during recovery than it was when emitting the
record, and thus we needed the part of visiblitymap_pin() that
released the old vmbuffer and got the new one corresponding to the
heap block.I can't quite think of how this could happen though.
Assuming it can't happen, then we can get rid of visiblitymap_pin()
(and add visibilitymap_pin_ok()) in both visiblitymap_set_vmbyte() and
visibilitymap_set(). I've done this to visibilitymap_set() in a
separate patch 0001. I would like other opinions/confirmation that the
block of the VM corresponding to the heap block cannot differ during
recovery from that what it was when the record was emitted during
normal operation, though.
I did micro git-blame research here. I spotted only one related change
[0]: https://github.com/postgres/postgres/commit/2c03216d8311
But not after this change, so this visibilitymap_pin is just an oversight?
Related thread is [1]/messages/by-id/533D6CBF.6080203@vmware.com. I quickly checked the discussion in this
thread, and it looks like no one was bothered about these lines or VM
logging changes (in this exact pin buffer aspect). The discussion was
of other aspects of this commit.
[0]: https://github.com/postgres/postgres/commit/2c03216d8311
[1]: /messages/by-id/533D6CBF.6080203@vmware.com
--
Best regards,
Kirill Reshke
On Thu, Aug 28, 2025 at 5:12 AM Kirill Reshke <reshkekirill@gmail.com> wrote:
I did micro git-blame research here. I spotted only one related change
[0]. Looks like before this change pin was indeed needed.
But not after this change, so this visibilitymap_pin is just an oversight?
Related thread is [1]. I quickly checked the discussion in this
thread, and it looks like no one was bothered about these lines or VM
logging changes (in this exact pin buffer aspect). The discussion was
of other aspects of this commit.
Wow, thanks so much for doing that research. Looking at it myself, it
does indeed seem like just an oversight. It isn't harmful since it
won't take another pin, but it is confusing, so I think we should at
least remove it in master. I'm not as sure about back branches.
I would like someone to confirm that there is no way we could end up
with a different block of the VM containing the vm bits for a heap
block during recovery than during normal operation.
- Melanie
On Tue, Sep 2, 2025 at 5:52 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:
On Thu, Aug 28, 2025 at 5:12 AM Kirill Reshke <reshkekirill@gmail.com> wrote:
I did micro git-blame research here. I spotted only one related change
[0]. Looks like before this change pin was indeed needed.
But not after this change, so this visibilitymap_pin is just an oversight?
Related thread is [1]. I quickly checked the discussion in this
thread, and it looks like no one was bothered about these lines or VM
logging changes (in this exact pin buffer aspect). The discussion was
of other aspects of this commit.Wow, thanks so much for doing that research. Looking at it myself, it
does indeed seem like just an oversight. It isn't harmful since it
won't take another pin, but it is confusing, so I think we should at
least remove it in master. I'm not as sure about back branches.
I've updated the commit message in the patch set to reflect the
research you did in attached v8.
- Melanie
Attachments:
v8-0002-Add-assert-and-log-message-to-visibilitymap_set.patchtext/x-patch; charset=US-ASCII; name=v8-0002-Add-assert-and-log-message-to-visibilitymap_set.patchDownload+4-2
v8-0001-Remove-unneeded-VM-pin-from-VM-replay.patchtext/x-patch; charset=US-ASCII; name=v8-0001-Remove-unneeded-VM-pin-from-VM-replay.patchDownload+1-2
v8-0005-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchtext/x-patch; charset=US-ASCII; name=v8-0005-Eliminate-xl_heap_visible-from-vacuum-phase-III.patchDownload+300-70
v8-0004-Make-heap_page_is_all_visible-independent-of-LVRe.patchtext/x-patch; charset=US-ASCII; name=v8-0004-Make-heap_page_is_all_visible-independent-of-LVRe.patchDownload+29-17
v8-0008-Combine-vacuum-phase-I-VM-update-cases.patchtext/x-patch; charset=US-ASCII; name=v8-0008-Combine-vacuum-phase-I-VM-update-cases.patchDownload+32-70
v8-0006-Use-xl_heap_prune-record-for-setting-empty-pages-.patchtext/x-patch; charset=US-ASCII; name=v8-0006-Use-xl_heap_prune-record-for-setting-empty-pages-.patchDownload+47-24
v8-0009-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchtext/x-patch; charset=US-ASCII; name=v8-0009-Find-and-fix-VM-corruption-in-heap_page_prune_and.patchDownload+96-73
v8-0007-Combine-lazy_scan_prune-VM-corruption-cases.patchtext/x-patch; charset=US-ASCII; name=v8-0007-Combine-lazy_scan_prune-VM-corruption-cases.patchDownload+73-42
v8-0003-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchtext/x-patch; charset=US-ASCII; name=v8-0003-Eliminate-xl_heap_visible-in-COPY-FREEZE.patchDownload+138-24
v8-0014-Remove-xl_heap_visible-entirely.patchtext/x-patch; charset=US-ASCII; name=v8-0014-Remove-xl_heap_visible-entirely.patchDownload+30-369
v8-0011-Update-VM-in-pruneheap.c.patchtext/x-patch; charset=US-ASCII; name=v8-0011-Update-VM-in-pruneheap.c.patchDownload+106-108
v8-0010-Keep-all_frozen-updated-too-in-heap_page_prune_an.patchtext/x-patch; charset=US-ASCII; name=v8-0010-Keep-all_frozen-updated-too-in-heap_page_prune_an.patchDownload+6-10
v8-0012-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchtext/x-patch; charset=US-ASCII; name=v8-0012-Eliminate-xl_heap_visible-from-vacuum-phase-I-pru.patchDownload+278-222
v8-0013-Rename-PruneState.freeze-to-attempt_freeze.patchtext/x-patch; charset=US-ASCII; name=v8-0013-Rename-PruneState.freeze-to-attempt_freeze.patchDownload+10-11
v8-0015-Rename-GlobalVisTestIsRemovableXid-to-GlobalVisXi.patchtext/x-patch; charset=US-ASCII; name=v8-0015-Rename-GlobalVisTestIsRemovableXid-to-GlobalVisXi.patchDownload+19-21
v8-0016-Use-GlobalVisState-to-determine-page-level-visibi.patchtext/x-patch; charset=US-ASCII; name=v8-0016-Use-GlobalVisState-to-determine-page-level-visibi.patchDownload+59-39
v8-0017-Inline-TransactionIdFollows-Precedes.patchtext/x-patch; charset=US-ASCII; name=v8-0017-Inline-TransactionIdFollows-Precedes.patchDownload+66-69
v8-0018-Unset-all-visible-sooner-if-not-freezing.patchtext/x-patch; charset=US-ASCII; name=v8-0018-Unset-all-visible-sooner-if-not-freezing.patchDownload+8-3
v8-0019-Allow-on-access-pruning-to-set-pages-all-visible.patchtext/x-patch; charset=US-ASCII; name=v8-0019-Allow-on-access-pruning-to-set-pages-all-visible.patchDownload+276-39
v8-0020-Add-helper-functions-to-heap_page_prune_and_freez.patchtext/x-patch; charset=US-ASCII; name=v8-0020-Add-helper-functions-to-heap_page_prune_and_freez.patchDownload+295-177
v8-0021-Reorder-heap_page_prune_and_freeze-parameters.patchtext/x-patch; charset=US-ASCII; name=v8-0021-Reorder-heap_page_prune_and_freeze-parameters.patchDownload+24-25
v8-0022-Set-pd_prune_xid-on-insert.patchtext/x-patch; charset=US-ASCII; name=v8-0022-Set-pd_prune_xid-on-insert.patchDownload+34-13
Hi,
On 2025-09-02 19:11:01 -0400, Melanie Plageman wrote:
From dd98177294011ee93cac122405516abd89f4e393 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 27 Aug 2025 08:50:15 -0400
Subject: [PATCH v8 01/22] Remove unneeded VM pin from VM replay
LGTM.
From 7c5cb3edf89735eaa8bee9ca46111bd6c554720b Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 27 Aug 2025 10:07:29 -0400
Subject: [PATCH v8 02/22] Add assert and log message to visibilitymap_set
LGTM.
From 07f31099754636ec9dabf6cca06c33c4b19c230c Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Tue, 17 Jun 2025 17:22:10 -0400
Subject: [PATCH v8 03/22] Eliminate xl_heap_visible in COPY FREEZEInstead of emitting a separate WAL record for setting the VM bits in
xl_heap_visible, specify the changes to make to the VM block in the
xl_heap_multi_insert record instead.Author: Melanie Plageman <melanieplageman@gmail.com>
Reviewed-by: Kirill Reshke <reshkekirill@gmail.com>
Discussion: /messages/by-id/flat/CAAKRu_ZMw6Npd_qm2KM+FwQ3cMOMx1Dh3VMhp8-V7SOLxdK9-g@mail.gmail.com
+ /* + * If we're only adding already frozen rows to a previously empty + * page, mark it as all-frozen and update the visibility map. We're + * already holding a pin on the vmbuffer. + */ else if (all_frozen_set) + { + Assert(visibilitymap_pin_ok(BufferGetBlockNumber(buffer), vmbuffer)); PageSetAllVisible(page); + LockBuffer(vmbuffer, BUFFER_LOCK_EXCLUSIVE); + visibilitymap_set_vmbyte(relation, + BufferGetBlockNumber(buffer), + vmbuffer, + VISIBILITYMAP_ALL_VISIBLE | + VISIBILITYMAP_ALL_FROZEN); + }
From an abstraction POV I don't love that heapam now is responsible for
acquiring and releasing the lock. But that ship already kind of has sailed, as
heapam.c is already responsible for releasing the vm buffer etc...
I've wondered about splitting the responsibilities up into multiple
visibilitymap_set_* functions, so that heapam.c wouldn't need to acquire the
lock and set the LSN. But it's probably not worth it.
+ /* + * Now read and update the VM block. Even if we skipped updating the heap + * page due to the file being dropped or truncated later in recovery, it's + * still safe to update the visibility map. Any WAL record that clears + * the visibility map bit does so before checking the page LSN, so any + * bits that need to be cleared will still be cleared. + * + * It is only okay to set the VM bits without holding the heap page lock + * because we can expect no other writers of this page. + */ + if (xlrec->flags & XLH_INSERT_ALL_FROZEN_SET && + XLogReadBufferForRedoExtended(record, 1, RBM_ZERO_ON_ERROR, false, + &vmbuffer) == BLK_NEEDS_REDO) + { + Relation reln = CreateFakeRelcacheEntry(rlocator); + + Assert(visibilitymap_pin_ok(blkno, vmbuffer)); + visibilitymap_set_vmbyte(reln, blkno, + vmbuffer, + VISIBILITYMAP_ALL_VISIBLE | + VISIBILITYMAP_ALL_FROZEN); + + /* + * It is not possible that the VM was already set for this heap page, + * so the vmbuffer must have been modified and marked dirty. + */ + Assert(BufferIsDirty(vmbuffer));
How about making visibilitymap_set_vmbyte() return whether it needed to do
something? This seems somewhat indirect...
I think it might be good to encapsulate this code into a helper in
visibilitymap.c, there will be more callers in the subsequent patches.
+/* + * Set flags in the VM block contained in the passed in vmBuf. + * + * This function is for callers which include the VM changes in the same WAL + * record as the modifications of the heap page which rendered it all-visible. + * Callers separately logging the VM changes should invoke visibilitymap_set() + * instead. + * + * Caller must have pinned and exclusive locked the correct block of the VM in + * vmBuf. This block should contain the VM bits for the given heapBlk. + * + * During normal operation (i.e. not recovery), this should be called in a + * critical section which also makes any necessary changes to the heap page + * and, if relevant, emits WAL. + * + * Caller is responsible for WAL logging the changes to the VM buffer and for + * making any changes needed to the associated heap page. This includes + * maintaining any invariants such as ensuring the buffer containing heapBlk + * is pinned and exclusive locked. + */ +uint8 +visibilitymap_set_vmbyte(Relation rel, BlockNumber heapBlk, + Buffer vmBuf, uint8 flags)
Why is it named vmbyte? This actually just sets the two bits corresponding to
the buffer, not the entire byte. So it seems somewhat misleading to reference
byte.
From dc318358572f61efbd0e05aae2b9a077b422bcf5 Mon Sep 17 00:00:00 2001
From: Melanie Plageman <melanieplageman@gmail.com>
Date: Wed, 18 Jun 2025 12:42:13 -0400
Subject: [PATCH v8 05/22] Eliminate xl_heap_visible from vacuum phase IIIInstead of emitting a separate xl_heap_visible record for each page that
is rendered all-visible by vacuum's third phase, include the updates to
the VM in the already emitted xl_heap_prune record.
Reading through the change I didn't particularly like that there's another
optional field in xl_heap_prune, as it seemed liked something that should be
encoded in flags. Of course there aren't enough flag bits available. But
that made me look at the rest of the record: Uh, what do we use the reason
field for? As far as I can tell f83d709760d8 added it without introducing any
users? It doesn't even seem to be set.
@@ -51,10 +52,15 @@ heap_xlog_prune_freeze(XLogReaderState *record)
(xlrec.flags & (XLHP_HAS_REDIRECTIONS | XLHP_HAS_DEAD_ITEMS)) == 0);/* - * We are about to remove and/or freeze tuples. In Hot Standby mode, - * ensure that there are no queries running for which the removed tuples - * are still visible or which still consider the frozen xids as running. - * The conflict horizon XID comes after xl_heap_prune. + * After xl_heap_prune is the optional snapshot conflict horizon. + * + * In Hot Standby mode, we must ensure that there are no running queries + * which would conflict with the changes in this record. If pruning, that + * means we cannot remove tuples still visible to transactions on the + * standby. If freezing, that means we cannot freeze tuples with xids that + * are still considered running on the standby. And for setting the VM, we + * cannot do so if the page isn't all-visible to all transactions on the + * standby. */
I'm a bit confused by this new comment - it sounds like we're deciding whether
to remove tuple versions, but that decision has long been made, no?
@@ -2846,8 +2848,11 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer, OffsetNumber unused[MaxHeapTuplesPerPage]; int nunused = 0; TransactionId visibility_cutoff_xid; + TransactionId conflict_xid = InvalidTransactionId; bool all_frozen; LVSavedErrInfo saved_err_info; + uint8 vmflags = 0; + bool set_pd_all_vis = false;Assert(vacrel->do_index_vacuuming);
@@ -2858,6 +2863,20 @@ lazy_vacuum_heap_page(LVRelState *vacrel, BlockNumber blkno, Buffer buffer,
VACUUM_ERRCB_PHASE_VACUUM_HEAP, blkno,
InvalidOffsetNumber);+ if (heap_page_is_all_visible_except_lpdead(vacrel->rel, buffer, + vacrel->cutoffs.OldestXmin, + deadoffsets, num_offsets, + &all_frozen, &visibility_cutoff_xid, + &vacrel->offnum)) + { + vmflags |= VISIBILITYMAP_ALL_VISIBLE; + if (all_frozen) + { + vmflags |= VISIBILITYMAP_ALL_FROZEN; + Assert(!TransactionIdIsValid(visibility_cutoff_xid)); + } + } + START_CRIT_SECTION();
I am rather confused - we never can set all-visible if there are any LP_DEAD
items left. If the idea is that we are removing the LP_DEAD items in
lazy_vacuum_heap_page() - what guarantees that all LP_DEAD items are being
removed? Couldn't some tuples get marked LP_DEAD by on-access pruning, after
vacuum visited the page and collected dead items?
Ugh, I see - it works because we pass in the set of dead items. I think that
makes the name *really* misleading, it's not except LP_DEAD, it's except the
offsets passed in, no?
But then you actually check that the set of dead items didn't change - what
guarantees that?
I didn't look at the later patches, except that I did notice this:
@@ -268,7 +264,7 @@ heap_xlog_prune_freeze(XLogReaderState *record)
Relation reln = CreateFakeRelcacheEntry(rlocator);visibilitymap_pin(reln, blkno, &vmbuffer); - old_vmbits = visibilitymap_set_vmbyte(reln, blkno, vmbuffer, vmflags); + old_vmbits = visibilitymap_set(reln, blkno, vmbuffer, vmflags); /* Only set VM page LSN if we modified the page */ if (old_vmbits != vmflags) PageSetLSN(BufferGetPage(vmbuffer), lsn); @@ -279,143 +275,6 @@ heap_xlog_prune_freeze(XLogReaderState *record) UnlockReleaseBuffer(vmbuffer); }
Why are we manually pinning the vm buffer here? Shouldn't the xlog machinery
have done so, as you noticed in one of the early on patches?
Greetings,
Andres Freund
On Wed, 3 Sept 2025 at 04:11, Melanie Plageman
<melanieplageman@gmail.com> wrote:
On Tue, Sep 2, 2025 at 5:52 PM Melanie Plageman
<melanieplageman@gmail.com> wrote:On Thu, Aug 28, 2025 at 5:12 AM Kirill Reshke <reshkekirill@gmail.com> wrote:
I did micro git-blame research here. I spotted only one related change
[0]. Looks like before this change pin was indeed needed.
But not after this change, so this visibilitymap_pin is just an oversight?
Related thread is [1]. I quickly checked the discussion in this
thread, and it looks like no one was bothered about these lines or VM
logging changes (in this exact pin buffer aspect). The discussion was
of other aspects of this commit.Wow, thanks so much for doing that research. Looking at it myself, it
does indeed seem like just an oversight. It isn't harmful since it
won't take another pin, but it is confusing, so I think we should at
least remove it in master. I'm not as sure about back branches.I've updated the commit message in the patch set to reflect the
research you did in attached v8.- Melanie
Hi!
small comments regarding new series
0001, 0002, 0017 LGTM
In 0015:
```
reshke@yezzey-cbdb-bench:~/postgres$ git diff
src/backend/access/heap/pruneheap.c
diff --git a/src/backend/access/heap/pruneheap.c
b/src/backend/access/heap/pruneheap.c
index 05b51bd8d25..0794af9ae89 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -1398,7 +1398,7 @@ heap_prune_record_unchanged_lp_normal(Page page,
PruneState *prstate, OffsetNumb
/*
* For now always use prstate->cutoffs
for this test, because
* we only update 'all_visible' when
freezing is requested. We
- * could use
GlobalVisTestIsRemovableXid instead, if a
+ * could use GlobalVisXidVisibleToAll
instead, if a
* non-freezing caller wanted to set the VM bit.
*/
Assert(prstate->cutoffs);
```
Also, maybe GlobalVisXidTestAllVisible is a slightly better name? (The
term 'all-visible' is one that we occasionally utilize)
--
Best regards,
Kirill Reshke