Skip to content
About
Main content

Statistics

Why are statistics always stale?

Where to spend the buckets

A is a lossy compression of a distribution under a fixed space budget, so the design question is: where do you spend the buckets?

  • Equi-width histograms spend buckets evenly over the value range. They are catastrophic under skew (a few very common values), because one bucket holds most of the rows, and the inside that bucket is then maximally wrong.
  • Equi-depth histograms spend buckets evenly over the mass, so every bucket holds the same number of rows. That equalizes the worst-case error per bucket, and that is why equi-depth won.
  • Max-diff and V-optimal histograms go further and place boundaries to minimize the variance inside each bucket directly.

The general principle is put the boundary where the error grows fastest. It is the same principle behind every non-uniform quantization scheme.

Why independence is assumed

Now the structural limit. To capture the correlation between k columns you need their joint distribution, whose size is the product of the distinct values: ∏ᵢ V(cᵢ). You can afford only the marginals (one distribution per column), whose total size is the sum of the bucket counts: Σᵢ b.

It is assumed because the joint distribution is exponential in the number of columns and the marginals are linear. Independence is a convenient falsehood elsewhere; here it is the only thing that fits in the budget.

The escapes and what each costs

  • Extended / multi-column statistics. Real, but you must name which column groups to build, which means you must already know where the correlation is. All pairs is C(n,2) groups, all subsets is worse, so coverage is always partial and chosen by a human who is guessing. (PostgreSQL's CREATE STATISTICS is an example.)
  • Functional dependency detection. A means one column determines another (postcode determines city). It is cheap where it applies, since it collapses two columns into one. It is useless where the correlation is statistical rather than exact, which is most of the time.
  • Sampling. The error on a mean falls as 1/√k, but tails and do not behave. And there is a trap specific to joins: If you take uniform samples of A and B at rate p, the join of the samples keeps only about p² of the join's rows. At p = 0.01 you keep one ten-thousandth (0.01 × 0.01 = 0.0001) of the result, and small true results vanish to zero. That is why sample-based join estimation needs correlated or index-assisted , not independent sampling of each side.

Auto-analyze: where staleness is made

are refreshed by an automatic background job (). The mechanism is worth knowing exactly. PostgreSQL triggers analyze on a table when the number of changed rows exceeds , where n is the table's row count. That threshold grows with table size.

Worse, and more common: the newly loaded partition has no statistics at all. It falls back to the , and it is exactly the partition that every query filters on. This is Ananya's bug. On Tuesday the new partition had no statistics, the assumed placeholder selectivities, produced "1 row", and picked the nested loop.

Why is this not just negligence? Because statistics collection is a scan, and a scan has a fixed cost per collection. A fixed cost divided by the benefit per refresh derives a natural refresh interval, just as a fixed I/O cost derives a batch size.

This also tells you the escape: anything that makes collection incremental instead of a full scan moves the fixed cost and therefore moves the interval. Two examples are statistics maintained at write time, and metadata written by the writer itself, as table formats do when they store per-file statistics at commit time. That is a real reason why such formats changed optimizer behavior more than any change to an optimizer did.

In one sentence: bucket boundaries go where error grows fastest; independence is a consequence of a space budget, not a belief; and statistics are stale because collection has a fixed cost that derives a refresh interval.