Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1x7dWx-00000000XI4-3F3e for pgsql-hackers@arkaria.postgresql.org; Fri, 18 Sep 2026 18:41:52 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.98.2) (envelope-from ) id 1x7dWw-00000003Ajh-0S8W for pgsql-hackers@arkaria.postgresql.org; Fri, 18 Sep 2026 18:41:50 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1x7dTP-0000000387R-3AZP for pgsql-hackers@lists.postgresql.org; Fri, 18 Sep 2026 18:38:11 +0000 Received: from mail-wm2-x11.google.com ([2a00:1450:4864:31::11]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1x7dTN-000000005Dd-0ZMI for pgsql-hackers@lists.postgresql.org; Fri, 18 Sep 2026 18:38:11 +0000 Received: by mail-wm2-x11.google.com with SMTP id 5b1f17b1804b1-49cd4ba9f68so14564395e9.1 for ; Fri, 18 Sep 2026 11:38:08 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cybertec.at; s=google; t=1789756688; x=1790361488; darn=lists.postgresql.org; h=message-id:date:content-type:mime-version:comments:references :in-reply-to:subject:cc:to:from:from:to:cc:subject:date:message-id :reply-to:content-type; bh=iuMul9fSe0zuKqXC0h0ONdv28t7UiC/oBagh9ldEcis=; b=dsEXH6c0oOk1d3r4XEqKDK3HCdlDXsB+ZHx6ZDCYIBUJc3c9uW9Xpt70Hjp3OwtmS4 4RfwKnWSfGBbLVYMfJnzn1GHKdk0/d0XXfBayiZeHIiorMTkfuWQvnBL4AI48SPrI/jD TiuujFDlwrA5K0vJKoz4jnNUMexI1HQpG1v5wZ7C0USPeOWM35X7WDVa+8jtld/QROfE RjGbGwtRzxCyph8RGHvVmYkmqU7KFS5CnGyTSFXePAwHr9FnAx1x78MbZZFZlmyaylhM 1DXha0sMp1iJTXsfVT9vkE000AVCAViG+O1WO/B7K9DRf5oFozMgMg4YbkM1o4kj8Mid 3e+w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20260707; t=1789756688; x=1790361488; h=message-id:date:content-type:mime-version:comments:references :in-reply-to:subject:cc:to:from:x-gm-gg:x-gm-message-state:from:to :cc:subject:date:message-id:reply-to:content-type; bh=iuMul9fSe0zuKqXC0h0ONdv28t7UiC/oBagh9ldEcis=; b=iZGjov2hJWpdULCfhbhfh6XtwLXMvpbipmMooBYFGEGPMAnb+yPFiYH7yRN6/VcOVW ud/mImXVaIQvnCJPK0RbIjQOSJK4i1/GuNiWvaPp/eTvF+84bMuitG6K8FCZ4S0WyK7r BekxpA2cXc0FSap/OXFX3V/qzM3jRrVUFgAqayzPHHRAyBfQa5JSkqnrpaylw78FRhOx V46IhKCL9yZZpW90fRq55kwrPKVJkCW5xXbQLbhRqJKkPvOm6qw921deTryHbnGK7g5m /YaYRlaNDLurvHlVDefZETMzdJVafuHWi+0CpElfcI3UZlhTRq+msQi3adxXNy5UIAtv ryuw== X-Forwarded-Encrypted: i=1; AKwUvBwpfzLYnXpu4hd9Wgh3RbZEYJyyZqM9ucD0ZVOxejBUJH4LgscvniEOuJJkoHywmj5XHbO1lHYdOPwn2Dtw@lists.postgresql.org X-Gm-Message-State: AFuF++ncLx8hTzTmN7wVSW5TBZIIlvxbWnaO4a5mcfv5Ha91pnkq/PSH KbbVSzYVzs9L347KCRXhU9FRhTK/88YX3fMwPRjgwNGQkGaYzJPnGkV9k7IdQLvAQng= X-Gm-Gg: AYBFou1gOgA37aq0nWs3NGXdFC//93mEau7BK0Vas8A2xtdJON28P6Dkp/R4ghw4hgn bC1pNzbZSAXKJDyylSxACXvn/HMaLF9t4HuvP2yMyhcGrgAjL+gBll+uLlAvDtcEkH0rLsY+DFn hMPZW0nyef2X7mFNTW0uRt2AIRks44PtM+LaCcJWj4cvd3U9xRbMun/tUMM5Ufuvc6C2MS5SZBH kbnvMxo23BqGbvqw1jzx7/ovdTxkZXVkZ6hQgK9Kob9M5S7BFlhWdQ9COc+E0GamA2ogR9rSWVI E7lR8BEv8fnkeaFoGjqD99iNkOj+mPgrRq8gRw51aSaSqyN7U6mJMUwUAxQxZldaa4hfkUVuu3d jwxvOJbRTrODMjJC7kYvVlPY5eOCmkRAeBqHTpvqPObw/LoQKfHXH03OQwjB5nA3sO9MF3MHL4D 5kxgGae98dP5S+om6vfKy6K+cw7LI8wDJBIbU7ge8EDRZkf5QsaFXEyI1eOwotwLAjK4EvrSlZO INqGGqHzySy X-Received: by 2002:a05:600c:6296:b0:49e:6fd2:a45 with SMTP id 5b1f17b1804b1-49fc5737c43mr47381405e9.16.1789756687721; Fri, 18 Sep 2026 11:38:07 -0700 (PDT) Received: from localhost (109-81-170-190.rct.o2.cz. [109.81.170.190]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-49fc59286b6sm61343395e9.4.2026.09.18.11.38.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Fri, 18 Sep 2026 11:38:07 -0700 (PDT) From: Antonin Houska To: shihao zhong Cc: alvherre@kurilemu.de, pgsql-hackers@lists.postgresql.org Subject: Re: REPACK enhancements In-reply-to: <38165.1789662971@localhost> References: <109367.1781614382@localhost> <108776.1784105248@localhost> <224072.1789577949@localhost> <38165.1789662971@localhost> Comments: In-reply-to Antonin Houska message dated "Thu, 17 Sep 2026 18:36:11 +0200." X-Mailer: MH-E 8.6+git; nmh 1.8; GNU Emacs 28.3 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Date: Fri, 18 Sep 2026 20:38:06 +0200 Message-ID: <21489.1789756686@localhost> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --=-=-= Content-Type: text/plain Antonin Houska wrote: > shihao zhong wrote: > > > 2. > > Looks like some rows are lost when the table grows/gap fill. > > > > heapScan->rs_nblocks is fixed when the scan starts. At a range boundary, > > changes to blocks at or beyond rs_nblocks are not in [range_start, range_end), > > so they are skipped. The next snapshot can see those tuples, but the scan never > > reaches those blocks. > > > > The attached extend.spec has 5 blocks. It uses > > repack_snapshot_after = 2, pauses at the first boundary, and inserts 100 > > rows. 70 of them go to blocks 5 to 7 and are missing after REPACK. > > Interestiong. I think we need to get the new rows from new blocks each time we > process the concurrent changes. I could spend a little while on this item. Attached is what fixes your extend.spec.text. -- Antonin Houska Web: https://www.cybertec-postgresql.com --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=nocfbot.do_not_miss_relation_extension.diff diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index e423340ae70..f2d33494d8f 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -61,6 +61,7 @@ static Snapshot finalize_block_range(Relation rel_old, Relation rel_dst, MultiXactId multi_cutoff, ChangeContext *chgcxt, BlockNumber cur, BlockNumber start, BlockNumber *end_p, + BlockNumber nblocks, RewriteState *rwstate_p, BlockNumber *range_start_dst_p); static void reform_and_rewrite_tuple(TupleTableSlot *src, TupleTableSlot *reform, @@ -626,6 +627,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, BlockNumber range_end = InvalidBlockNumber; BlockNumber range_start_new = InvalidBlockNumber; BlockNumber range_end_new = InvalidBlockNumber; + BlockNumber nblocks; Relation rel_dst; /* Remember if it's a system catalog */ @@ -742,6 +744,18 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, snapshot = repack_get_snapshot(chgcxt); chgcxt->cc_last_snapshot_xmin = snapshot->xmin; + /* + * Unlike a scan during query processing, we also need to capture + * changes on pages that the scan will not reach: when processing + * particular range of pages, we always need to process changes on + * pages beyond nblocks. + * + * This had to wait until logical decoding has started (i.e. until we + * received the snapshot above), otherwise we could miss tuples + * inserted right after the block that we consider the last one. + */ + nblocks = heapScan->rs_nblocks; + /* * Since we currently do not freeze tuples when replaying data * changes, the replayed transactions should not precede the new value @@ -937,7 +951,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, OldestXmin, *xid_cutoff, *multi_cutoff, chgcxt, blkno, range_start, - &range_end, &rwstate, + &range_end, nblocks, + &rwstate, &range_start_new); /* Finally check the tuple visibility. */ @@ -1002,6 +1017,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, */ end_of_wal = GetFlushRecPtr(NULL); repack_process_concurrent_changes(chgcxt, end_of_wal, + InvalidBlockNumber, InvalidBlockNumber, InvalidBlockNumber, false, false); @@ -1170,6 +1186,7 @@ finalize_block_range(Relation rel_old, Relation rel_dst, MultiXactId multi_cutoff, ChangeContext *chgcxt, BlockNumber cur, BlockNumber start, BlockNumber *end_p, + BlockNumber nblocks, RewriteState *rwstate_p, BlockNumber *range_start_dst_p) { @@ -1216,8 +1233,8 @@ finalize_block_range(Relation rel_old, Relation rel_dst, * rid of the old tuple in the current range. */ end_of_wal = GetFlushRecPtr(NULL); - repack_process_concurrent_changes(chgcxt, end_of_wal, start, end, true, - false); + repack_process_concurrent_changes(chgcxt, end_of_wal, start, end, nblocks, + true, false); /* * A new snapshot will be pushed below. Note that it's important to not do diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c index 8cdf0d3ed20..eb8ceaed82e 100644 --- a/src/backend/commands/repack.c +++ b/src/backend/commands/repack.c @@ -208,7 +208,8 @@ static bool repack_is_permitted_for_relation(RepackCommand cmd, static void apply_concurrent_changes(ChangeContext *chgcxt, BlockNumber range_start, - BlockNumber range_end); + BlockNumber range_end, + BlockNumber nblocks); static void apply_concurrent_insert(RepackDest *dest, TupleTableSlot *spill_tuple, TupleTableSlot *new_tuple, @@ -227,7 +228,7 @@ static void restore_tuple(BufFile *file, Relation relation, static void adjust_toast_pointers(Relation relation, TupleTableSlot *dest, TupleTableSlot *src); static bool is_block_in_range(BlockNumber blknum, BlockNumber start, - BlockNumber end); + BlockNumber end, BlockNumber nblocks); static bool find_target_tuple(RepackDest *dest, TupleTableSlot *locator, TupleTableSlot *retrieved); static bool identity_key_equal(RepackDest *dest, @@ -3174,11 +3175,12 @@ RepackCommandAsString(RepackCommand cmd) } /* - * Apply data changes that affect pages in given range. + * Apply data changes that affect pages in given range (plus changes beyond + * nblocks). */ static void apply_concurrent_changes(ChangeContext *chgcxt, BlockNumber range_start, - BlockNumber range_end) + BlockNumber range_end, BlockNumber nblocks) { ConcurrentChangeKind kind = '\0'; RepackDest *dest; @@ -3275,7 +3277,7 @@ apply_concurrent_changes(ChangeContext *chgcxt, BlockNumber range_start, * range does not matter). */ if (!check_range || - is_block_in_range(block, range_start, range_end)) + is_block_in_range(block, range_start, range_end, nblocks)) apply_concurrent_insert(dest, spilled_tuple, new_tuple, xid); } else if (kind == CHANGE_DELETE) @@ -3285,7 +3287,7 @@ apply_concurrent_changes(ChangeContext *chgcxt, BlockNumber range_start, * range does not matter). */ if (!check_range || - is_block_in_range(block, range_start, range_end)) + is_block_in_range(block, range_start, range_end, nblocks)) { bool found; @@ -3311,8 +3313,8 @@ apply_concurrent_changes(ChangeContext *chgcxt, BlockNumber range_start, * current range. */ if (!check_range || - (is_block_in_range(old_block, range_start, range_end) && - is_block_in_range(block, range_start, range_end))) + (is_block_in_range(old_block, range_start, range_end, nblocks) && + is_block_in_range(block, range_start, range_end, nblocks))) { /* Find the tuple to be updated or deleted. */ found = find_target_tuple(dest, key, ondisk_tuple); @@ -3335,7 +3337,7 @@ apply_concurrent_changes(ChangeContext *chgcxt, BlockNumber range_start, { Assert(check_range); - if (is_block_in_range(block, range_start, range_end)) + if (is_block_in_range(block, range_start, range_end, nblocks)) { /* * The old key is in another range, so only insert the new @@ -3353,7 +3355,8 @@ apply_concurrent_changes(ChangeContext *chgcxt, BlockNumber range_start, apply_concurrent_insert(dest, spilled_tuple, new_tuple, xid); } - else if (is_block_in_range(old_block, range_start, range_end)) + else if (is_block_in_range(old_block, range_start, range_end, + nblocks)) { found = find_target_tuple(dest, key, ondisk_tuple); if (!found) @@ -3673,11 +3676,18 @@ adjust_toast_pointers(Relation relation, TupleTableSlot *dest, * copied. */ static bool -is_block_in_range(BlockNumber blknum, BlockNumber start, BlockNumber end) +is_block_in_range(BlockNumber blknum, BlockNumber start, BlockNumber end, + BlockNumber nblocks) { Assert(BlockNumberIsValid(start) && BlockNumberIsValid(end)); Assert(BlockNumberIsValid(blknum)); + /* + * Changes beyond the scan boundary are always needed. + */ + if (blknum >= nblocks) + return true; + if (start < end) return blknum >= start && blknum < end; else @@ -4285,6 +4295,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap, */ repack_process_concurrent_changes(chgcxt, end_of_wal, InvalidBlockNumber, InvalidBlockNumber, + InvalidBlockNumber, false, false); /* @@ -4344,6 +4355,7 @@ rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap, */ repack_process_concurrent_changes(chgcxt, end_of_wal, InvalidBlockNumber, InvalidBlockNumber, + InvalidBlockNumber, false, true); /* Remember info about rel before closing OldHeap */ @@ -5105,7 +5117,8 @@ repack_get_snapshot(ChangeContext *chgcxt) /* * Get concurrent changes, up to (and including) the record whose LSN is * 'end_of_wal', from the decoding worker, and apply them to the new table. If - * block range is specified, only apply changes related to that range. + * block range is specified, only apply changes related to that range, plus + * changes in blocks >= nblocks. * * If 'request_snapshot' is true, the snapshot built at LSN following the last * data change needs to be exported too. @@ -5115,6 +5128,7 @@ repack_process_concurrent_changes(ChangeContext *chgcxt, XLogRecPtr end_of_wal, BlockNumber range_start, BlockNumber range_end, + BlockNumber nblocks, bool request_snapshot, bool done) { DecodingWorkerShared *shared; @@ -5163,7 +5177,7 @@ repack_process_concurrent_changes(ChangeContext *chgcxt, #endif /* Apply the changes to the new table. */ - apply_concurrent_changes(chgcxt, range_start, range_end); + apply_concurrent_changes(chgcxt, range_start, range_end, nblocks); /* Get ready for the next set of changes. */ chgcxt->cc_file_seq_changes++; diff --git a/src/include/commands/repack.h b/src/include/commands/repack.h index 9e2f3e491c6..2f3312428ee 100644 --- a/src/include/commands/repack.h +++ b/src/include/commands/repack.h @@ -165,6 +165,7 @@ extern void repack_process_concurrent_changes(ChangeContext *chgcxt, XLogRecPtr end_of_wal, BlockNumber range_start, BlockNumber range_end, + BlockNumber nblocks, bool request_snapshot, bool done); extern void HandleRepackMessageInterrupt(void); extern void ProcessRepackMessages(void); --=-=-=--