From 0e2fb2c8c307aaee5ab47aa92055b53ca146706b Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Wed, 5 Aug 2026 16:55:26 +0900 Subject: [PATCH] Initialize read stream before fetching metapage in hash bulk-deletion hashbulkdelete() fetched the relcache's cached hash metapage before calling read_stream_begin_relation(). During the read stream initialization, relation lookups may process pending relcache invalidation messages, which could cause the cached metapage to be invalidated before the read stream uses it, leading to the failure of a VACUUM bulk-deletion for a hash index. This commit reworks the order of hashbulkdelete() so as its read stream is initialized before fetching the cached metapage, so as pending invalidation messages do not interfere with the relation scan. Issue introduced by bfa3c4f106b1. Author: Mikhail Nikalayeu Reviewed-by: Bertrand Drouvot Reviewed-by: Nazir Bilal Yavuz Reviewed-by: Xuneng Zhou Discussion: https://postgr.es/m/CADzfLwVEJ2_7ioX3ZSB1P7qXW40ghUy_ZCzeFvhDLoa_3Muztg@mail.gmail.com Backpatch-through: 19 --- src/backend/access/hash/hash.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index 8d8cd30dc38..b2e34d2d45e 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -515,6 +515,21 @@ hashbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, tuples_removed = 0; num_index_tuples = 0; + /* + * Set up the streaming read before fetching the cached metapage as read + * stream initialization may process relcache invalidation messages, + * invalidating the cached metapage. It is safe to use batchmode as + * hash_bulkdelete_read_stream_cb takes no locks. + */ + stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE | + READ_STREAM_USE_BATCHING, + info->strategy, + rel, + MAIN_FORKNUM, + hash_bulkdelete_read_stream_cb, + &stream_private, + 0); + /* * We need a copy of the metapage so that we can use its hashm_spares[] * values to compute bucket page addresses, but a cached copy should be @@ -536,19 +551,6 @@ hashbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, stream_private.next_bucket = cur_bucket; stream_private.max_bucket = cur_maxbucket; - /* - * It is safe to use batchmode as hash_bulkdelete_read_stream_cb takes no - * locks. - */ - stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE | - READ_STREAM_USE_BATCHING, - info->strategy, - rel, - MAIN_FORKNUM, - hash_bulkdelete_read_stream_cb, - &stream_private, - 0); - bucket_loop: while (cur_bucket <= cur_maxbucket) {