From 7db3830e6f00346c464d6a3d7b340f2f2ec4f11e Mon Sep 17 00:00:00 2001 From: Rui Zhao Date: Sun, 23 Aug 2026 23:34:22 +0800 Subject: [PATCH] Free pre-detoasted index values on every scan-loop exit heapam_index_build_range_scan() detoasts a recently-dead tuple's external values before running the partial-index predicate, but the ExecQual() == false path left the loop without freeing them: an index build over N rejected recently-dead tuples kept all N detoasted copies until the end of the scan. The missing-TOAST skip path had the mirror problem: it freed the values but did not reset detoasted_attrs to NULL, so the next tuple operated on the freed set. Move the cleanup into one helper and call it on all three exits of the scan-loop iteration. Measured with 800 recently-dead rows of 391 kB each and a partial index rejecting every row: the build backend's memory context total passes 200 MB mid-build without this fix and peaks at 1.4 MB with it. --- src/backend/access/heap/heapam_handler.c | 55 +++++++++++++----------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 0444bb53ace..c3494a5b2c2 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -61,6 +61,8 @@ static void heap_insert_for_repack(HeapTuple tuple, Relation OldHeap, BulkInsertState bistate); static HeapTuple reform_tuple(HeapTuple tuple, Relation OldHeap, Relation NewHeap, Datum *values, bool *isnull); +static void free_detoasted_attrs(TupleTableSlot *slot, + Bitmapset **detoasted_attrs); static bool SampleHeapTupleVisible(TableScanDesc scan, Buffer buffer, HeapTuple tuple, @@ -1209,6 +1211,30 @@ heapam_scan_analyze_next_tuple(TableScanDesc scan, return false; } +/* + * Free the values heapam_index_build_range_scan() pre-detoasted for a + * recently-dead tuple, and reset the tracking set for the next tuple. + * Every exit from the scan-loop iteration must come through here once + * something was detoasted, whether the tuple was indexed or not. + */ +static void +free_detoasted_attrs(TupleTableSlot *slot, Bitmapset **detoasted_attrs) +{ + int attno = -1; + + if (*detoasted_attrs == NULL) + return; + + while ((attno = bms_next_member(*detoasted_attrs, attno)) != -2) + { + Assert(attno > 0); + pfree(DatumGetPointer(slot->tts_values[attno - 1])); + } + + bms_free(*detoasted_attrs); + *detoasted_attrs = NULL; +} + static double heapam_index_build_range_scan(Relation heapRelation, Relation indexRelation, @@ -1789,17 +1815,7 @@ heapam_index_build_range_scan(Relation heapRelation, if (skip) { - attno = -1; - - /* cleanup any pre-detoasted values */ - while ((attno = bms_next_member(detoasted_attrs, attno)) != -2) - { - Assert(attno > 0); - pfree(DatumGetPointer(slot->tts_values[attno - 1])); - } - - /* final cleanup of this iteration's memory */ - bms_free(detoasted_attrs); + free_detoasted_attrs(slot, &detoasted_attrs); continue; } } @@ -1811,7 +1827,10 @@ heapam_index_build_range_scan(Relation heapRelation, if (predicate != NULL) { if (!ExecQual(predicate, econtext)) + { + free_detoasted_attrs(slot, &detoasted_attrs); continue; + } } /* @@ -1878,19 +1897,7 @@ heapam_index_build_range_scan(Relation heapRelation, tupleIsAlive, callback_state); } - if (!bms_is_empty(detoasted_attrs)) - { - int attno = -1; - - while ((attno = bms_next_member(detoasted_attrs, attno)) != -2) - { - Assert(attno > 0); - pfree(DatumGetPointer(slot->tts_values[attno - 1])); - } - - bms_free(detoasted_attrs); - detoasted_attrs = NULL; - } + free_detoasted_attrs(slot, &detoasted_attrs); } /* Report scan progress one last time. */ -- 2.43.7