Hi,

On 2023-01-18 13:42:40 -0800, Andres Freund wrote:
> The real point of change appears to be 10->11.
>
> There's a relevant looking difference in the vac_estimate_reltuples call:
> 10:
>       /* now we can compute the new value for pg_class.reltuples */
>       vacrelstats->new_rel_tuples = vac_estimate_reltuples(onerel, false,
>                                                                               
>                                  nblocks,
>                                                                               
>                                  vacrelstats->tupcount_pages,
>                                                                               
>                                  num_tuples);
>
> 11:
>       /* now we can compute the new value for pg_class.reltuples */
>       vacrelstats->new_live_tuples = vac_estimate_reltuples(onerel,
>                                                                               
>                                   nblocks,
>                                                                               
>                                   vacrelstats->tupcount_pages,
>                                                                               
>                                   live_tuples);
> which points to:
>
> commit 7c91a0364fcf5d739a09cc87e7adb1d4a33ed112
> Author: Tom Lane <t...@sss.pgh.pa.us>
> Date:   2018-03-22 15:47:29 -0400
>
>     Sync up our various ways of estimating pg_class.reltuples.

The problem with the change is here:

        /*
         * Okay, we've covered the corner cases.  The normal calculation is to
         * convert the old measurement to a density (tuples per page), then
         * estimate the number of tuples in the unscanned pages using that 
figure,
         * and finally add on the number of tuples in the scanned pages.
         */
        old_density = old_rel_tuples / old_rel_pages;
        unscanned_pages = (double) total_pages - (double) scanned_pages;
        total_tuples = old_density * unscanned_pages + scanned_tuples;
        return floor(total_tuples + 0.5);


Because we'll re-scan the pages for not-yet-removable rows in subsequent
vacuums, the next vacuum will process the same pages again. By using
scanned_tuples = live_tuples, we basically remove not-yet-removable tuples
from reltuples, each time.

The commit *did* try to account for that to some degree:

+    /* also compute total number of surviving heap entries */
+    vacrelstats->new_rel_tuples =
+        vacrelstats->new_live_tuples + vacrelstats->new_dead_tuples;


but new_rel_tuples isn't used for pg_class.reltuples or pgstat.


This is pretty nasty. We use reltuples for a lot of things. And while analyze
might fix it sometimes, that won't reliably be the case, particularly when
there are repeated autovacuums due to a longrunning transaction - there's no
cause for auto-analyze to trigger again soon, while autovacuum will go at it
again and again.

Greetings,

Andres Freund


Reply via email to