postgresql/src/backend/statistics
Michael Paquier 530b6b02f8 Fix set of issues with extended statistics on expressions
This commit addresses two defects regarding extended statistics on
expressions:
- When building extended statistics in lookup_var_attr_stats(), the call
to examine_attribute() did not account for the possibility of a NULL
return value.  This can happen depending on the behavior of a typanalyze
callback — for example, if the callback returns false, if no rows are
sampled, or if no statistics are computed.  In such cases, the code
attempted to build MCV, dependency, and ndistinct statistics using a
NULL pointer, incorrectly assuming valid statistics were available,
which could lead to a server crash.
- When loading extended statistics for expressions,
statext_expressions_load() did not account for NULL entries in the
pg_statistic array storing expression statistics.  Such NULL entries can
be generated when statistics collection fails for an expression, as may
occur during the final step of serialize_expr_stats().  An extended
statistics object defining N expressions requires N corresponding
elements in the pg_statistic array stored for the expressions, and some
of these elements can be NULL.  This situation is reachable when a
typanalyze callback returns true, but sets stats_valid to indicate that
no useful statistics could be computed.

While these scenarios cannot occur with in-core typanalyze callbacks, as
far as I have analyzed, they can be triggered by custom data types with
custom typanalyze implementations, at least.

No tests are added in this commit.  A follow-up commit will introduce a
test module that can be extended to cover similar edge cases if
additional issues are discovered.  This takes care of the core of the
problem.

Attribute and relation statistics already offer similar protections:
- ANALYZE detects and skips the build of invalid statistics.
- Invalid catalog data is handled defensively when loading statistics.

This issue exists since the support for extended statistics on
expressions has been added, down to v14 as of a4d75c86bf.  Backpatch
to all supported stable branches.

Author: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Corey Huinker <corey.huinker@gmail.com>
Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Discussion: https://postgr.es/m/aaDrJsE1I5mrE-QF@paquier.xyz
Backpatch-through: 14
2026-03-02 09:38:42 +09:00
..
dependencies.c Fix typos and duplicate words 2024-04-18 21:28:07 +02:00
extended_stats.c Fix set of issues with extended statistics on expressions 2026-03-02 09:38:42 +09:00
Makefile Split all OBJS style lines in makefiles into one-line-per-entry style. 2019-11-05 14:41:07 -08:00
mcv.c Fix incorrectly reported stats kind in "can't happen" ERROR 2024-03-05 16:17:02 +13:00
meson.build Update copyright for 2024 2024-01-03 20:49:05 -05:00
mvdistinct.c Remove unused #include's from backend .c files 2024-03-04 12:02:20 +01:00
README Fix typos and grammar in code comments 2021-09-27 14:21:28 +09:00
README.dependencies Remove obsolete comments about functional dependencies 2017-07-26 11:40:39 -04:00
README.mcv Fix typos and grammar in code comments 2021-09-27 14:21:28 +09:00

Extended statistics
===================

When estimating various quantities (e.g. condition selectivities) the default
approach relies on the assumption of independence. In practice that's often
not true, resulting in estimation errors.

Extended statistics track different types of dependencies between the columns,
hopefully improving the estimates and producing better plans.


Types of statistics
-------------------

There are currently several kinds of extended statistics:

    (a) ndistinct coefficients

    (b) soft functional dependencies (README.dependencies)

    (c) MCV lists (README.mcv)


Compatible clause types
-----------------------

Each type of statistics may be used to estimate some subset of clause types.

    (a) functional dependencies - equality clauses (AND), possibly IS NULL

    (b) MCV lists - equality and inequality clauses (AND, OR, NOT), IS [NOT] NULL

Currently, only OpExprs in the form Var op Const, or Const op Var are
supported, however it's feasible to expand the code later to also estimate the
selectivities on clauses such as Var op Var.


Complex clauses
---------------

We also support estimating more complex clauses - essentially AND/OR clauses
with (Var op Const) as leaves, as long as all the referenced attributes are
covered by a single statistics object.

For example this condition

    (a=1) AND ((b=2) OR ((c=3) AND (d=4)))

may be estimated using statistics on (a,b,c,d). If we only have statistics on
(b,c,d) we may estimate the second part, and estimate (a=1) using simple stats.

If we only have statistics on (a,b,c) we can't apply it at all at this point,
but it's worth pointing out clauselist_selectivity() works recursively and when
handling the second part (the OR-clause), we'll be able to apply the statistics.

Note: The multi-statistics estimation patch also makes it possible to pass some
clauses as 'conditions' into the deeper parts of the expression tree.


Selectivity estimation
----------------------

Throughout the planner clauselist_selectivity() still remains in charge of
most selectivity estimate requests. clauselist_selectivity() can be instructed
to try to make use of any extended statistics on the given RelOptInfo, which
it will do if:

    (a) An actual valid RelOptInfo was given. Join relations are passed in as
        NULL, therefore are invalid.

    (b) The relation given actually has any extended statistics defined which
        are actually built.

When the above conditions are met, clauselist_selectivity() first attempts to
pass the clause list off to the extended statistics selectivity estimation
function. This function may not find any clauses which it can perform any
estimations on. In such cases, these clauses are simply ignored. When actual
estimation work is performed in these functions they're expected to mark which
clauses they've performed estimations for so that any other function
performing estimations knows which clauses are to be skipped.

Size of sample in ANALYZE
-------------------------

When performing ANALYZE, the number of rows to sample is determined as

    (300 * statistics_target)

That works reasonably well for statistics on individual columns, but perhaps
it's not enough for extended statistics. Papers analyzing estimation errors
all use samples proportional to the table (usually finding that 1-3% of the
table is enough to build accurate stats).

The requested accuracy (number of MCV items or histogram bins) should also
be considered when determining the sample size, and in extended statistics
those are not necessarily limited by statistics_target.

This however merits further discussion, because collecting the sample is quite
expensive and increasing it further would make ANALYZE even more painful.
Judging by the experiments with the current implementation, the fixed size
seems to work reasonably well for now, so we leave this as future work.