mirror of
https://github.com/postgres/postgres.git
synced 2025-07-03 20:02:46 +03:00
Sync up our various ways of estimating pg_class.reltuples.
VACUUM thought that reltuples represents the total number of tuples in the relation, while ANALYZE counted only live tuples. This can cause "flapping" in the value when background vacuums and analyzes happen separately. The planner's use of reltuples essentially assumes that it's the count of live (visible) tuples, so let's standardize on having it mean live tuples. Another issue is that the definition of "live tuple" isn't totally clear; what should be done with INSERT_IN_PROGRESS or DELETE_IN_PROGRESS tuples? ANALYZE's choices in this regard are made on the assumption that if the originating transaction commits at all, it will happen after ANALYZE finishes, so we should ignore the effects of the in-progress transaction --- unless it is our own transaction, and then we should count it. Let's propagate this definition into VACUUM, too. Likewise propagate this definition into CREATE INDEX, and into contrib/pgstattuple's pgstattuple_approx() function. Tomas Vondra, reviewed by Haribabu Kommi, some corrections by me Discussion: https://postgr.es/m/16db4468-edfa-830a-f921-39a50498e77e@2ndquadrant.com
This commit is contained in:
@ -771,6 +771,9 @@ vacuum_set_xid_limits(Relation rel,
|
||||
* subset of the table. When we have only partial information, we take
|
||||
* the old value of pg_class.reltuples as a measurement of the
|
||||
* tuple density in the unscanned pages.
|
||||
*
|
||||
* Note: scanned_tuples should count only *live* tuples, since
|
||||
* pg_class.reltuples is defined that way.
|
||||
*/
|
||||
double
|
||||
vac_estimate_reltuples(Relation relation,
|
||||
@ -852,6 +855,9 @@ vac_estimate_reltuples(Relation relation,
|
||||
* transaction. This is OK since postponing the flag maintenance is
|
||||
* always allowable.
|
||||
*
|
||||
* Note: num_tuples should count only *live* tuples, since
|
||||
* pg_class.reltuples is defined that way.
|
||||
*
|
||||
* This routine is shared by VACUUM and ANALYZE.
|
||||
*/
|
||||
void
|
||||
|
@ -114,9 +114,9 @@ typedef struct LVRelStats
|
||||
BlockNumber pinskipped_pages; /* # of pages we skipped due to a pin */
|
||||
BlockNumber frozenskipped_pages; /* # of frozen pages we skipped */
|
||||
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 old_live_tuples; /* previous value of pg_class.reltuples */
|
||||
double new_rel_tuples; /* new estimated total # of tuples */
|
||||
double new_live_tuples; /* new estimated total # of live tuples */
|
||||
double new_dead_tuples; /* new estimated total # of dead tuples */
|
||||
BlockNumber pages_removed;
|
||||
double tuples_deleted;
|
||||
@ -196,7 +196,6 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
|
||||
TransactionId xidFullScanLimit;
|
||||
MultiXactId mxactFullScanLimit;
|
||||
BlockNumber new_rel_pages;
|
||||
double new_rel_tuples;
|
||||
BlockNumber new_rel_allvisible;
|
||||
double new_live_tuples;
|
||||
TransactionId new_frozen_xid;
|
||||
@ -245,7 +244,7 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
|
||||
vacrelstats = (LVRelStats *) palloc0(sizeof(LVRelStats));
|
||||
|
||||
vacrelstats->old_rel_pages = onerel->rd_rel->relpages;
|
||||
vacrelstats->old_rel_tuples = onerel->rd_rel->reltuples;
|
||||
vacrelstats->old_live_tuples = onerel->rd_rel->reltuples;
|
||||
vacrelstats->num_index_scans = 0;
|
||||
vacrelstats->pages_removed = 0;
|
||||
vacrelstats->lock_waiter_detected = false;
|
||||
@ -311,11 +310,11 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
|
||||
* since then we don't know for certain that all tuples have a newer xmin.
|
||||
*/
|
||||
new_rel_pages = vacrelstats->rel_pages;
|
||||
new_rel_tuples = vacrelstats->new_rel_tuples;
|
||||
new_live_tuples = vacrelstats->new_live_tuples;
|
||||
if (vacrelstats->tupcount_pages == 0 && new_rel_pages > 0)
|
||||
{
|
||||
new_rel_pages = vacrelstats->old_rel_pages;
|
||||
new_rel_tuples = vacrelstats->old_rel_tuples;
|
||||
new_live_tuples = vacrelstats->old_live_tuples;
|
||||
}
|
||||
|
||||
visibilitymap_count(onerel, &new_rel_allvisible, NULL);
|
||||
@ -327,7 +326,7 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
|
||||
|
||||
vac_update_relstats(onerel,
|
||||
new_rel_pages,
|
||||
new_rel_tuples,
|
||||
new_live_tuples,
|
||||
new_rel_allvisible,
|
||||
vacrelstats->hasindex,
|
||||
new_frozen_xid,
|
||||
@ -335,10 +334,6 @@ lazy_vacuum_rel(Relation onerel, int options, VacuumParams *params,
|
||||
false);
|
||||
|
||||
/* report results to the stats collector, too */
|
||||
new_live_tuples = new_rel_tuples - vacrelstats->new_dead_tuples;
|
||||
if (new_live_tuples < 0)
|
||||
new_live_tuples = 0; /* just in case */
|
||||
|
||||
pgstat_report_vacuum(RelationGetRelid(onerel),
|
||||
onerel->rd_rel->relisshared,
|
||||
new_live_tuples,
|
||||
@ -471,10 +466,11 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
|
||||
TransactionId relminmxid = onerel->rd_rel->relminmxid;
|
||||
BlockNumber empty_pages,
|
||||
vacuumed_pages;
|
||||
double num_tuples,
|
||||
tups_vacuumed,
|
||||
nkeep,
|
||||
nunused;
|
||||
double num_tuples, /* total number of nonremovable tuples */
|
||||
live_tuples, /* live tuples (reltuples estimate) */
|
||||
tups_vacuumed, /* tuples cleaned up by vacuum */
|
||||
nkeep, /* dead-but-not-removable tuples */
|
||||
nunused; /* unused item pointers */
|
||||
IndexBulkDeleteResult **indstats;
|
||||
int i;
|
||||
PGRUsage ru0;
|
||||
@ -505,7 +501,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
|
||||
relname)));
|
||||
|
||||
empty_pages = vacuumed_pages = 0;
|
||||
num_tuples = tups_vacuumed = nkeep = nunused = 0;
|
||||
num_tuples = live_tuples = tups_vacuumed = nkeep = nunused = 0;
|
||||
|
||||
indstats = (IndexBulkDeleteResult **)
|
||||
palloc0(nindexes * sizeof(IndexBulkDeleteResult *));
|
||||
@ -988,6 +984,17 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
|
||||
|
||||
tupgone = false;
|
||||
|
||||
/*
|
||||
* The criteria for counting a tuple as live in this block need to
|
||||
* match what analyze.c's acquire_sample_rows() does, otherwise
|
||||
* VACUUM and ANALYZE may produce wildly different reltuples
|
||||
* values, e.g. when there are many recently-dead tuples.
|
||||
*
|
||||
* The logic here is a bit simpler than acquire_sample_rows(), as
|
||||
* VACUUM can't run inside a transaction block, which makes some
|
||||
* cases impossible (e.g. in-progress insert from the same
|
||||
* transaction).
|
||||
*/
|
||||
switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
|
||||
{
|
||||
case HEAPTUPLE_DEAD:
|
||||
@ -1028,6 +1035,12 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
|
||||
elog(WARNING, "relation \"%s\" TID %u/%u: OID is invalid",
|
||||
relname, blkno, offnum);
|
||||
|
||||
/*
|
||||
* Count it as live. Not only is this natural, but it's
|
||||
* also what acquire_sample_rows() does.
|
||||
*/
|
||||
live_tuples += 1;
|
||||
|
||||
/*
|
||||
* Is the tuple definitely visible to all transactions?
|
||||
*
|
||||
@ -1073,12 +1086,29 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
|
||||
all_visible = false;
|
||||
break;
|
||||
case HEAPTUPLE_INSERT_IN_PROGRESS:
|
||||
/* This is an expected case during concurrent vacuum */
|
||||
|
||||
/*
|
||||
* This is an expected case during concurrent vacuum.
|
||||
*
|
||||
* We do not count these rows as live, because we expect
|
||||
* the inserting transaction to update the counters at
|
||||
* commit, and we assume that will happen only after we
|
||||
* report our results. This assumption is a bit shaky,
|
||||
* but it is what acquire_sample_rows() does, so be
|
||||
* consistent.
|
||||
*/
|
||||
all_visible = false;
|
||||
break;
|
||||
case HEAPTUPLE_DELETE_IN_PROGRESS:
|
||||
/* This is an expected case during concurrent vacuum */
|
||||
all_visible = false;
|
||||
|
||||
/*
|
||||
* Count such rows as live. As above, we assume the
|
||||
* deleting transaction will commit and update the
|
||||
* counters after we report.
|
||||
*/
|
||||
live_tuples += 1;
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
|
||||
@ -1281,15 +1311,18 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
|
||||
pfree(frozen);
|
||||
|
||||
/* save stats for use later */
|
||||
vacrelstats->scanned_tuples = num_tuples;
|
||||
vacrelstats->tuples_deleted = tups_vacuumed;
|
||||
vacrelstats->new_dead_tuples = nkeep;
|
||||
|
||||
/* now we can compute the new value for pg_class.reltuples */
|
||||
vacrelstats->new_rel_tuples = vac_estimate_reltuples(onerel,
|
||||
nblocks,
|
||||
vacrelstats->tupcount_pages,
|
||||
num_tuples);
|
||||
vacrelstats->new_live_tuples = vac_estimate_reltuples(onerel,
|
||||
nblocks,
|
||||
vacrelstats->tupcount_pages,
|
||||
live_tuples);
|
||||
|
||||
/* also compute total number of surviving heap entries */
|
||||
vacrelstats->new_rel_tuples =
|
||||
vacrelstats->new_live_tuples + vacrelstats->new_dead_tuples;
|
||||
|
||||
/*
|
||||
* Release any remaining pin on visibility map page.
|
||||
@ -1625,7 +1658,8 @@ lazy_vacuum_index(Relation indrel,
|
||||
ivinfo.analyze_only = false;
|
||||
ivinfo.estimated_count = true;
|
||||
ivinfo.message_level = elevel;
|
||||
ivinfo.num_heap_tuples = vacrelstats->old_rel_tuples;
|
||||
/* We can only provide an approximate value of num_heap_tuples here */
|
||||
ivinfo.num_heap_tuples = vacrelstats->old_live_tuples;
|
||||
ivinfo.strategy = vac_strategy;
|
||||
|
||||
/* Do bulk deletion */
|
||||
@ -1656,6 +1690,12 @@ lazy_cleanup_index(Relation indrel,
|
||||
ivinfo.analyze_only = false;
|
||||
ivinfo.estimated_count = (vacrelstats->tupcount_pages < vacrelstats->rel_pages);
|
||||
ivinfo.message_level = elevel;
|
||||
|
||||
/*
|
||||
* Now we can provide a better estimate of total number of surviving
|
||||
* tuples (we assume indexes are more interested in that than in the
|
||||
* number of nominally live tuples).
|
||||
*/
|
||||
ivinfo.num_heap_tuples = vacrelstats->new_rel_tuples;
|
||||
ivinfo.strategy = vac_strategy;
|
||||
|
||||
|
Reference in New Issue
Block a user