From 6a4b57ba2e5ba0a5e28b952400c07932e84a62df Mon Sep 17 00:00:00 2001 From: TatsuyaKawata Date: Sun, 2 Aug 2026 19:30:55 +0900 Subject: [PATCH] Don't disable abbreviated keys for bounded heap sorts COMPARETUP already falls back to the real value on an abbreviated tie, so bounded heap comparisons work fine without an eager restore at switch time. Track the post-switch heap-survival rate instead, and abandon abbreviation only once it stops paying off. --- src/backend/utils/sort/tuplesort.c | 69 ++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/src/backend/utils/sort/tuplesort.c b/src/backend/utils/sort/tuplesort.c index c0e7527b9ca..0ca3beb78b1 100644 --- a/src/backend/utils/sort/tuplesort.c +++ b/src/backend/utils/sort/tuplesort.c @@ -328,6 +328,15 @@ struct Tuplesortstate int64 abbrevNext; /* Tuple # at which to next check * applicability */ + /* + * Tracks whether abbreviation is still worth its cost once we're + * maintaining a bounded heap. See tuplesort_puttuple_common(). + */ + int64 boundedArrivals; /* tuples seen since entering TSS_BOUNDED */ + int64 boundedReplacements; /* of those, how many replaced the heap root */ + int64 boundedAbbrevNext; /* boundedArrivals value at which to next + * reconsider abbreviation's usefulness */ + /* * Resource snapshot for time of sort start. */ @@ -759,19 +768,6 @@ tuplesort_set_bound(Tuplesortstate *state, int64 bound) state->bounded = true; state->bound = (int) bound; - - /* - * Bounded sorts are not an effective target for abbreviated key - * optimization. Disable by setting state to be consistent with no - * abbreviation support. - */ - state->base.sortKeys->abbrev_converter = NULL; - if (state->base.sortKeys->abbrev_full_comparator) - state->base.sortKeys->comparator = state->base.sortKeys->abbrev_full_comparator; - - /* Not strictly necessary, but be tidy */ - state->base.sortKeys->abbrev_abort = NULL; - state->base.sortKeys->abbrev_full_comparator = NULL; } /* @@ -1189,6 +1185,42 @@ tuplesort_puttuple_common(Tuplesortstate *state, SortTuple *tuple, /* discard top of heap, replacing it with the new tuple */ free_sort_tuple(state, &state->memtuples[0]); tuplesort_heap_replace_top(state, tuple); + state->boundedReplacements++; + } + + /* + * Each arrival here is compared against the root once and + * usually discarded, so abbreviation's cost isn't amortized + * the way it is for an ordinary sort. Periodically check the + * survival rate and give up on abbreviation if it's low; the + * restore is cheap since the heap holds only state->bound + * tuples. + */ + if (state->base.sortKeys->abbrev_converter != NULL) + { + state->boundedArrivals++; + if (state->boundedArrivals >= state->boundedAbbrevNext) + { + state->boundedAbbrevNext *= 2; + + /* less than 5% of arrivals since the switch survived */ + if (state->boundedReplacements * 20 < state->boundedArrivals) + { + SortSupport sortKey = state->base.sortKeys; + + if (trace_sort) + elog(LOG, "abandoning abbreviation in bounded heap after %lld arrivals (%lld replacements): %s", + (long long) state->boundedArrivals, + (long long) state->boundedReplacements, + pg_rusage_show(&state->ru_start)); + + REMOVEABBREV(state, state->memtuples, state->memtupcount); + sortKey->comparator = sortKey->abbrev_full_comparator; + sortKey->abbrev_converter = NULL; + sortKey->abbrev_abort = NULL; + sortKey->abbrev_full_comparator = NULL; + } + } } break; @@ -2491,6 +2523,17 @@ make_bounded_heap(Tuplesortstate *state) Assert(tupcount >= state->bound); Assert(SERIAL(state)); + /* + * Don't restore/disable abbreviation here: COMPARETUP already falls + * back to the real value on an abbreviated tie (e.g. + * comparetup_heap_tiebreak), so it works fine on abbreviated tuples. + * tuplesort_puttuple_common() decides separately whether to abandon + * abbreviation once we're maintaining the heap. + */ + state->boundedArrivals = 0; + state->boundedReplacements = 0; + state->boundedAbbrevNext = 1000; + /* Reverse sort direction so largest entry will be at root */ reversedirection(state); -- 2.34.1