Initialize read stream before fetching metapage in hash bulk-deletion
authorMichael Paquier <michael@paquier.xyz>
Wed, 5 Aug 2026 07:55:23 +0000 (16:55 +0900)
committerMichael Paquier <michael@paquier.xyz>
Wed, 5 Aug 2026 07:55:23 +0000 (16:55 +0900)
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 <mihailnikalayeu@gmail.com>
Reviewed-by: Bertrand Drouvot <bertranddrouvot.pg@gmail.com>
Reviewed-by: Nazir Bilal Yavuz <byavuz81@gmail.com>
Reviewed-by: Xuneng Zhou <xunengzhou@gmail.com>
Discussion: https://postgr.es/m/CADzfLwVEJ2_7ioX3ZSB1P7qXW40ghUy_ZCzeFvhDLoa_3Muztg@mail.gmail.com
Backpatch-through: 19

src/backend/access/hash/hash.c

index 8d8cd30dc386bff9fbb1fc974f4ced4345817574..b2e34d2d45e8f3c645df93780e111f7ec46324c6 100644 (file)
@@ -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)
    {