Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
Diagnostics:
MissingIncludes: None
Comment on lines +1 to +2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This .clangd file is a personal editor/LSP configuration and should not be committed to the tree. The repository's own .gitignore (lines 1-3) explicitly states the project convention: "Auxiliary files from local workflows, your preferred editor, etc. should be ignored locally using $GIT_DIR/info/exclude or ~/.gitexclude." A patch destined for pgsql-hackers/commitfest must not add personal tooling config; committing it violates the minimal-diff rule and would be an immediate rejection reason. Keep this file local (via $GIT_DIR/info/exclude) rather than adding it to the repo.

Comment on lines +1 to +2

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This .clangd file is a personal editor/tooling configuration and must not be part of a PostgreSQL patch destined for pgsql-hackers. PostgreSQL's tree deliberately keeps editor/IDE-specific config out of the repository (there is no .clangd, .vscode, etc.). Committing it is an unrelated change that violates the minimal-diff rule and will draw an immediate rejection. Such settings belong in your local ignored files (e.g. via a personal global gitignore or a local, uncommitted file), not in the shared tree. Note that several sibling files in this change (.gdbinit, pg-aliases.sh, src/test/benchmarks/siu/scripts/*) appear to be the same class of personal-workflow artifacts and should be dropped from the patch as well.

Beyond not belonging in the tree, the contents are also non-portable and stale: the hardcoded relative include path -I../../../../src/include only resolves from a specific subdirectory depth and is meaningless from the repo root; -DPGDLLIMPORT= defeats the Windows/MSVC PGDLLIMPORT checks; and the flag list will silently diverge from the real build flags maintained by the build system (make/meson).

InlayHints:
Enabled: true
ParameterNames: true
DeducedTypes: true
CompileFlags:
CompilationDatabase: build/ # Search build/ directory for compile_commands.json
Remove: [ -Werror ]
Add:
- -DDEBUG
- -DLOCAL
- -DPGDLLIMPORT=
- -DPIC
- -O2
- -Wall
- -Wcast-function-type
- -Wconversion
- -Wdeclaration-after-statement
- -Wendif-labels
- -Werror=vla
- -Wextra
- -Wfloat-equal
- -Wformat-security
- -Wimplicit-fallthrough=3
- -Wmissing-format-attribute
- -Wmissing-prototypes
- -Wno-format-truncation
- -Wno-sign-conversion
- -Wno-stringop-truncation
- -Wno-unused-const-variable
- -Wpointer-arith
- -Wshadow
- -Wshadow=compatible-local
- -fPIC
- -fexcess-precision=standard
- -fno-strict-aliasing
- -fvisibility=hidden
- -fwrapv
- -g
- -std=c11
- -I.
- -I../../../../src/include
156 changes: 156 additions & 0 deletions .gdbinit
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# HOT Indexed Updates — GDB breakpoints for code review

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This .gdbinit is a personal debugging artifact and does not belong in a PostgreSQL patch. It violates the minimal-diff / one-logical-change discipline (an automatic rejection reason on pgsql-hackers) and is also a security footgun: GDB auto-loads a .gdbinit from the current working directory, so anyone running gdb inside the checkout silently executes these commands (auto-load-safe-path concerns). Remove this file from the patch; if such tooling is wanted, keep it in a personal dotfile outside the repo root.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This entire file is a personal developer debugging artifact and must not be committed to a PostgreSQL patch destined for pgsql-hackers/commitfest. It is unrelated to the feature implementation and violates the 'minimal diff' discipline -- one of the top rejection reasons on -hackers. It is added alongside other stray developer-only files in this same change (.clangd, pg-aliases.sh, src/test/benchmarks/siu/scripts/build.sh), which reinforces that these all belong in a local/ignored setup, not in the tree. Remove .gdbinit from the patch entirely.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This .gdbinit is a personal developer debugging artifact and must not be part of a PostgreSQL patch destined for pgsql-hackers/commitfest. It is unrelated to the HOT indexed updates feature and violates the minimal-diff discipline (a change should not carry per-developer debug scaffolding). The repo's own .github/docs/pristine-master-policy.md already lists .gdbinit as debugger configuration that should stay out of the tracked tree. There is also a footgun: GDB auto-sources a .gdbinit from the current working directory, so anyone running gdb in the repo root would silently execute these commands. Drop this file from the patch. Note the same scope-creep applies to the bundled .clangd, pg-aliases.sh, and src/test/benchmarks/siu/scripts/* dev tooling, which should likewise be excluded. Confidence: high.

#
# Usage: gdb -x .gdbinit <postgres-binary>
# Or from gdb: source .gdbinit
#
# These breakpoints cover the major code paths introduced or modified by
# the HOT indexed updates patch series. They are organized by subsystem
# to make it easy to enable/disable groups during debugging.
#
# Tip: to skip to a specific subsystem, disable all then enable selectively:
# disable breakpoints
# enable 1 2 3 # just the update-decision group

# =========================================================================
# 1. UPDATE DECISION — heap_update() HOT/HOT-indexed/non-HOT choice

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-ASCII em-dash characters (U+2014) appear in the comment headers throughout this file (lines 1, 15, 38, 52, 81, 98, 106, 117, 140, 148). PostgreSQL requires ASCII-only content in source and diffs; em-dashes, smart quotes and ellipses are disallowed. If any of this were retained, replace the em-dashes with a plain ASCII hyphen/dash. Confidence: high.

# src/backend/access/heap/heapam.c

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-ASCII punctuation (em-dash U+2014) in comment text. PostgreSQL source and diffs must be ASCII-only; em/en-dashes and smart quotes are not permitted. This occurs throughout the header comments (lines 1, 15, 38, 52, 81, 98, 106, 117, 140, 148). Even setting aside that the file should be removed, this would fail the ASCII-only rule if reused in real source.

# =========================================================================

# Main entry: heap_update
break heapam.c:3210

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breakpoints pinned to hard-coded line numbers in files modified by this very patch series (heapam.c:3210/4019/4024/4033/4101/4147, heapam_indexscan.c:182/250/297, indexam.c:299, execIndexing.c:370, pruneheap.c:1287/1802/1836/1863/2936) are extremely fragile. Any further edit shifts every subsequent line, so the breakpoints silently land on the wrong statements and the annotations (e.g. 'Line 4019: pure HOT', 'Line 4024: HOT indexed path') cannot be trusted. Another reason this file should not be tracked in the repo.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breakpoints set on hard-coded absolute source line numbers are extremely brittle: any edit to these files (or a rebase) immediately makes them point at the wrong statement, silently misleading anyone who sources the file. The accompanying comments (e.g. 'Line 4019: pure HOT') hardcode line placement that will drift. This applies to every file:NNN breakpoint in the file (heapam.c:3210/4019/4024/4033/4101/4147, heapam_indexscan.c:182/250/297, indexam.c:299, execIndexing.c:370, pruneheap.c:1802/1836/1863/1287/2936). Another reason to drop the file entirely.


# HOT decision block: pure HOT vs HOT indexed vs non-HOT
# Line 4019: pure HOT (no indexed columns changed)
# Line 4024: HOT indexed path (non-catalog, some indexed columns changed)
# Line 4031: predict augmented tuple size
# Line 4033: size+space check before creating augmented tuple
break heapam.c:4019
break heapam.c:4024
break heapam.c:4033
Comment on lines +27 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded file:line breakpoints (heapam.c:3210/4019/4024/4033/4101/4147, heapam_indexscan.c:182/250/297, indexam.c:299, execIndexing.c:370, pruneheap.c:1287/1802/1836/1863/2936) are inherently fragile: they silently drift to the wrong statement whenever the source is edited — including by this very patch series. The accompanying comments assert exact per-line semantics (e.g. "Line 4019: pure HOT", "Line 4147: Restore HEAD_INDEXED_UPDATED on old tuple"), which become stale and misleading the moment the code moves. If any GDB helper were kept at all, it should use function-name breakpoints only. Confidence: high.


# Set HEAP_INDEXED_UPDATED flag on new tuple before page insertion
break heapam.c:4101

# Restore HEAP_INDEXED_UPDATED on old tuple (only if it previously had it)
break heapam.c:4147

# =========================================================================
# 2. TUPLE CREATION — building the augmented tuple with embedded bitmap
# src/backend/access/heap/heapam.c
# =========================================================================

# Predict augmented tuple size (returns 0 if t_hoff would overflow)
break heap_hot_indexed_tuple_size

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These function-name breakpoints reference symbols that do not exist in the patched source. The tuple-building code is heap_form_hot_indexed_tuple (heapam.c:4924), not heap_hot_indexed_create_tuple; and there is no heap_hot_indexed_tuple_size/heap_hot_indexed_serialize_bitmap (verified via search — no matches). GDB will fail to bind these breakpoints and the descriptive comments are inaccurate. Only heap_hot_search_buffer and the HEAP_INDEXED_UPDATED flag were confirmed to exist.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function-name breakpoint on a symbol that does not exist. A codebase search finds no heap_hot_indexed_* symbols anywhere in src/backend/access/heap/*.c or src/include/access/*.h. GDB will silently create unresolved/pending breakpoints for all of these (heap_hot_indexed_tuple_size, heap_hot_indexed_create_tuple, heap_hot_indexed_serialize_bitmap, heap_hot_indexed_bitmap_raw_size, heap_hot_indexed_has_bitmap_space, heap_hot_indexed_read_bitmap, heap_hot_indexed_bitmap_overlaps_raw, heap_hot_indexed_bitmap_or_raw, heap_hot_indexed_accum_overlaps, heap_hot_indexed_merge_bitmaps_raw, heap_hot_indexed_deserialize_bitmap), giving a false impression of coverage. This is a symptom of tracking debug scaffolding in the repo rather than keeping it local.


# Create augmented tuple with embedded modified-column bitmap
break heap_hot_indexed_create_tuple

# Serialize Bitmapset into raw bytes in tuple header
break heap_hot_indexed_serialize_bitmap

# =========================================================================
# 3. BITMAP UTILITIES — raw bitmap operations for chain following
# src/backend/access/heap/heapam.c
# =========================================================================

# Compute raw bitmap byte size from natts
break heap_hot_indexed_bitmap_raw_size

# Check if tuple header has room for bitmap between null bitmap and data
break heap_hot_indexed_has_bitmap_space

# Read HOT indexed bitmap from tuple header (returns Bitmapset)
break heap_hot_indexed_read_bitmap

# Fast overlap check: does tuple's raw bitmap overlap with indexed_attrs?
break heap_hot_indexed_bitmap_overlaps_raw

# OR a tuple's raw bitmap into an accumulator buffer
break heap_hot_indexed_bitmap_or_raw

# Check if accumulated raw bitmap overlaps with indexed_attrs
break heap_hot_indexed_accum_overlaps

# Merge bitmaps from dead tuples into a target tuple on the page
break heap_hot_indexed_merge_bitmaps_raw

# Deserialize raw bytes back to Bitmapset
break heap_hot_indexed_deserialize_bitmap

# =========================================================================
# 4. INDEX SCAN — HOT chain following with stale-entry detection
# src/backend/access/heap/heapam_indexscan.c
# =========================================================================

# Main HOT chain search with indexed update awareness
break heap_hot_search_buffer

# Redirect-with-data: initialize bitmap accumulator from collapsed redirect
break heapam_indexscan.c:182

# Accumulate bitmap from INDEXED_UPDATED tuple in chain
break heapam_indexscan.c:250

# Stale entry detection: accumulated bitmap overlaps this index's attrs
break heapam_indexscan.c:297

# =========================================================================
# 5. INDEX SCAN SETUP — indexed_attrs bitmap computation
# src/backend/access/index/indexam.c
# =========================================================================

# Compute indexed_attrs for HOT indexed update chain following
break indexam.c:299

# =========================================================================
# 6. INDEX INSERTION — skip unchanged indexes for HOT indexed updates
# src/backend/executor/execIndexing.c
# =========================================================================

# Entry: insert/update index tuples
break ExecInsertIndexTuples

# Index skip decision: skip indexes whose attrs don't overlap modified set
break execIndexing.c:370

# =========================================================================
# 7. PRUNING — chain collapsing and redirect-with-data
# src/backend/access/heap/pruneheap.c
# =========================================================================

# Main prune function
break heap_page_prune_and_freeze

# Per-chain pruning entry
break heap_prune_chain

# Chain collapsing: collect bitmaps from dead INDEXED_UPDATED intermediates
break pruneheap.c:1802

# OR dead tuple bitmaps into combined bitmap
break pruneheap.c:1836

# Record redirect-with-data for execute phase
break pruneheap.c:1863

# Execute phase: apply redirect-with-data entries on the page
break pruneheap.c:1287

# =========================================================================
# 8. WAL REPLAY — recovery of HOT indexed updates
# src/backend/access/heap/heapam_xlog.c
# =========================================================================

# WAL replay for XLOG_HEAP2_INDEXED_UPDATE
break heap_xlog_indexed_update

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no heap_xlog_indexed_update function in the patch. WAL replay for HOT indexed updates is handled inside heap_xlog_update(record, true) (heapam_xlog.c:734, dispatched at line 1300); there is no separate XLOG_HEAP2_INDEXED_UPDATE handler. This breakpoint will never bind and the comment is inaccurate.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function-name breakpoint on a nonexistent symbol: a codebase search finds no heap_xlog_indexed_update function (there is no XLOG_HEAP2_INDEXED_UPDATE replay handler by that name). This breakpoint will silently remain unresolved.


# =========================================================================
# 9. WAL LOGGING — writing HOT indexed update records
# src/backend/access/heap/heapam.c
# =========================================================================

# WAL logging for heap updates (handles indexed_update flag)
break log_heap_update

# Serialize redirect-with-data into WAL record (pruneheap.c)
break pruneheap.c:2936
40 changes: 40 additions & 0 deletions contrib/amcheck/expected/check_heap.out
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,46 @@ SELECT * FROM verify_heapam('test_foreign_table',
endblock := NULL);
ERROR: cannot check relation "test_foreign_table"
DETAIL: This operation is not supported for foreign tables.
-- HOT-indexed (HOT/SIU) on-page artifacts:
--
-- A HOT-indexed UPDATE keeps the new tuple on the same page as a heap-only
-- tuple marked HEAP_INDEXED_UPDATED and plants index entries pointing at its
-- own TID. Pruning a chain of such updates collapses dead members to
-- LP_REDIRECT forwarders and preserves the LP of a live HOT-indexed member
-- whose index entries may not yet be swept. verify_heapam must treat all of
-- these as legitimate. This scenario exercises them and asserts that
-- verify_heapam reports zero corruption against legitimate HOT-indexed
-- activity.
CREATE TABLE hot_indexed_check (id int PRIMARY KEY, c1 int, c2 int, c3 int)
WITH (fillfactor = 70);
CREATE INDEX hot_indexed_check_c1 ON hot_indexed_check (c1);
CREATE INDEX hot_indexed_check_c2 ON hot_indexed_check (c2);
INSERT INTO hot_indexed_check
SELECT g, g, g, g FROM generate_series(1, 200) g;
-- Single-step UPDATEs: each row gets one HOT-indexed update. Each
-- successful HOT-indexed update keeps its new tuple on-page and inserts an
-- entry only into the index whose attribute changed.
UPDATE hot_indexed_check SET c1 = c1 + 1000;
-- Multi-step UPDATEs: drive several successive HOT-indexed updates against
-- the same rows so prune sees a chain of dead intermediates and collapses
-- them to LP_REDIRECT forwarders. An explicit VACUUM runs the prune path
-- and exercises chain collapse.
UPDATE hot_indexed_check SET c2 = c2 + 1 WHERE id <= 50;
UPDATE hot_indexed_check SET c2 = c2 + 1 WHERE id <= 50;
UPDATE hot_indexed_check SET c2 = c2 + 1 WHERE id <= 50;
VACUUM (INDEX_CLEANUP off) hot_indexed_check;
-- verify_heapam must not report any corruption against legitimate HOT-
-- indexed artifacts. Selecting the corrupting message makes any
-- regression unmistakable in the regress diff.
SELECT blkno, offnum, attnum, msg
FROM verify_heapam('hot_indexed_check',
startblock := NULL,
endblock := NULL);
blkno | offnum | attnum | msg
-------+--------+--------+-----
(0 rows)

DROP TABLE hot_indexed_check;
-- cleanup
DROP TABLE heaptest;
DROP TABLESPACE regress_test_stats_tblspc;
Expand Down
37 changes: 37 additions & 0 deletions contrib/amcheck/sql/check_heap.sql
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,43 @@ SELECT * FROM verify_heapam('test_foreign_table',
startblock := NULL,
endblock := NULL);

-- HOT-indexed (HOT/SIU) on-page artifacts:
--
-- A HOT-indexed UPDATE keeps the new tuple on the same page as a heap-only
-- tuple marked HEAP_INDEXED_UPDATED and plants index entries pointing at its
-- own TID. Pruning a chain of such updates collapses dead members to
-- LP_REDIRECT forwarders and preserves the LP of a live HOT-indexed member
Comment on lines +144 to +146

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment-accuracy (low confidence): the comment says collapse "collapses dead members to LP_REDIRECT forwarders" and "preserves the LP of a live HOT-indexed member". The actual mechanism (hot_indexed.h L129-161, pruneheap.c heap_prune_record_stub) preserves dead entry-bearing members as xid-free LP_NORMAL stubs, not LP_REDIRECTs, and the root (not a live member) is redirected. Aligning the comment with the stub terminology used elsewhere would avoid confusion about what the test asserts.

Comment on lines +144 to +146

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prose states these outcomes as guaranteed facts ("collapses dead members to LP_REDIRECT forwarders and preserves the LP of a live HOT-indexed member"), but nothing in the scenario guarantees or verifies them: with 4 int columns at fillfactor 70 and 200 rows there is no assertion that every update stayed on-page as an SIU update, nor that pruning produced stubs vs redirects. Either verify the artifacts (see the verify_heapam comment) or soften these definite claims so the comment describes what the test actually checks.

-- whose index entries may not yet be swept. verify_heapam must treat all of
Comment on lines +145 to +147

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment misdescribes the artifact this test is meant to protect. Per pruneheap.c (heap_prune_chain collapse path), a dead HOT-indexed key member that still has a live index entry is preserved as an xid-free stub (an LP_NORMAL forwarding item carrying the segment's modified-attrs bitmap), not as an "LP_REDIRECT forwarder"; only the chain root is turned into an LP_REDIRECT. Stub handling (HotIndexedHeaderIsStub) is precisely the new logic added to verify_heapam.c that this test is supposed to cover, so conflating stubs with LP_REDIRECT undersells and misstates the scenario. Suggest rewording to describe stubs (and separately the root redirect).

Confidence: high.

Suggested change
-- own TID. Pruning a chain of such updates collapses dead members to
-- LP_REDIRECT forwarders and preserves the LP of a live HOT-indexed member
-- whose index entries may not yet be swept. verify_heapam must treat all of
-- own TID. Pruning a chain of such updates preserves each dead HOT-indexed
-- key member (whose index entries may not yet be swept) as an xid-free stub
-- forwarding to the next survivor, and redirects the chain root (LP_REDIRECT)
-- to the first survivor. verify_heapam must treat all of

-- these as legitimate. This scenario exercises them and asserts that
-- verify_heapam reports zero corruption against legitimate HOT-indexed
-- activity.
CREATE TABLE hot_indexed_check (id int PRIMARY KEY, c1 int, c2 int, c3 int)
WITH (fillfactor = 70);
Comment on lines +151 to +152

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fillfactor = 70 is a weak guarantee for keeping updated tuples on the same page: it leaves relatively little free space, and after three successive c2 updates the block may fill and force off-page (non-HOT) updates, defeating the scenario. The companion feature tests (contrib/pageinspect/sql/hot_indexed_updates.sql) deliberately use fillfactor = 50/40/10 for exactly this reason. Also, without autovacuum_enabled = false an autovacuum can prune/collapse between statements, making the intended chain-of-dead-intermediates state non-deterministic. Recommend a roomier fillfactor and autovacuum_enabled = false to make the exercised state reliable. [moderate confidence]

CREATE INDEX hot_indexed_check_c1 ON hot_indexed_check (c1);
CREATE INDEX hot_indexed_check_c2 ON hot_indexed_check (c2);
INSERT INTO hot_indexed_check
SELECT g, g, g, g FROM generate_series(1, 200) g;
-- Single-step UPDATEs: each row gets one HOT-indexed update. Each
-- successful HOT-indexed update keeps its new tuple on-page and inserts an
-- entry only into the index whose attribute changed.
UPDATE hot_indexed_check SET c1 = c1 + 1000;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a pure negative test: it only asserts verify_heapam returns zero rows. If the SIU/HOT-indexed path silently fails to fire here (e.g. an update falls back to non-HOT because the new version does not fit on-page, or HeapUpdateHotAllowable does not promote as expected), the test still passes with (0 rows) while exercising nothing -- a silently-passing test. Since pg_stat_get_tuples_hot_indexed_updated() exists (used by the new pageinspect tests), consider asserting that counter is non-zero after these UPDATEs so a regression that disables the SIU path is caught here rather than passing vacuously. Confidence: low.

-- Multi-step UPDATEs: drive several successive HOT-indexed updates against
-- the same rows so prune sees a chain of dead intermediates and collapses
-- them to LP_REDIRECT forwarders. An explicit VACUUM runs the prune path
-- and exercises chain collapse.
Comment on lines +163 to +164

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment overstates coverage as a guarantee. The feature's own tests document that VACUUM collapse to LP_REDIRECT and stub creation are gated on the global xmin horizon, which a concurrent snapshot in the running regression cluster can pin back indefinitely (see contrib/pageinspect/sql/hot_indexed_updates.sql test 12: "whether the chain has collapsed to an LP_REDIRECT is not deterministic here"). So this VACUUM does not reliably "collapse them to LP_REDIRECT forwarders" or "exercise chain collapse" on any given run; the new verify_heapam stub / multi-predecessor / redirect paths this test is meant to guard may not be reached. The test still correctly passes (verify_heapam reports 0 rows regardless of layout), but the comment asserts horizon-dependent behavior as fact. Reword to reflect that collapse is best-effort here, and note that deterministic collapse coverage lives in the isolation spec. Confidence: high.

Comment on lines +163 to +164

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment asserts chain collapse as a certainty ("collapses them to LP_REDIRECT forwarders", "exercises chain collapse"), but prune/collapse of HOT-indexed chains is gated on the superseded versions falling below the global xmin horizon. A concurrent snapshot elsewhere in the running regression cluster can pin that horizon and prevent collapse entirely -- this is exactly the reason the feature's own pageinspect test (hot_indexed_updates.sql, section 12) deliberately refuses to assert any post-VACUUM collapse state. This test's assertion (verify_heapam returns 0 rows) is horizon-independent and holds whether or not collapse actually happens, so it is safe; but the comment overstates what the test guarantees and can mislead a future reader into thinking collapse is verified here. Suggest softening the wording to "may collapse" / "drives the prune path (collapse is horizon-gated and not asserted here)" so the comment matches what the code actually checks. (moderate confidence)

UPDATE hot_indexed_check SET c2 = c2 + 1 WHERE id <= 50;
UPDATE hot_indexed_check SET c2 = c2 + 1 WHERE id <= 50;
UPDATE hot_indexed_check SET c2 = c2 + 1 WHERE id <= 50;
Comment on lines +165 to +167

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test-coverage gap (moderate confidence): all three multi-step UPDATEs modify the same indexed column (c2). During chain collapse (pruneheap.c ~L1971-2000), each dead member's modified-attrs bitmap is {c2}, and laterattrs (union of later hops) also contains {c2}, so HotIndexedBitmapIsSubset() is true for every dead member -- they are all reclaimed via heap_prune_record_dead_or_unused (LP_DEAD). heap_prune_record_stub() is never reached, so the collapse-survivor stub (the LP_NORMAL non-tuple that is the principal new artifact verify_heapam had to be taught to tolerate: see verify_heapam.c L623-779) is never produced. The comment claims to exercise the preserved live-member/collapse path, but this scenario does not create a stub. To actually cover it, drive updates against different indexed columns across hops (e.g. update c1, then c2, then c1) so a dead member changed an attribute not superseded by a later hop, forcing a stub.

VACUUM (INDEX_CLEANUP off) hot_indexed_check;
Comment on lines +167 to +168

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This positive test can silently become a no-op and give false confidence. Whether the intended artifacts (LP_REDIRECT forwarders, collapse-survivor stubs, HEAP_INDEXED_UPDATED members) actually get produced depends on prune/collapse running, which in turn depends on tuple visibility and page fill. With 200 tiny 4-int rows the initial data spans only ~1-2 pages, and a concurrent snapshot (e.g. autovacuum, or the VACUUM's own horizon) can leave the dead intermediates unprunable so no chain collapse occurs. Because the test only asserts (0 rows), it passes identically whether or not the new code paths were exercised — so it cannot detect a regression that stops producing these on-page structures. Consider asserting the artifacts are actually present (e.g. via pageinspect heap_page_items to show LP_REDIRECT / stub items) before running verify_heapam, so the coverage is verifiable rather than assumed. (moderate confidence)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment asserts VACUUM "collapses dead members to LP_REDIRECT forwarders", but VACUUM here runs against a heap whose only preceding UPDATE traffic is from earlier statements in the same session's autocommit transactions. Whether the three c2 UPDATEs on id<=50 actually build a dead intermediate chain that prune collapses depends on visibility (OldestXmin) at VACUUM time and on all three prior UPDATEs being HOT-indexed on-page. Nothing here verifies a redirect/stub was actually created, so the strong claim in the comment ("exercises chain collapse") is unverifiable from the test and may be aspirational. Either verify the redirect/stub count (pg_relation_hot_indexed_stats n_redirects/n_chains) or soften the comment to describe intent rather than asserting the outcome. (moderate confidence)

-- verify_heapam must not report any corruption against legitimate HOT-
-- indexed artifacts. Selecting the corrupting message makes any
-- regression unmistakable in the regress diff.
SELECT blkno, offnum, attnum, msg
FROM verify_heapam('hot_indexed_check',
startblock := NULL,
endblock := NULL);
Comment on lines +172 to +175

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test cannot detect whether the HOT-indexed artifacts it describes (HEAP_INDEXED_UPDATED tuples, LP_REDIRECT forwarders, collapse stubs) were actually produced. The only assertion is verify_heapam returning zero rows, which will also pass if the UPDATEs silently fell back to non-HOT updates (e.g., tuples spilling to a new page, or the feature being disabled). It therefore risks becoming a false-negative test that passes without exercising the intended prune/collapse paths. Consider adding a positive check (e.g., via pageinspect's heap_page_items to confirm the presence of LP_REDIRECT/stub items and HEAP_INDEXED_UPDATED tuples) so a regression that stops generating these artifacts is caught rather than silently masked.

Comment on lines +172 to +175

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a purely negative assertion (expected output is (0 rows)). If VACUUM does not actually collapse the chains into LP_REDIRECT forwarders / collapse-survivor stubs — e.g. because OldestXmin has not advanced past the intermediate tuples, or a concurrent snapshot holds them back — verify_heapam never reaches the new stub-forward-link and multi-predecessor-redirect branches in verify_heapam.c, yet the test still prints (0 rows) and passes. The test therefore cannot detect whether it exercised the code it claims to cover, so it can silently rot into providing no coverage. Consider asserting that the intended on-page artifacts actually exist (e.g. via pageinspect heap_page_items showing LP_REDIRECT / stub items after the VACUUM) so a regression that stops producing those artifacts is caught. Confidence: moderate.

Comment on lines +172 to +175

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This adds only a positive smoke test. The corresponding verify_heapam.c change introduces several new corruption-detection branches that get zero coverage here: the stub forward-link range check ("HOT-indexed stub forward link ... out of range or self-referential"), the duplicate-redirect check ("redirect line pointer points to offset ... but offset ... also points there"), and the stub-forwards-to-non-heap-only check. A change that guarantees verify_heapam does not false-positive on legitimate HOT-indexed layout does not prove it still detects real corruption in these paths. amcheck convention (see the rest of this suite / verify_nbtree tests) is to also exercise the detection paths against deliberately-corrupted pages. Please add negative coverage for the new report_corruption branches. (moderate confidence)

Comment on lines +172 to +175

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only asserts "zero corruption rows", so it passes trivially even if none of the HOT-indexed artifacts the comment describes (HEAP_INDEXED_UPDATED on-page tuples, LP_REDIRECT forwarders, collapse-survivor stubs) are actually produced — e.g. if the update ever falls back to a non-HOT path or the SIU path is disabled. As written it cannot detect a regression where the feature silently stops taking the HOT-indexed path; it would still report 0 rows. Since the comment claims the scenario "exercises" and "asserts" these artifacts, consider adding a positive assertion that they exist (e.g. via pageinspect heap_page_items checking for LP_REDIRECT/stub items, or pg_relation_hot_indexed_stats) before running verify_heapam, so the test fails loudly if the artifacts are absent. Confidence: moderate.

Comment on lines +172 to +175

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test only asserts the absence of corruption (0 rows), but never confirms the HOT-indexed artifacts it claims to exercise were actually produced. If a future change disables the SIU path, or the page geometry causes these UPDATEs to go non-HOT (fillfactor 70 with 200 rows leaves little free space; once a page fills, subsequent updates on that page migrate off-page and are ordinary non-HOT/HOT updates, not HOT-indexed), this test still passes with 0 rows while exercising none of the LP_REDIRECT / stub / HEAP_INDEXED_UPDATED handling the comment describes. That is a silently-vacuous test: it cannot distinguish "verify_heapam handled the artifacts correctly" from "no artifacts were created". Add a positive precondition assertion (e.g. pg_stat_get_tuples_hot_indexed_updated() > 0, or pg_relation_hot_indexed_stats() showing n_hot_indexed / n_chains > 0 after the VACUUM) so the test fails loudly if the SIU path stops firing. (moderate confidence)

Comment on lines +172 to +175

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The test asserts only (0 rows) from verify_heapam, so it passes identically whether or not any UPDATE actually took the HOT-indexed path, kept its tuple on-page, or produced an LP_REDIRECT chain. Nothing here confirms the premise stated in the comments ("keeps the new tuple on the same page", "collapses dead members to LP_REDIRECT forwarders"). If a future change silently disabled the HOT-indexed path, or if these updates fall back to non-HOT/off-page updates (see the fillfactor note), this test would still pass green while exercising none of the artifacts it claims to. Consider asserting the on-page/redirect state (e.g. via pageinspect heap_page_items, as the dedicated hot_indexed_updates.sql tests do) so the test actually pins down what it documents. [moderate confidence]

Comment on lines +172 to +175

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test asserts only "0 corruption rows" but never confirms the HOT-indexed artifacts it claims to exercise (HEAP_INDEXED_UPDATED live tuples, collapse stubs, LP_REDIRECT forwarders) were actually produced. This is a green-but-useless hazard: if the SIU path stops emitting these structures (a regression, or a page-fit difference on some platform/BLCKSZ so the update falls back to non-HOT), the test still passes with 0 rows while covering none of the new verify_heapam branches. The sibling pageinspect tests anchor the artifacts explicitly (pg_stat_get_tuples_hot_indexed_updated, heap_page_items). Anchor them here too before the verify_heapam call, e.g. assert that hot-indexed updates occurred and that at least one on-page redirect/stub exists, so the test fails loudly if the artifacts vanish.

Comment on lines +172 to +175

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continuation-line indentation here is inconsistent with the seven identical verify_heapam(...) calls already in this file (lines 96-139), which align the startblock/endblock arguments with pure tab indentation. These new lines use tabs followed by spaces to align under the opening paren of verify_heapam(, mixing tabs and spaces. For a minimal, consistent diff, match the existing style in this file.

Confidence: high (verified against the other calls in the same file).

Suggested change
SELECT blkno, offnum, attnum, msg
FROM verify_heapam('hot_indexed_check',
startblock := NULL,
endblock := NULL);
SELECT blkno, offnum, attnum, msg
FROM verify_heapam('hot_indexed_check',
startblock := NULL,
endblock := NULL);

DROP TABLE hot_indexed_check;

-- cleanup
DROP TABLE heaptest;
DROP TABLESPACE regress_test_stats_tblspc;
Expand Down
Loading