On Mon, Aug 31, 2026 at 3:57 PM Michael Paquier <[email protected]> wrote:
>
> On Thu, Aug 27, 2026 at 04:09:21PM +0800, Ewan Young wrote:
> > Thanks for the thorough review, and for the history -- that context on why
> > the checks were removed is helpful.
>
> Question: do we get elog(ERROR) problems, assertion failures or
> backend breakages when we insert these values or is the backend OK
> with them?

No hard breakage.  On an assertion-enabled build of master I injected
NaN and +/-Infinity through every unchecked argument (null_frac,
n_distinct, correlation, most_common_freqs, most_common_elem_freqs,
elem_count_histogram) and ran queries exercising each stat slot
(IS NULL, =, IN, GROUP BY, equijoins, array @>/<@, ORDER BY + LIMIT
over an index): no assertion failures, no elog(ERROR), no crashes.
And since only estimates are affected, query results stay correct.

What the values do poison is the cost model, in two distinct ways:

1. NaN probabilities sail through CLAMP_PROBABILITY (both of its
   comparisons are false for NaN); the NaN selectivity then hits
   clamp_row_est(), whose isnan() guard turns it into
   MAXIMUM_ROWCOUNT.  With null_frac = NaN:

     Seq Scan on tf  (cost=0.00..20.00 rows=1e100 width=47)
       Filter: (a IS NULL)

   (rows is printed as the full 101-digit integer), and every join or
   aggregate above such a scan now plans against 1e100 rows.  The
   same happens for = / IN / join selectivity when most_common_freqs
   contains NaN.  +/-Infinity is tamer here, since Inf > 1.0 is true
   and CLAMP_PROBABILITY catches it.

2. NaN correlation flows into the index-scan cost arithmetic
   unclamped, producing paths whose cost is literally NaN:

     Index Scan using tf_a_idx on tf  (cost=0.28..NaN rows=889 ...)

   Every comparison involving a NaN cost is false, so path cost
   comparisons degenerate and the chosen plan is essentially
   arbitrary.  A NaN also propagates up through the whole plan tree
   (Limit/GroupAggregate above it print cost=..NaN too).

There is no self-healing: the values sit in pg_statistic until some
later ANALYZE happens to overwrite them.

So the damage class is the same as the reltuples case fixed by
7cb9060dcde: nothing crashes, but it's stored garbage the planner has
no defense against, and rejecting it at import time seems much
cheaper than teaching every consumer of pg_statistic to cope with
non-finite inputs.

> --
> Michael



-- 
Regards,
Ewan Young


Reply via email to