Skip to content

Commit

Permalink
Avoid having vacuum set reltuples to 0 on non-empty relations in the
Browse files Browse the repository at this point in the history
presence of page pins, which leads to serious estimation errors in the
planner.  This particularly affects small heavily-accessed tables,
especially where locking (e.g. from FK constraints) forces frequent
vacuums for mxid cleanup.

Fix by keeping separate track of pages whose live tuples were actually
counted vs. pages that were only scanned for freezing purposes.  Thus,
reltuples can only be set to 0 if all pages of the relation were
actually counted.

Backpatch to all supported versions.

Per bug #14057 from Nicolas Baccelli, analyzed by me.

Discussion: https://postgr.es/m/20160331103739.8956.94469@wrigleys.postgresql.org
  • Loading branch information
RhodiumToad committed Mar 16, 2017
1 parent 087e696 commit ee78ad5
Showing 1 changed file with 11 additions and 4 deletions.
15 changes: 11 additions & 4 deletions src/backend/commands/vacuumlazy.c
Expand Up @@ -106,7 +106,8 @@ typedef struct LVRelStats
BlockNumber rel_pages; /* total number of pages */
BlockNumber scanned_pages; /* number of pages we examined */
BlockNumber pinskipped_pages; /* # of pages we skipped due to a pin */
double scanned_tuples; /* counts only tuples on scanned pages */
BlockNumber tupcount_pages; /* pages whose tuples we counted */
double scanned_tuples; /* counts only tuples on tupcount_pages */
double old_rel_tuples; /* previous value of pg_class.reltuples */
double new_rel_tuples; /* new estimated total # of tuples */
double new_dead_tuples; /* new estimated total # of dead tuples */
Expand Down Expand Up @@ -286,6 +287,10 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
* density") with nonzero relpages and reltuples=0 (which means "zero
* tuple density") unless there's some actual evidence for the latter.
*
* It's important that we use tupcount_pages and not scanned_pages for the
* check described above; scanned_pages counts pages where we could not get
* cleanup lock, and which were processed only for frozenxid purposes.
*
* We do update relallvisible even in the corner case, since if the table
* is all-visible we'd definitely like to know that. But clamp the value
* to be not more than what we're setting relpages to.
Expand All @@ -295,7 +300,7 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
*/
new_rel_pages = vacrelstats->rel_pages;
new_rel_tuples = vacrelstats->new_rel_tuples;
if (vacrelstats->scanned_pages == 0 && new_rel_pages > 0)
if (vacrelstats->tupcount_pages == 0 && new_rel_pages > 0)
{
new_rel_pages = vacrelstats->old_rel_pages;
new_rel_tuples = vacrelstats->old_rel_tuples;
Expand Down Expand Up @@ -474,6 +479,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
nblocks = RelationGetNumberOfBlocks(onerel);
vacrelstats->rel_pages = nblocks;
vacrelstats->scanned_pages = 0;
vacrelstats->tupcount_pages = 0;
vacrelstats->nonempty_pages = 0;
vacrelstats->latestRemovedXid = InvalidTransactionId;

Expand Down Expand Up @@ -666,6 +672,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
}

vacrelstats->scanned_pages++;
vacrelstats->tupcount_pages++;

page = BufferGetPage(buf);

Expand Down Expand Up @@ -1074,7 +1081,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats,
/* now we can compute the new value for pg_class.reltuples */
vacrelstats->new_rel_tuples = vac_estimate_reltuples(onerel, false,
nblocks,
vacrelstats->scanned_pages,
vacrelstats->tupcount_pages,
num_tuples);

/*
Expand Down Expand Up @@ -1393,7 +1400,7 @@ lazy_cleanup_index(Relation indrel,

ivinfo.index = indrel;
ivinfo.analyze_only = false;
ivinfo.estimated_count = (vacrelstats->scanned_pages < vacrelstats->rel_pages);
ivinfo.estimated_count = (vacrelstats->tupcount_pages < vacrelstats->rel_pages);
ivinfo.message_level = elevel;
ivinfo.num_heap_tuples = vacrelstats->new_rel_tuples;
ivinfo.strategy = vac_strategy;
Expand Down

0 comments on commit ee78ad5

Please sign in to comment.