From 1722ece7f754b136d481737dc05c18695d039c32 Mon Sep 17 00:00:00 2001 From: Khoa Nguyen Date: Wed, 9 Sep 2026 20:04:19 -0700 Subject: [PATCH] Add hash_build_sort_mode developer GUC to force the hash build sort path. hashbuild() sorts index tuples by bucket number only when the initial index size exceeds maintenance_work_mem or the number of usable buffers, whichever is less. Measuring whether that sort is actually useless on small indexes requires running both paths at a fixed row count and a fixed sort memory budget, which the existing knobs cannot do: maintenance_work_mem selects the path and sizes the sort at the same time. Add a developer-only enum GUC, hash_build_sort_mode, with values auto (default, historical behavior), on, and off. Marked DEVELOPER_OPTIONS and GUC_NOT_IN_SAMPLE. Benchmarking test only; not intended for submission. --- src/backend/access/hash/hash.c | 44 ++++++++++++++++++++++- src/backend/utils/misc/guc_parameters.dat | 9 +++++ src/backend/utils/misc/guc_tables.c | 8 +++++ src/include/access/hash.h | 16 +++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c index b2e34d2..a88fcfb 100644 --- a/src/backend/access/hash/hash.c +++ b/src/backend/access/hash/hash.c @@ -61,6 +61,13 @@ static BlockNumber hash_bulkdelete_read_stream_cb(ReadStream *stream, void *callback_private_data, void *per_buffer_data); +/* + * Developer GUC: override the automatic choice of whether hashbuild() sorts + * index tuples by bucket number. Defaults to HASH_BUILD_SORT_AUTO, which + * preserves the historical behavior. See hashbuild(). + */ +int hash_build_sort_mode = HASH_BUILD_SORT_AUTO; + /* * Hash handler function: return IndexAmRoutine with access method parameters @@ -140,6 +147,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo) double allvisfrac; uint32 num_buckets; Size sort_threshold; + bool do_sort; HashBuildState buildstate; /* @@ -173,6 +181,12 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo) * NOTE: this test will need adjustment if a bucket is ever different from * one page. Also, "initial index size" accounting does not include the * metapage, nor the first bitmap page. + * + * The hash_build_sort_mode developer GUC can override this decision, so + * that the sorted and unsorted paths can be compared at a fixed row count + * and a fixed maintenance_work_mem. Without it, the only way to reach the + * unsorted path is to raise maintenance_work_mem, which also changes how + * much memory the sort itself gets, confounding the comparison. */ sort_threshold = (maintenance_work_mem * (Size) 1024) / BLCKSZ; if (index->rd_rel->relpersistence != RELPERSISTENCE_TEMP) @@ -180,7 +194,21 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo) else sort_threshold = Min(sort_threshold, NLocBuffer); - if (num_buckets >= sort_threshold) + switch (hash_build_sort_mode) + { + case HASH_BUILD_SORT_ON: + do_sort = true; + break; + case HASH_BUILD_SORT_OFF: + do_sort = false; + break; + case HASH_BUILD_SORT_AUTO: + default: + do_sort = (num_buckets >= sort_threshold); + break; + } + + if (do_sort) buildstate.spool = _h_spoolinit(heap, index, num_buckets); else buildstate.spool = NULL; diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat index 3c5e16a..21b9934 100644 --- a/src/backend/utils/misc/guc_parameters.dat +++ b/src/backend/utils/misc/guc_parameters.dat @@ -1217,6 +1217,15 @@ boot_val => 'false', }, +{ name => 'hash_build_sort_mode', type => 'enum', context => 'PGC_USERSET', group => 'DEVELOPER_OPTIONS', + short_desc => 'Forces or disables the sort step during hash index builds.', + long_desc => 'The default, auto, sorts only when the initial index size exceeds "maintenance_work_mem" or the number of usable buffers, whichever is less.', + flags => 'GUC_NOT_IN_SAMPLE', + variable => 'hash_build_sort_mode', + boot_val => 'HASH_BUILD_SORT_AUTO', + options => 'hash_build_sort_mode_options', +}, + { name => 'hash_mem_multiplier', type => 'real', context => 'PGC_USERSET', group => 'RESOURCES_MEM', short_desc => 'Multiple of "work_mem" to use for hash tables.', flags => 'GUC_EXPLAIN', diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c index c6d9b2a..3385a82 100644 --- a/src/backend/utils/misc/guc_tables.c +++ b/src/backend/utils/misc/guc_tables.c @@ -31,6 +31,7 @@ #include "access/commit_ts.h" #include "access/gin.h" +#include "access/hash.h" #include "access/slru.h" #include "access/toast_compression.h" #include "access/twophase.h" @@ -520,6 +521,13 @@ static const struct config_enum_entry data_checksums_options[] = { {NULL, 0, false} }; +static const struct config_enum_entry hash_build_sort_mode_options[] = { + {"auto", HASH_BUILD_SORT_AUTO, false}, + {"on", HASH_BUILD_SORT_ON, false}, + {"off", HASH_BUILD_SORT_OFF, false}, + {NULL, 0, false} +}; + /* * Options for enum values stored in other modules */ diff --git a/src/include/access/hash.h b/src/include/access/hash.h index a8702f0..c965c4e 100644 --- a/src/include/access/hash.h +++ b/src/include/access/hash.h @@ -358,6 +358,22 @@ typedef struct HashOptions #define HASHNProcs 3 +/* + * Values for the hash_build_sort_mode developer GUC, which overrides the + * automatic choice of whether hashbuild() sorts index tuples by bucket + * number. HASH_BUILD_SORT_AUTO must be the boot value: it leaves the + * historical maintenance_work_mem/NBuffers test in force. + */ +typedef enum HashBuildSortMode +{ + HASH_BUILD_SORT_AUTO = 0, + HASH_BUILD_SORT_ON, + HASH_BUILD_SORT_OFF, +} HashBuildSortMode; + +extern PGDLLIMPORT int hash_build_sort_mode; + + /* public routines */ extern IndexBuildResult *hashbuild(Relation heap, Relation index, -- 2.39.5