1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-30 21:42:05 +03:00

Use streaming read I/O in GiST vacuuming

Like c5c239e26e did for btree vacuuming, make GiST vacuum use the
read stream API for sequentially processed pages.

Because it is possible for concurrent insertions to relocate unprocessed
index entries to already vacuumed pages, GiST vacuum must backtrack and
reprocess those pages. These pages are still read with explicit
ReadBuffer() calls.

Author: Andrey M. Borodin <x4mmm@yandex-team.ru>
Reviewed-by: Melanie Plageman <melanieplageman@gmail.com>
Discussion: https://postgr.es/m/EFEBED92-18D1-4C0F-A4EB-CD47072EF071%40yandex-team.ru
This commit is contained in:
Melanie Plageman
2025-03-21 14:05:36 -04:00
parent 3f850c3fc5
commit 69273b818b

View File

@ -22,6 +22,7 @@
#include "miscadmin.h" #include "miscadmin.h"
#include "storage/indexfsm.h" #include "storage/indexfsm.h"
#include "storage/lmgr.h" #include "storage/lmgr.h"
#include "storage/read_stream.h"
#include "utils/memutils.h" #include "utils/memutils.h"
/* Working state needed by gistbulkdelete */ /* Working state needed by gistbulkdelete */
@ -44,8 +45,7 @@ typedef struct
static void gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, static void gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
IndexBulkDeleteCallback callback, void *callback_state); IndexBulkDeleteCallback callback, void *callback_state);
static void gistvacuumpage(GistVacState *vstate, BlockNumber blkno, static void gistvacuumpage(GistVacState *vstate, Buffer buffer);
BlockNumber orig_blkno);
static void gistvacuum_delete_empty_pages(IndexVacuumInfo *info, static void gistvacuum_delete_empty_pages(IndexVacuumInfo *info,
GistVacState *vstate); GistVacState *vstate);
static bool gistdeletepage(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, static bool gistdeletepage(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
@ -129,8 +129,9 @@ gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
GistVacState vstate; GistVacState vstate;
BlockNumber num_pages; BlockNumber num_pages;
bool needLock; bool needLock;
BlockNumber blkno;
MemoryContext oldctx; MemoryContext oldctx;
BlockRangeReadStreamPrivate p;
ReadStream *stream = NULL;
/* /*
* Reset fields that track information about the entire index now. This * Reset fields that track information about the entire index now. This
@ -208,7 +209,14 @@ gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
*/ */
needLock = !RELATION_IS_LOCAL(rel); needLock = !RELATION_IS_LOCAL(rel);
blkno = GIST_ROOT_BLKNO; p.current_blocknum = GIST_ROOT_BLKNO;
stream = read_stream_begin_relation(READ_STREAM_FULL,
info->strategy,
rel,
MAIN_FORKNUM,
block_range_read_stream_cb,
&p,
0);
for (;;) for (;;)
{ {
/* Get the current relation length */ /* Get the current relation length */
@ -219,13 +227,39 @@ gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
UnlockRelationForExtension(rel, ExclusiveLock); UnlockRelationForExtension(rel, ExclusiveLock);
/* Quit if we've scanned the whole relation */ /* Quit if we've scanned the whole relation */
if (blkno >= num_pages) if (p.current_blocknum >= num_pages)
break; break;
/* Iterate over pages, then loop back to recheck length */
for (; blkno < num_pages; blkno++) p.last_exclusive = num_pages;
gistvacuumpage(&vstate, blkno, blkno);
/* Iterate over pages, then loop back to recheck relation length */
while (true)
{
Buffer buf;
/* call vacuum_delay_point while not holding any buffer lock */
vacuum_delay_point(false);
buf = read_stream_next_buffer(stream, NULL);
if (!BufferIsValid(buf))
break;
gistvacuumpage(&vstate, buf);
}
Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
/*
* We have to reset the read stream to use it again. After returning
* InvalidBuffer, the read stream API won't invoke our callback again
* until the stream has been reset.
*/
read_stream_reset(stream);
} }
read_stream_end(stream);
/* /*
* If we found any recyclable pages (and recorded them in the FSM), then * If we found any recyclable pages (and recorded them in the FSM), then
* forcibly update the upper-level FSM pages to ensure that searchers can * forcibly update the upper-level FSM pages to ensure that searchers can
@ -260,34 +294,32 @@ gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
/* /*
* gistvacuumpage --- VACUUM one page * gistvacuumpage --- VACUUM one page
* *
* This processes a single page for gistbulkdelete(). In some cases we * This processes a single page for gistbulkdelete(). `buffer` contains the
* must go back and re-examine previously-scanned pages; this routine * page to process. In some cases we must go back and reexamine
* recurses when necessary to handle that case. * previously-scanned pages; this routine recurses when necessary to handle
* * that case.
* blkno is the page to process. orig_blkno is the highest block number
* reached by the outer gistvacuumscan loop (the same as blkno, unless we
* are recursing to re-examine a previous page).
*/ */
static void static void
gistvacuumpage(GistVacState *vstate, BlockNumber blkno, BlockNumber orig_blkno) gistvacuumpage(GistVacState *vstate, Buffer buffer)
{ {
IndexVacuumInfo *info = vstate->info; IndexVacuumInfo *info = vstate->info;
IndexBulkDeleteCallback callback = vstate->callback; IndexBulkDeleteCallback callback = vstate->callback;
void *callback_state = vstate->callback_state; void *callback_state = vstate->callback_state;
Relation rel = info->index; Relation rel = info->index;
Buffer buffer; BlockNumber orig_blkno = BufferGetBlockNumber(buffer);
Page page; Page page;
BlockNumber recurse_to; BlockNumber recurse_to;
/*
* orig_blkno is the highest block number reached by the outer
* gistvacuumscan() loop. This will be the same as blkno unless we are
* recursing to reexamine a previous page.
*/
BlockNumber blkno = orig_blkno;
restart: restart:
recurse_to = InvalidBlockNumber; recurse_to = InvalidBlockNumber;
/* call vacuum_delay_point while not holding any buffer lock */
vacuum_delay_point(false);
buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
info->strategy);
/* /*
* We are not going to stay here for a long time, aggressively grab an * We are not going to stay here for a long time, aggressively grab an
* exclusive lock. * exclusive lock.
@ -450,6 +482,12 @@ restart:
if (recurse_to != InvalidBlockNumber) if (recurse_to != InvalidBlockNumber)
{ {
blkno = recurse_to; blkno = recurse_to;
/* check for vacuum delay while not holding any buffer lock */
vacuum_delay_point(false);
buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
info->strategy);
goto restart; goto restart;
} }
} }