#include "storage/bufmgr.h"
#include "storage/freespace.h"
#include "storage/lmgr.h"
+#include "storage/read_stream.h"
#include "utils/lsyscache.h"
#include "utils/pg_rusage.h"
#include "utils/timestamp.h"
static void lazy_scan_heap(LVRelState *vacrel);
static void heap_vacuum_eager_scan_setup(LVRelState *vacrel,
VacuumParams *params);
-static bool heap_vac_scan_next_block(LVRelState *vacrel, BlockNumber *blkno,
- uint8 *blk_info);
+static BlockNumber heap_vac_scan_next_block(ReadStream *stream,
+ void *callback_private_data,
+ void *per_buffer_data);
static void find_next_unskippable_block(LVRelState *vacrel, bool *skipsallvis);
static bool lazy_scan_new_or_empty(LVRelState *vacrel, Buffer buf,
BlockNumber blkno, Page page,
static void
lazy_scan_heap(LVRelState *vacrel)
{
+ ReadStream *stream;
BlockNumber rel_pages = vacrel->rel_pages,
- blkno,
+ blkno = 0,
next_fsm_block_to_vacuum = 0;
- uint8 blk_info = 0;
+ void *per_buffer_data = NULL;
BlockNumber orig_eager_scan_success_limit =
vacrel->eager_scan_remaining_successes; /* for logging */
Buffer vmbuffer = InvalidBuffer;
vacrel->next_unskippable_eager_scanned = false;
vacrel->next_unskippable_vmbuffer = InvalidBuffer;
- while (heap_vac_scan_next_block(vacrel, &blkno, &blk_info))
+ /* Set up the read stream for vacuum's first pass through the heap */
+ stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE,
+ vacrel->bstrategy,
+ vacrel->rel,
+ MAIN_FORKNUM,
+ heap_vac_scan_next_block,
+ vacrel,
+ sizeof(uint8));
+
+ while (true)
{
Buffer buf;
Page page;
+ uint8 blk_info = 0;
bool has_lpdead_items;
bool vm_page_frozen = false;
bool got_cleanup_lock = false;
- vacrel->scanned_pages++;
- if (blk_info & VAC_BLK_WAS_EAGER_SCANNED)
- vacrel->eager_scanned_pages++;
-
- /* Report as block scanned, update error traceback information */
- pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno);
- update_vacuum_error_info(vacrel, NULL, VACUUM_ERRCB_PHASE_SCAN_HEAP,
- blkno, InvalidOffsetNumber);
-
vacuum_delay_point(false);
/*
* one-pass strategy, and the two-pass strategy with the index_cleanup
* param set to 'off'.
*/
- if (vacrel->scanned_pages % FAILSAFE_EVERY_PAGES == 0)
+ if (vacrel->scanned_pages > 0 &&
+ vacrel->scanned_pages % FAILSAFE_EVERY_PAGES == 0)
lazy_check_wraparound_failsafe(vacrel);
/*
/*
* Vacuum the Free Space Map to make newly-freed space visible on
- * upper-level FSM pages. Note we have not yet processed blkno.
+ * upper-level FSM pages. Note that blkno is the previously
+ * processed block.
*/
FreeSpaceMapVacuumRange(vacrel->rel, next_fsm_block_to_vacuum,
- blkno);
+ blkno + 1);
next_fsm_block_to_vacuum = blkno;
/* Report that we are once again scanning the heap */
PROGRESS_VACUUM_PHASE_SCAN_HEAP);
}
+ buf = read_stream_next_buffer(stream, &per_buffer_data);
+
+ /* The relation is exhausted. */
+ if (!BufferIsValid(buf))
+ break;
+
+ blk_info = *((uint8 *) per_buffer_data);
+ CheckBufferIsPinnedOnce(buf);
+ page = BufferGetPage(buf);
+ blkno = BufferGetBlockNumber(buf);
+
+ vacrel->scanned_pages++;
+ if (blk_info & VAC_BLK_WAS_EAGER_SCANNED)
+ vacrel->eager_scanned_pages++;
+
+ /* Report as block scanned, update error traceback information */
+ pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno);
+ update_vacuum_error_info(vacrel, NULL, VACUUM_ERRCB_PHASE_SCAN_HEAP,
+ blkno, InvalidOffsetNumber);
+
/*
* Pin the visibility map page in case we need to mark the page
* all-visible. In most cases this will be very cheap, because we'll
*/
visibilitymap_pin(vacrel->rel, blkno, &vmbuffer);
- buf = ReadBufferExtended(vacrel->rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
- vacrel->bstrategy);
- page = BufferGetPage(buf);
-
/*
* We need a buffer cleanup lock to prune HOT chains and defragment
* the page in lazy_scan_prune. But when it's not possible to acquire
if (BufferIsValid(vmbuffer))
ReleaseBuffer(vmbuffer);
- /* report that everything is now scanned */
- pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED, blkno);
+ /*
+ * Report that everything is now scanned. We never skip scanning the last
+ * block in the relation, so we can pass rel_pages here.
+ */
+ pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_SCANNED,
+ rel_pages);
/* now we can compute the new value for pg_class.reltuples */
vacrel->new_live_tuples = vac_estimate_reltuples(vacrel->rel, rel_pages,
Max(vacrel->new_live_tuples, 0) + vacrel->recently_dead_tuples +
vacrel->missed_dead_tuples;
+ read_stream_end(stream);
+
/*
* Do index vacuuming (call each index's ambulkdelete routine), then do
* related heap vacuuming
/*
* Vacuum the remainder of the Free Space Map. We must do this whether or
* not there were indexes, and whether or not we bypassed index vacuuming.
+ * We can pass rel_pages here because we never skip scanning the last
+ * block of the relation.
*/
- if (blkno > next_fsm_block_to_vacuum)
- FreeSpaceMapVacuumRange(vacrel->rel, next_fsm_block_to_vacuum, blkno);
+ if (rel_pages > next_fsm_block_to_vacuum)
+ FreeSpaceMapVacuumRange(vacrel->rel, next_fsm_block_to_vacuum, rel_pages);
/* report all blocks vacuumed */
- pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, blkno);
+ pgstat_progress_update_param(PROGRESS_VACUUM_HEAP_BLKS_VACUUMED, rel_pages);
/* Do final index cleanup (call each index's amvacuumcleanup routine) */
if (vacrel->nindexes > 0 && vacrel->do_index_cleanup)
}
/*
- * heap_vac_scan_next_block() -- get next block for vacuum to process
- *
- * lazy_scan_heap() calls here every time it needs to get the next block to
- * prune and vacuum. The function uses the visibility map, vacuum options,
- * and various thresholds to skip blocks which do not need to be processed and
- * sets blkno to the next block to process.
- *
- * The block number of the next block to process is set in *blkno and its
- * visibility status and whether or not it was eager scanned is set in
- * *blk_info.
- *
- * The return value is false if there are no further blocks to process.
- *
- * vacrel is an in/out parameter here. Vacuum options and information about
- * the relation are read. vacrel->skippedallvis is set if we skip a block
- * that's all-visible but not all-frozen, to ensure that we don't update
- * relfrozenxid in that case. vacrel also holds information about the next
- * unskippable block, as bookkeeping for this function.
+ * heap_vac_scan_next_block() -- read stream callback to get the next block
+ * for vacuum to process
+ *
+ * Every time lazy_scan_heap() needs a new block to process during its first
+ * phase, it invokes read_stream_next_buffer() with a stream set up to call
+ * heap_vac_scan_next_block() to get the next block.
+ *
+ * heap_vac_scan_next_block() uses the visibility map, vacuum options, and
+ * various thresholds to skip blocks which do not need to be processed and
+ * returns the next block to process or InvalidBlockNumber if there are no
+ * remaining blocks.
+ *
+ * The visibility status of the next block to process and whether or not it
+ * was eager scanned is set in the per_buffer_data.
+ *
+ * callback_private_data contains a reference to the LVRelState, passed to the
+ * read stream API during stream setup. The LVRelState is an in/out parameter
+ * here (locally named `vacrel`). Vacuum options and information about the
+ * relation are read from it. vacrel->skippedallvis is set if we skip a block
+ * that's all-visible but not all-frozen (to ensure that we don't update
+ * relfrozenxid in that case). vacrel also holds information about the next
+ * unskippable block -- as bookkeeping for this function.
*/
-static bool
-heap_vac_scan_next_block(LVRelState *vacrel, BlockNumber *blkno,
- uint8 *blk_info)
+static BlockNumber
+heap_vac_scan_next_block(ReadStream *stream,
+ void *callback_private_data,
+ void *per_buffer_data)
{
BlockNumber next_block;
+ LVRelState *vacrel = callback_private_data;
+ uint8 blk_info = 0;
/* relies on InvalidBlockNumber + 1 overflowing to 0 on first call */
next_block = vacrel->current_block + 1;
- *blk_info = 0;
-
/* Have we reached the end of the relation? */
if (next_block >= vacrel->rel_pages)
{
ReleaseBuffer(vacrel->next_unskippable_vmbuffer);
vacrel->next_unskippable_vmbuffer = InvalidBuffer;
}
- *blkno = vacrel->rel_pages;
- return false;
+ return InvalidBlockNumber;
}
/*
* but chose not to. We know that they are all-visible in the VM,
* otherwise they would've been unskippable.
*/
- *blkno = vacrel->current_block = next_block;
- *blk_info |= VAC_BLK_ALL_VISIBLE_ACCORDING_TO_VM;
- return true;
+ vacrel->current_block = next_block;
+ blk_info |= VAC_BLK_ALL_VISIBLE_ACCORDING_TO_VM;
+ *((uint8 *) per_buffer_data) = blk_info;
+ return vacrel->current_block;
}
else
{
*/
Assert(next_block == vacrel->next_unskippable_block);
- *blkno = vacrel->current_block = next_block;
+ vacrel->current_block = next_block;
if (vacrel->next_unskippable_allvis)
- *blk_info |= VAC_BLK_ALL_VISIBLE_ACCORDING_TO_VM;
+ blk_info |= VAC_BLK_ALL_VISIBLE_ACCORDING_TO_VM;
if (vacrel->next_unskippable_eager_scanned)
- *blk_info |= VAC_BLK_WAS_EAGER_SCANNED;
- return true;
+ blk_info |= VAC_BLK_WAS_EAGER_SCANNED;
+ *((uint8 *) per_buffer_data) = blk_info;
+ return vacrel->current_block;
}
}