vacuum_mem_estimate.v1.patch
application/octet-stream
Filename: vacuum_mem_estimate.v1.patch
Type: application/octet-stream
Part: 0
Patch
Format: unified
Series: patch v1
| File | + | − |
|---|---|---|
| src/backend/commands/vacuumlazy.c | 58 | 14 |
diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c
index b5fb325..478ec8c 100644
--- a/src/backend/commands/vacuumlazy.c
+++ b/src/backend/commands/vacuumlazy.c
@@ -88,9 +88,13 @@
/*
* Guesstimation of number of dead tuples per page. This is used to
* provide an upper limit to memory allocated when vacuuming small
- * tables.
+ * tables. The guesstimation becomes ineffective as table size grows
+ * so we use a heuristic to say that beyond LAZY_ESTIMATION_THRESHOLD
+ * we need better estimates and will use more precise pg_stats
+ * data after that point, noting that it is still only an estimate.
*/
#define LAZY_ALLOC_TUPLES MaxHeapTuplesPerPage
+#define LAZY_ESTIMATION_THRESHOLD 1000
/*
* Before we consider skipping a page that's marked as clean in
@@ -117,8 +121,8 @@ typedef struct LVRelStats
BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */
/* List of TIDs of tuples we intend to delete */
/* NB: this list is ordered by TID address */
- int num_dead_tuples; /* current # of entries */
- int max_dead_tuples; /* # slots allocated in array */
+ long num_dead_tuples; /* current # of entries */
+ long max_dead_tuples; /* # slots allocated in array */
ItemPointer dead_tuples; /* array of ItemPointerData */
int num_index_scans;
TransactionId latestRemovedXid;
@@ -154,7 +158,7 @@ static bool should_attempt_truncation(LVRelStats *vacrelstats);
static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats);
static BlockNumber count_nondeletable_pages(Relation onerel,
LVRelStats *vacrelstats);
-static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks);
+static void lazy_space_alloc(Relation onerel, LVRelStats *vacrelstats, BlockNumber relblocks);
static void lazy_record_dead_tuple(LVRelStats *vacrelstats,
ItemPointer itemptr);
static bool lazy_tid_reaped(ItemPointer itemptr, void *state);
@@ -492,7 +496,7 @@ lazy_scan_heap(Relation onerel, int options, LVRelStats *vacrelstats,
vacrelstats->nonempty_pages = 0;
vacrelstats->latestRemovedXid = InvalidTransactionId;
- lazy_space_alloc(vacrelstats, nblocks);
+ lazy_space_alloc(onerel, vacrelstats, nblocks);
frozen = palloc(sizeof(xl_heap_freeze_tuple) * MaxHeapTuplesPerPage);
/* Report that we're scanning the heap, advertising total # of blocks */
@@ -1597,7 +1601,7 @@ lazy_vacuum_index(Relation indrel,
lazy_tid_reaped, (void *) vacrelstats);
ereport(elevel,
- (errmsg("scanned index \"%s\" to remove %d row versions",
+ (errmsg("scanned index \"%s\" to remove %ld row versions",
RelationGetRelationName(indrel),
vacrelstats->num_dead_tuples),
errdetail("%s.", pg_rusage_show(&ru0))));
@@ -1941,7 +1945,7 @@ count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats)
* See the comments at the head of this file for rationale.
*/
static void
-lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks)
+lazy_space_alloc(Relation onerel, LVRelStats *vacrelstats, BlockNumber relblocks)
{
long maxtuples;
int vac_work_mem = IsAutoVacuumWorkerProcess() &&
@@ -1950,13 +1954,52 @@ lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks)
if (vacrelstats->hasindex)
{
+ long tuple_estimate = 0;
+
+ /*
+ * We need to allocate an array to track dead tuples, so apply initial
+ * criteria based upon user preferences and limits.
+ */
maxtuples = (vac_work_mem * 1024L) / sizeof(ItemPointerData);
- maxtuples = Min(maxtuples, INT_MAX);
- maxtuples = Min(maxtuples, MaxAllocSize / sizeof(ItemPointerData));
+ maxtuples = Min(maxtuples, LONG_MAX);
+ maxtuples = Min(maxtuples, MaxAllocHugeSize / sizeof(ItemPointerData));
+
+ /*
+ * The amount of memory used for tracking dead tuples will be
+ * limited by the number of blocks. For large tables this limit
+ * becomes ineffective in many cases, so we need to more
+ * accurately estimate the number of rows likely to be found
+ * during the VACUUM, if we are scanning indexes. We could
+ * use this in all cases, but the stats lookup is slow and
+ * we don't gain much from it for smaller tables.
+ */
+ if (relblocks > LAZY_ESTIMATION_THRESHOLD)
+ {
+ PgStat_StatTabEntry *tabentry = NULL;
+
+ tabentry = pgstat_fetch_stat_tabentry(RelationGetRelid(onerel));
+ if (PointerIsValid(tabentry))
+ tuple_estimate = tabentry->n_dead_tuples;
+ }
- /* curious coding here to ensure the multiplication can't overflow */
- if ((BlockNumber) (maxtuples / LAZY_ALLOC_TUPLES) > relblocks)
- maxtuples = relblocks * LAZY_ALLOC_TUPLES;
+ /*
+ * Apply the information we have about the table to further reduce
+ * the size of the allocation so we do not overallocate memory.
+ */
+ if (tuple_estimate > 0)
+ {
+ /*
+ * We have a tuple estimate, but allow 10% slop for timing and
+ * other issues affecting the accuracy.
+ */
+ maxtuples = Min(maxtuples, (long) tuple_estimate * 1.1);
+ }
+ else
+ {
+ /* curious coding here to ensure the multiplication can't overflow */
+ if ((BlockNumber) (maxtuples / LAZY_ALLOC_TUPLES) > relblocks)
+ maxtuples = relblocks * LAZY_ALLOC_TUPLES;
+ }
/* stay sane if small maintenance_work_mem */
maxtuples = Max(maxtuples, MaxHeapTuplesPerPage);
@@ -1967,9 +2010,10 @@ lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks)
}
vacrelstats->num_dead_tuples = 0;
- vacrelstats->max_dead_tuples = (int) maxtuples;
+ vacrelstats->max_dead_tuples = (long) maxtuples;
vacrelstats->dead_tuples = (ItemPointer)
- palloc(maxtuples * sizeof(ItemPointerData));
+ MemoryContextAllocHuge(CurrentMemoryContext,
+ maxtuples * sizeof(ItemPointerData));
}
/*