Hi, ginVacuumPostingTreeLeaves() sweeps the leaf pages of a GIN posting tree via their rightlinks, but that loop has no vacuum_delay_point() (and hence no CHECK_FOR_INTERRUPTS()), unlike the sibling loops in ginbulkdelete() and ginvacuumcleanup().
The call used to be there: it was dropped when fd83c83d094 rewrote this cleanup from a recursive walk into the current iterative sweep, and never re-added to the new loop. That commit was only about fixing a deadlock, so the removal looks inadvertent. A posting tree holds all TIDs for a single key, so for a common key it can span many leaf pages. While one such tree is being vacuumed, the operation now ignores vacuum_cost_delay and cannot be interrupted (query cancel, statement_timeout, or autovacuum cancellation when another backend wants a conflicting lock) until the whole sweep finishes; a corrupted rightlink forming a cycle turns it into an uninterruptible hang. The attached patch restores the call, placed where the buffer has already been released so no content lock is held across the delay. It is against master; on branches before 18 vacuum_delay_point() takes no argument (the is_analyze parameter was added by e5b0b0ce150), so the back-branch version is just "vacuum_delay_point();". I left the initial leftmost-descent loop in the same function as-is, since it mirrors the entry-tree descent in ginbulkdelete(), which likewise has no delay point. Thanks, Paul
From d9b5ddb3cb0cfb55d418a09f571a78ee069eb3e1 Mon Sep 17 00:00:00 2001 From: Paul Kim <[email protected]> Date: Sun, 19 Jul 2026 23:08:12 +0900 Subject: [PATCH v1] Restore vacuum_delay_point() in GIN posting-tree leaf vacuum Commit fd83c83d094 turned the recursive posting-tree cleanup in ginVacuumPostingTreeLeaves() into an iterative sweep that follows the tree's leaf pages via their rightlinks. The recursive version called vacuum_delay_point() while processing the tree, but that call was removed and never re-added to the new loop. As that commit only set out to fix a deadlock, the removal appears to have been unintentional. Consequently the leaf-page sweep of a single posting tree runs with no vacuum_delay_point(), and therefore no CHECK_FOR_INTERRUPTS(). A posting tree stores all the TIDs for one indexed key, so for a frequently-occurring key it can span a large number of leaf pages. While such a tree is being vacuumed the operation ignores vacuum_cost_delay and does not respond to query cancellation or statement_timeout; an autovacuum worker likewise cannot be interrupted mid-sweep when another backend requests a conflicting lock. A corrupted rightlink that forms a cycle makes the loop run uninterruptibly forever. Restore the call, placed after the current page has been unlocked and released so that no buffer content lock is held across a potential delay (cf. 21c27af65fb). The sibling loops in ginbulkdelete() and ginvacuumcleanup() already call vacuum_delay_point() once per page. Back-patch to all supported branches, which all contain fd83c83d094. --- src/backend/access/gin/ginvacuum.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/backend/access/gin/ginvacuum.c b/src/backend/access/gin/ginvacuum.c index 840543eb664..a0b81a215a9 100644 --- a/src/backend/access/gin/ginvacuum.c +++ b/src/backend/access/gin/ginvacuum.c @@ -429,6 +429,8 @@ ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno) if (blkno == InvalidBlockNumber) break; + vacuum_delay_point(false); + buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno, RBM_NORMAL, gvs->strategy); LockBuffer(buffer, GIN_EXCLUSIVE); -- 2.50.1 (Apple Git-155)
