Re: Optimize MCV stats for sortable types and utilize sorted-order properties

From: ZizhuanLiu X-MAN <44973863(at)qq(dot)com>
To: pgsql-hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Cc: tgl <tgl(at)sss(dot)pgh(dot)pa(dot)us>, ilya(dot)evdokimov <ilya(dot)evdokimov(at)tantorlabs(dot)com>
Subject: Re: Optimize MCV stats for sortable types and utilize sorted-order properties
Date: 2026-09-17 03:53:36
Message-ID: tencent_456F3ACB71EAFB460AF8C2B61B5AF389B307@qq.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

>I write
>From: ZizhuanLiu X-MAN <44973863(at)qq(dot)com>
>Date: Sep 17, 2026 10:01
>To: ZizhuanLiu X-MAN <44973863(at)qq(dot)com>, pgsql-hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
>Cc: tgl <tgl(at)sss(dot)pgh(dot)pa(dot)us>, ilya.evdokimov <ilya(dot)evdokimov(at)tantorlabs(dot)com>
>Subject: Re: Optimize MCV stats for sortable types and utilize sorted-order properties
>......
>There is another fundamental issue:
> If the new-version compute_scalar_stats() no longer generates STATISTIC_KIND_MCV, but third-party code or extensions try to fetch STATISTIC_KIND_MCV. When only STATISTIC_KIND_MCV_VALUE_SORTED exists in the system,
> - should get_attstatsslot() re-sort STATISTIC_KIND_MCV_VALUE_SORTED by numbers[] in ascending order before returning it to the caller?
> (This re-sort cost should be manageable, since we sort on numbers[], not on values[].)
> - Or should we return STATISTIC_KIND_MCV_VALUE_SORTED directly without any processing?
>
>For good backward-compatibility, I lean toward the former option, though it is not a very elegant design.

Looking at the call graph in LIST-1:
When a caller requests STATISTIC_KIND_MCV but only STATISTIC_KIND_MCV_VALUE_SORTED is available in the system,
some reordering is required.

Based on how these callers consume the data, we only need to swap the entry with the largest number[i] and its
corresponding values[i] into slot [0], **rather than performing a full sort of the entire array**.

As shown by the logic in LIST-1: existing callers either rely on slot [0] holding the entry with the highest count
(the original behaviour of STATISTIC_KIND_MCV), or iterate over the number[] / values[] arrays.

Simply swapping the maximum-count entry to index [0] is sufficient to preserve backward compatibility. This keeps
the adjustment minimal: it requires N comparisons over the double-typed number[] array and at most one two-element swap,
so the overhead is kept as small as possible.

I will go ahead and implement along these lines. Deep insights and further feedback are very welcome.

>=== LIST-1 ===
>
>src/backend/commands/analyze.c
> static void compute_distinct_stats() — producer ; generates MCV statistics for data types that only have the "=" operator.
> static void compute_scalar_stats() — producer ; generates MCV statistics for data types that have both "=" and "<" operators.
>
>src/backend/executor/nodeHash.c
> static void ExecHashBuildSkewHash() — consumer ; reads the MCV list and iterates through it, accumulating sslot.numbers[i].
>
>src/backend/statistics/attribute_stats.c
> static bool attribute_statistics_update_internal() — producer ; generates MCV statistics from the input parameters using statatt_build_stavalues() and updates the MCV statistics with statatt_set_slot().
>
>src/backend/statistics/extended_stats_funcs.c
> static Datum import_pg_statistic() — producer ; generates MCV statistics from JSONB input.
>
>src/backend/utils/adt/network_selfuncs.c
> static Selectivity networkjoinsel_inner() — consumer ; reads the MCV list and either accumulates mcv_numbers[i] or compares values for equality using "=".
> static Selectivity networkjoinsel_semi() — consumer ; reads the MCV list and either accumulates mcv_numbers[i] or compares values for equality using "=".
>
>src/backend/utils/adt/selfuncs.c
> double var_eq_const() — consumer ; reads the MCV list, compares values using "=", obtains the selectivity of a matching value, and accumulates sslot.numbers when there is no match.
> double var_eq_non_const() — consumer ; reads the MCV statistics and currently uses sslot.numbers[0], i.e., the largest selectivity.
> double mcv_selectivity() — consumer ; reads the MCV list, checks each value against the comparison condition, and accumulates the corresponding selectivities.
> double ineq_histogram_selectivity() — consumer ; reads the MCV statistics but only uses mcvslot.nnumbers.
> Selectivity booltestsel() — consumer ; reads the first MCV element. If the first element is true, it uses sslot.numbers[0]; otherwise, it uses 1.0 - sslot.numbers[0] - freq_null.
> Datum eqjoinsel() — consumer ; apart from the hash algorithm, iterates through the MCV list and compares values using "=".
> void estimate_hash_bucket_stats() — consumer ; uses the first/largest-selectivity element by taking mcv_freq = sslot.numbers[0].
> static bool get_variable_range() — consumer ; iterates through the MCV list and compares values using "=".

regards,
--
ZizhuanLiu (X-MAN) 
44973863(at)qq(dot)com

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message shveta malik 2026-09-17 04:10:43 Re: [PATCH] Release replication slot on error in SQL-callable slot functions
Previous Message Michael Paquier 2026-09-17 03:44:41 Re: DSA_ALLOC_NO_OOM vs dsm_create ERROR leaving a half-initialized pgstats hash entry