END_CRIT_SECTION();
+ gvs->result->pages_newly_deleted++;
gvs->result->pages_deleted++;
}
MemoryContext oldctx;
/*
- * Reset counts that will be incremented during the scan; needed in case
- * of multiple scans during a single VACUUM command.
+ * Reset fields that track information about the entire index now. This
+ * avoids double-counting in the case where a single VACUUM command
+ * requires multiple scans of the index.
+ *
+ * Avoid resetting the tuples_removed and pages_newly_deleted fields here,
+ * since they track information about the VACUUM command, and so must last
+ * across each call to gistvacuumscan().
+ *
+ * (Note that pages_free is treated as state about the whole index, not
+ * the current VACUUM. This is appropriate because RecordFreeIndexPage()
+ * calls are idempotent, and get repeated for the same deleted pages in
+ * some scenarios. The point for us is to track the number of recyclable
+ * pages in the index at the end of the VACUUM command.)
*/
+ stats->num_pages = 0;
stats->estimated_count = false;
stats->num_index_tuples = 0;
stats->pages_deleted = 0;
{
/* Okay to recycle this page */
RecordFreeIndexPage(rel, blkno);
- vstate->stats->pages_free++;
vstate->stats->pages_deleted++;
+ vstate->stats->pages_free++;
}
else if (GistPageIsDeleted(page))
{
/* mark the page as deleted */
MarkBufferDirty(leafBuffer);
GistPageSetDeleted(leafPage, txid);
+ stats->pages_newly_deleted++;
stats->pages_deleted++;
/* remove the downlink from the parent */
(*stats)->num_index_tuples,
(*stats)->num_pages),
errdetail("%.0f index row versions were removed.\n"
- "%u index pages have been deleted, %u are currently reusable.\n"
+ "%u index pages were newly deleted.\n"
+ "%u index pages are currently deleted, of which %u are currently reusable.\n"
"%s.",
(*stats)->tuples_removed,
+ (*stats)->pages_newly_deleted,
(*stats)->pages_deleted, (*stats)->pages_free,
pg_rusage_show(&ru0))));
}
static bool _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf,
BlockNumber scanblkno,
bool *rightsib_empty,
- uint32 *ndeleted);
+ BTVacState *vstate);
static bool _bt_lock_subtree_parent(Relation rel, BlockNumber child,
BTStack stack,
Buffer *subtreeparent,
* should never pass a buffer containing an existing deleted page here. The
* lock and pin on caller's buffer will be dropped before we return.
*
- * Returns the number of pages successfully deleted (zero if page cannot
- * be deleted now; could be more than one if parent or right sibling pages
- * were deleted too). Note that this does not include pages that we delete
- * that the btvacuumscan scan has yet to reach; they'll get counted later
- * instead.
+ * Maintains bulk delete stats for caller, which are taken from vstate. We
+ * need to cooperate closely with caller here so that whole VACUUM operation
+ * reliably avoids any double counting of subsidiary-to-leafbuf pages that we
+ * delete in passing. If such pages happen to be from a block number that is
+ * ahead of the current scanblkno position, then caller is expected to count
+ * them directly later on. It's simpler for us to understand caller's
+ * requirements than it would be for caller to understand when or how a
+ * deleted page became deleted after the fact.
*
* NOTE: this leaks memory. Rather than trying to clean up everything
* carefully, it's better to run it in a temp context that can be reset
* frequently.
*/
-uint32
-_bt_pagedel(Relation rel, Buffer leafbuf)
+void
+_bt_pagedel(Relation rel, Buffer leafbuf, BTVacState *vstate)
{
- uint32 ndeleted = 0;
BlockNumber rightsib;
bool rightsib_empty;
Page page;
/*
* Save original leafbuf block number from caller. Only deleted blocks
- * that are <= scanblkno get counted in ndeleted return value.
+ * that are <= scanblkno are added to bulk delete stat's pages_deleted
+ * count.
*/
BlockNumber scanblkno = BufferGetBlockNumber(leafbuf);
RelationGetRelationName(rel))));
_bt_relbuf(rel, leafbuf);
- return ndeleted;
+ return;
}
/*
Assert(!P_ISHALFDEAD(opaque));
_bt_relbuf(rel, leafbuf);
- return ndeleted;
+ return;
}
/*
if (_bt_leftsib_splitflag(rel, leftsib, leafblkno))
{
ReleaseBuffer(leafbuf);
- Assert(ndeleted == 0);
- return ndeleted;
+ return;
}
/* we need an insertion scan key for the search, so build one */
if (!_bt_mark_page_halfdead(rel, leafbuf, stack))
{
_bt_relbuf(rel, leafbuf);
- return ndeleted;
+ return;
}
}
{
/* Check for interrupts in _bt_unlink_halfdead_page */
if (!_bt_unlink_halfdead_page(rel, leafbuf, scanblkno,
- &rightsib_empty, &ndeleted))
+ &rightsib_empty, vstate))
{
/*
* _bt_unlink_halfdead_page should never fail, since we
* lock and pin on leafbuf for us.
*/
Assert(false);
- return ndeleted;
+ return;
}
}
leafbuf = _bt_getbuf(rel, rightsib, BT_WRITE);
}
-
- return ndeleted;
}
/*
*/
static bool
_bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno,
- bool *rightsib_empty, uint32 *ndeleted)
+ bool *rightsib_empty, BTVacState *vstate)
{
BlockNumber leafblkno = BufferGetBlockNumber(leafbuf);
+ IndexBulkDeleteResult *stats = vstate->stats;
BlockNumber leafleftsib;
BlockNumber leafrightsib;
BlockNumber target;
_bt_relbuf(rel, buf);
/*
- * If btvacuumscan won't revisit this page in a future btvacuumpage call
- * and count it as deleted then, we count it as deleted by current
- * btvacuumpage call
+ * Maintain pages_newly_deleted, which is simply the number of pages
+ * deleted by the ongoing VACUUM operation.
+ *
+ * Maintain pages_deleted in a way that takes into account how
+ * btvacuumpage() will count deleted pages that have yet to become
+ * scanblkno -- only count page when it's not going to get that treatment
+ * later on.
*/
+ stats->pages_newly_deleted++;
if (target <= scanblkno)
- (*ndeleted)++;
+ stats->pages_deleted++;
return true;
}
#include "utils/memutils.h"
-/* Working state needed by btvacuumpage */
-typedef struct
-{
- IndexVacuumInfo *info;
- IndexBulkDeleteResult *stats;
- IndexBulkDeleteCallback callback;
- void *callback_state;
- BTCycleId cycleid;
- MemoryContext pagedelcontext;
-} BTVacState;
-
/*
* BTPARALLEL_NOT_INITIALIZED indicates that the scan has not started.
*
* avoids double-counting in the case where a single VACUUM command
* requires multiple scans of the index.
*
- * Avoid resetting the tuples_removed field here, since it tracks
- * information about the VACUUM command, and so must last across each call
- * to btvacuumscan().
+ * Avoid resetting the tuples_removed and pages_newly_deleted fields here,
+ * since they track information about the VACUUM command, and so must last
+ * across each call to btvacuumscan().
*
* (Note that pages_free is treated as state about the whole index, not
* the current VACUUM. This is appropriate because RecordFreeIndexPage()
}
else if (P_ISHALFDEAD(opaque))
{
+ /* Half-dead leaf page (from interrupted VACUUM) -- finish deleting */
+ attempt_pagedel = true;
+
/*
- * Half-dead leaf page. Try to delete now. Might update
- * pages_deleted below.
+ * _bt_pagedel() will increment both pages_newly_deleted and
+ * pages_deleted stats in all cases (barring corruption)
*/
- attempt_pagedel = true;
}
else if (P_ISLEAF(opaque))
{
oldcontext = MemoryContextSwitchTo(vstate->pagedelcontext);
/*
- * We trust the _bt_pagedel return value because it does not include
- * any page that a future call here from btvacuumscan is expected to
- * count. There will be no double-counting.
+ * _bt_pagedel maintains the bulk delete stats on our behalf;
+ * pages_newly_deleted and pages_deleted are likely to be incremented
+ * during call
*/
Assert(blkno == scanblkno);
- stats->pages_deleted += _bt_pagedel(rel, buf);
+ _bt_pagedel(rel, buf, vstate);
MemoryContextSwitchTo(oldcontext);
/* pagedel released buffer, so we shouldn't */
/* Report final stats */
bds->stats->num_pages = num_pages;
+ bds->stats->pages_newly_deleted = bds->stats->pages_deleted;
bds->stats->pages_free = bds->stats->pages_deleted;
}
* of which this is just the first field; this provides a way for ambulkdelete
* to communicate additional private data to amvacuumcleanup.
*
- * Note: pages_deleted and pages_free refer to free space within the index
- * file. Some index AMs may compute num_index_tuples by reference to
+ * Note: pages_newly_deleted is the number of pages in the index that were
+ * deleted by the current vacuum operation. pages_deleted and pages_free
+ * refer to free space within the index file.
+ *
+ * Note: Some index AMs may compute num_index_tuples by reference to
* num_heap_tuples, in which case they should copy the estimated_count field
* from IndexVacuumInfo.
*/
bool estimated_count; /* num_index_tuples is an estimate */
double num_index_tuples; /* tuples remaining */
double tuples_removed; /* # removed during vacuum operation */
- BlockNumber pages_deleted; /* # unused pages in index */
+ BlockNumber pages_newly_deleted; /* # pages marked deleted by us */
+ BlockNumber pages_deleted; /* # pages marked deleted (could be by us) */
BlockNumber pages_free; /* # pages available for reuse */
} IndexBulkDeleteResult;
return false;
}
+/*
+ * BTVacState is private nbtree.c state used during VACUUM. It is exported
+ * for use by page deletion related code in nbtpage.c.
+ */
+typedef struct BTVacState
+{
+ IndexVacuumInfo *info;
+ IndexBulkDeleteResult *stats;
+ IndexBulkDeleteCallback callback;
+ void *callback_state;
+ BTCycleId cycleid;
+ MemoryContext pagedelcontext;
+} BTVacState;
+
/*
* Lehman and Yao's algorithm requires a ``high key'' on every non-rightmost
* page. The high key is not a tuple that is used to visit the heap. It is
extern void _bt_delitems_delete_check(Relation rel, Buffer buf,
Relation heapRel,
TM_IndexDeleteOp *delstate);
-extern uint32 _bt_pagedel(Relation rel, Buffer leafbuf);
+extern void _bt_pagedel(Relation rel, Buffer leafbuf, BTVacState *vstate);
/*
* prototypes for functions in nbtsearch.c