On Mon, Jun 30, 2025 at 9:41 AM Masahiko Sawada <sawada.m...@gmail.com> wrote:
>
> I've rebased the patches to the current HEAD.

Hi Sawada-san,

I've started reviewing this. I initially meant to review the eager
scan part, but I think it would be easier to do that if it were in a
separate patch on top of the rest of the parallel heap vacuuming
patches -- just during review. I had a hard time telling which parts
of the design (and code) were necessitated by the eager scanning
feature.

I started reviewing the rest of the code and this is some initial
feedback on it:

LV
---
I don't think we should use "LV" and "lazy vacuum" anymore. Let's call
it what it is: "heap vacuuming". We don't need to rename vacuumlazy.c
right now, but if you are making new structs or substantially altering
old ones, I wouldn't use "LV".

table AM callbacks
-------------------------
I think there is a fundamental tension here related to whether or not
vacuumparallel.c should be table-AM agnostic. All of its code is
invoked below heap_vacuum_rel(). I would argue that vacuumparallel.c
is index AM-agnostic but heap-specific.

I think this is what makes your table AM callbacks feel odd. Other
table AM callbacks are invoked from general code -- for example from
executor code that is agnostic to the underlying table AMs. But, all
of the functions you added and the associated callbacks are invoked
below heap_vacuum_rel() (as is all of vacuumparallel.c).

duplicated information across structs
-----------------------------------------------
I think a useful preliminary patch to this set would be to refactor
LVRelState into multiple structs that each represent the static input,
working state, and output of vacuum. You start to do this in one of
your patches with LVScanData but it doesn't go far enough.

In fact, I think there are members of all the parallel data structures
you introduced and of those that are already present that overlap with
members of the LVRelState and we could start putting these smaller
structs. For my examples, I am referring both to existing code and
code you added.

For example, relnamespace, relname, and indname in LVRelState and
ParallelVacuumState and ParallelVacuumState->heaprel and
LVRelState->rel. Seems like you could have a smaller struct that is
accessible to the vacuum error callback and also to the users in
LVRelState.

(Side note: In your patch, I find it confusing that these members of
LVRelState are initialized in
heap_parallel_vacuum_collect_dead_items() instead of
heap_parallel_vacuum_initialize_worker().)

And even for cases where the information has to be passed from the
leader to the worker, there is no reason you can't have the same
struct but in shared memory for the parallel case and local memory for
the serial case. For example with the struct members "aggressive",
"skipwithvm", and "cutoffs" in LVRelState and ParallelLVShared. Or
ParallelLVScanDesc->nblocks and LVRelState->rel_pages.

Ideally the entire part of the LVRelState that is an unchanging
input/reference data is in a struct which is in local memory for the
serial and local parallel case and a single read-only location in the
shared parallel case. Or, for the shared case, if there is a reason
not to read from the shared memory, we copy them over to a local
instance of the same struct. Maybe it is called HeapVacInput or
similar.

There are a bunch of other instances like this. For example, the
leader initializes LVScanData->NewRelfrozenXid from
vacrel->cutoffs.OldestXmin, then uses this to set
ParallelLVShared->NewRelfrozenXid. Then the worker copies it from
ParallelLVShared->NewRelfrozenXid back to LVScanData->NewRelfrozenXid.
But the worker has access to ParallelLVShared->cutoffs. So you could
either get it from there or allow the workers to access the first
LVScanData and have that always belong to the leader. Either way, I
don't think you should need NewRelfrozenXid in ParallelLVShared.

Another related concern: I don't understand why
ParallelLVScanWorkerData->nallocated has to be in shared memory. I get
that each worker has to keep track for itself how many blocks it has
"processed" so far (either skipped or scanned) and I get that there
has to be some coordination variable that keeps track of whether or
not all blocks have been processed and where the next chunk is. But
why does the worker's nallocated have to be in shared memory? It
doesn't seem like the leader accesses it.

(Side note: I'm rather confused by why ParallelLVScanDesc is its own
thing [instead of part of ParallelLVShared] -- not to mention its
chunk_size member appears to be unused.)

Separately, I think the fact that PVShared and ParallelVacuumState
stayed almost untouched (and continue to have general,
parallel-sounding names) with your patch but now mostly deal with
stuff about indexes while most other parallel vacuuming content is in
other structs is confusing. I think we need to consider which members
in PVShared and ParallelVacuumState are phase II only and put that in
an appropriately named structure or get more of the heap vacuuming
specific members in those generally named structures.

- Melanie


Reply via email to