Re: pg_restore_attribute_stats() accepts non-finite values - Mailing list pgsql-hackers

From Ewan Young
Subject Re: pg_restore_attribute_stats() accepts non-finite values
Date
Msg-id CAON2xHNe-rFvLbMcFUGDHxsazKFxJp4FPmB+WCsQfZCimhaTtA@mail.gmail.com
Whole thread
In response to Re: pg_restore_attribute_stats() accepts non-finite values  (Ewan Young <kdbase.hack@gmail.com>)
List pgsql-hackers
On Mon, Aug 31, 2026 at 3:57 PM Michael Paquier <michael@paquier.xyz> 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



pgsql-hackers by date:

Previous
From: JoongHyuk Shin
Date:
Subject: Re: [PATCH] Prevent repeated deadlock-check signals in standby buffer pin waits
Next
From: Mihail Nikalayeu
Date:
Subject: Re: Routed ON CONFLICT inserts broken by partition-local deferrable unique constraints in 19 and master