PostgreSQL Source Code git master
gistvacuum.c File Reference
#include "postgres.h"
#include "access/genam.h"
#include "access/gist_private.h"
#include "access/transam.h"
#include "commands/vacuum.h"
#include "lib/integerset.h"
#include "miscadmin.h"
#include "storage/indexfsm.h"
#include "storage/lmgr.h"
#include "storage/read_stream.h"
#include "utils/memutils.h"
Include dependency graph for gistvacuum.c:

Go to the source code of this file.

Data Structures

struct  GistVacState
 

Functions

static void gistvacuumscan (IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
 
static void gistvacuumpage (GistVacState *vstate, Buffer buffer)
 
static void gistvacuum_delete_empty_pages (IndexVacuumInfo *info, GistVacState *vstate)
 
static bool gistdeletepage (IndexVacuumInfo *info, IndexBulkDeleteResult *stats, Buffer parentBuffer, OffsetNumber downlink, Buffer leafBuffer)
 
IndexBulkDeleteResultgistbulkdelete (IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
 
IndexBulkDeleteResultgistvacuumcleanup (IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 

Function Documentation

◆ gistbulkdelete()

IndexBulkDeleteResult * gistbulkdelete ( IndexVacuumInfo info,
IndexBulkDeleteResult stats,
IndexBulkDeleteCallback  callback,
void *  callback_state 
)

Definition at line 59 of file gistvacuum.c.

61{
62 /* allocate stats if first time through, else re-use existing struct */
63 if (stats == NULL)
65
66 gistvacuumscan(info, stats, callback, callback_state);
67
68 return stats;
69}
static void gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
Definition: gistvacuum.c:125
void * palloc0(Size size)
Definition: mcxt.c:1975
static void callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
Definition: test_ifaddrs.c:46

References callback(), gistvacuumscan(), and palloc0().

Referenced by gisthandler().

◆ gistdeletepage()

static bool gistdeletepage ( IndexVacuumInfo info,
IndexBulkDeleteResult stats,
Buffer  parentBuffer,
OffsetNumber  downlink,
Buffer  leafBuffer 
)
static

Definition at line 631 of file gistvacuum.c.

634{
635 Page parentPage = BufferGetPage(parentBuffer);
636 Page leafPage = BufferGetPage(leafBuffer);
637 ItemId iid;
638 IndexTuple idxtuple;
639 XLogRecPtr recptr;
641
642 /*
643 * Check that the leaf is still empty and deletable.
644 */
645 if (!GistPageIsLeaf(leafPage))
646 {
647 /* a leaf page should never become a non-leaf page */
648 Assert(false);
649 return false;
650 }
651
652 if (GistFollowRight(leafPage))
653 return false; /* don't mess with a concurrent page split */
654
656 return false; /* not empty anymore */
657
658 /*
659 * Ok, the leaf is deletable. Is the downlink in the parent page still
660 * valid? It might have been moved by a concurrent insert. We could try
661 * to re-find it by scanning the page again, possibly moving right if the
662 * was split. But for now, let's keep it simple and just give up. The
663 * next VACUUM will pick it up.
664 */
665 if (PageIsNew(parentPage) || GistPageIsDeleted(parentPage) ||
666 GistPageIsLeaf(parentPage))
667 {
668 /* shouldn't happen, internal pages are never deleted */
669 Assert(false);
670 return false;
671 }
672
673 if (PageGetMaxOffsetNumber(parentPage) < downlink
675 return false;
676
677 iid = PageGetItemId(parentPage, downlink);
678 idxtuple = (IndexTuple) PageGetItem(parentPage, iid);
679 if (BufferGetBlockNumber(leafBuffer) !=
680 ItemPointerGetBlockNumber(&(idxtuple->t_tid)))
681 return false;
682
683 /*
684 * All good, proceed with the deletion.
685 *
686 * The page cannot be immediately recycled, because in-progress scans that
687 * saw the downlink might still visit it. Mark the page with the current
688 * next-XID counter, so that we know when it can be recycled. Once that
689 * XID becomes older than GlobalXmin, we know that all scans that are
690 * currently in progress must have ended. (That's much more conservative
691 * than needed, but let's keep it safe and simple.)
692 */
694
696
697 /* mark the page as deleted */
698 MarkBufferDirty(leafBuffer);
699 GistPageSetDeleted(leafPage, txid);
700 stats->pages_newly_deleted++;
701 stats->pages_deleted++;
702
703 /* remove the downlink from the parent */
704 MarkBufferDirty(parentBuffer);
705 PageIndexTupleDelete(parentPage, downlink);
706
707 if (RelationNeedsWAL(info->index))
708 recptr = gistXLogPageDelete(leafBuffer, txid, parentBuffer, downlink);
709 else
710 recptr = gistGetFakeLSN(info->index);
711 PageSetLSN(parentPage, recptr);
712 PageSetLSN(leafPage, recptr);
713
715
716 return true;
717}
BlockNumber BufferGetBlockNumber(Buffer buffer)
Definition: bufmgr.c:4231
void MarkBufferDirty(Buffer buffer)
Definition: bufmgr.c:2952
static Page BufferGetPage(Buffer buffer)
Definition: bufmgr.h:417
void PageIndexTupleDelete(Page page, OffsetNumber offnum)
Definition: bufpage.c:1051
static Item PageGetItem(const PageData *page, const ItemIdData *itemId)
Definition: bufpage.h:354
static bool PageIsNew(const PageData *page)
Definition: bufpage.h:234
static ItemId PageGetItemId(Page page, OffsetNumber offsetNumber)
Definition: bufpage.h:244
static void PageSetLSN(Page page, XLogRecPtr lsn)
Definition: bufpage.h:391
PageData * Page
Definition: bufpage.h:82
static OffsetNumber PageGetMaxOffsetNumber(const PageData *page)
Definition: bufpage.h:372
#define GistPageIsLeaf(page)
Definition: gist.h:170
static void GistPageSetDeleted(Page page, FullTransactionId deletexid)
Definition: gist.h:205
#define GistFollowRight(page)
Definition: gist.h:183
#define GistPageIsDeleted(page)
Definition: gist.h:173
XLogRecPtr gistGetFakeLSN(Relation rel)
Definition: gistutil.c:1016
XLogRecPtr gistXLogPageDelete(Buffer buffer, FullTransactionId xid, Buffer parentBuffer, OffsetNumber downlinkOffset)
Definition: gistxlog.c:552
Assert(PointerIsAligned(start, uint64))
static BlockNumber ItemPointerGetBlockNumber(const ItemPointerData *pointer)
Definition: itemptr.h:103
IndexTupleData * IndexTuple
Definition: itup.h:53
#define START_CRIT_SECTION()
Definition: miscadmin.h:150
#define END_CRIT_SECTION()
Definition: miscadmin.h:152
#define InvalidOffsetNumber
Definition: off.h:26
#define FirstOffsetNumber
Definition: off.h:27
#define RelationNeedsWAL(relation)
Definition: rel.h:639
BlockNumber pages_deleted
Definition: genam.h:105
BlockNumber pages_newly_deleted
Definition: genam.h:104
ItemPointerData t_tid
Definition: itup.h:37
Relation index
Definition: genam.h:69
FullTransactionId ReadNextFullTransactionId(void)
Definition: varsup.c:288
uint64 XLogRecPtr
Definition: xlogdefs.h:21

References Assert(), BufferGetBlockNumber(), BufferGetPage(), END_CRIT_SECTION, FirstOffsetNumber, GistFollowRight, gistGetFakeLSN(), GistPageIsDeleted, GistPageIsLeaf, GistPageSetDeleted(), gistXLogPageDelete(), IndexVacuumInfo::index, InvalidOffsetNumber, ItemPointerGetBlockNumber(), MarkBufferDirty(), PageGetItem(), PageGetItemId(), PageGetMaxOffsetNumber(), PageIndexTupleDelete(), PageIsNew(), IndexBulkDeleteResult::pages_deleted, IndexBulkDeleteResult::pages_newly_deleted, PageSetLSN(), ReadNextFullTransactionId(), RelationNeedsWAL, START_CRIT_SECTION, and IndexTupleData::t_tid.

Referenced by gistvacuum_delete_empty_pages().

◆ gistvacuum_delete_empty_pages()

static void gistvacuum_delete_empty_pages ( IndexVacuumInfo info,
GistVacState vstate 
)
static

Definition at line 504 of file gistvacuum.c.

505{
506 Relation rel = info->index;
507 BlockNumber empty_pages_remaining;
508 uint64 blkno;
509
510 /*
511 * Rescan all inner pages to find those that have empty child pages.
512 */
513 empty_pages_remaining = intset_num_entries(vstate->empty_leaf_set);
515 while (empty_pages_remaining > 0 &&
516 intset_iterate_next(vstate->internal_page_set, &blkno))
517 {
518 Buffer buffer;
519 Page page;
520 OffsetNumber off,
521 maxoff;
523 BlockNumber leafs_to_delete[MaxOffsetNumber];
524 int ntodelete;
525 int deleted;
526
527 buffer = ReadBufferExtended(rel, MAIN_FORKNUM, (BlockNumber) blkno,
528 RBM_NORMAL, info->strategy);
529
530 LockBuffer(buffer, GIST_SHARE);
531 page = (Page) BufferGetPage(buffer);
532
533 if (PageIsNew(page) || GistPageIsDeleted(page) || GistPageIsLeaf(page))
534 {
535 /*
536 * This page was an internal page earlier, but now it's something
537 * else. Shouldn't happen...
538 */
539 Assert(false);
540 UnlockReleaseBuffer(buffer);
541 continue;
542 }
543
544 /*
545 * Scan all the downlinks, and see if any of them point to empty leaf
546 * pages.
547 */
548 maxoff = PageGetMaxOffsetNumber(page);
549 ntodelete = 0;
550 for (off = FirstOffsetNumber;
551 off <= maxoff && ntodelete < maxoff - 1;
552 off = OffsetNumberNext(off))
553 {
554 ItemId iid = PageGetItemId(page, off);
555 IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
556 BlockNumber leafblk;
557
558 leafblk = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
559 if (intset_is_member(vstate->empty_leaf_set, leafblk))
560 {
561 leafs_to_delete[ntodelete] = leafblk;
562 todelete[ntodelete++] = off;
563 }
564 }
565
566 /*
567 * In order to avoid deadlock, child page must be locked before
568 * parent, so we must release the lock on the parent, lock the child,
569 * and then re-acquire the lock the parent. (And we wouldn't want to
570 * do I/O, while holding a lock, anyway.)
571 *
572 * At the instant that we're not holding a lock on the parent, the
573 * downlink might get moved by a concurrent insert, so we must
574 * re-check that it still points to the same child page after we have
575 * acquired both locks. Also, another backend might have inserted a
576 * tuple to the page, so that it is no longer empty. gistdeletepage()
577 * re-checks all these conditions.
578 */
579 LockBuffer(buffer, GIST_UNLOCK);
580
581 deleted = 0;
582 for (int i = 0; i < ntodelete; i++)
583 {
584 Buffer leafbuf;
585
586 /*
587 * Don't remove the last downlink from the parent. That would
588 * confuse the insertion code.
589 */
591 break;
592
593 leafbuf = ReadBufferExtended(rel, MAIN_FORKNUM, leafs_to_delete[i],
594 RBM_NORMAL, info->strategy);
595 LockBuffer(leafbuf, GIST_EXCLUSIVE);
596 gistcheckpage(rel, leafbuf);
597
598 LockBuffer(buffer, GIST_EXCLUSIVE);
599 if (gistdeletepage(info, vstate->stats,
600 buffer, todelete[i] - deleted,
601 leafbuf))
602 deleted++;
603 LockBuffer(buffer, GIST_UNLOCK);
604
605 UnlockReleaseBuffer(leafbuf);
606 }
607
608 ReleaseBuffer(buffer);
609
610 /*
611 * We can stop the scan as soon as we have seen the downlinks, even if
612 * we were not able to remove them all.
613 */
614 empty_pages_remaining -= ntodelete;
615 }
616}
uint32 BlockNumber
Definition: block.h:31
int Buffer
Definition: buf.h:23
void ReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:5373
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:5390
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:5607
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:805
@ RBM_NORMAL
Definition: bufmgr.h:46
uint64_t uint64
Definition: c.h:503
#define GIST_UNLOCK
Definition: gist_private.h:44
#define GIST_EXCLUSIVE
Definition: gist_private.h:43
#define GIST_SHARE
Definition: gist_private.h:42
void gistcheckpage(Relation rel, Buffer buf)
Definition: gistutil.c:785
static bool gistdeletepage(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, Buffer parentBuffer, OffsetNumber downlink, Buffer leafBuffer)
Definition: gistvacuum.c:631
uint64 intset_num_entries(IntegerSet *intset)
Definition: integerset.c:349
bool intset_is_member(IntegerSet *intset, uint64 x)
Definition: integerset.c:553
void intset_begin_iterate(IntegerSet *intset)
Definition: integerset.c:623
bool intset_iterate_next(IntegerSet *intset, uint64 *next)
Definition: integerset.c:642
int i
Definition: isn.c:77
#define OffsetNumberNext(offsetNumber)
Definition: off.h:52
uint16 OffsetNumber
Definition: off.h:24
#define MaxOffsetNumber
Definition: off.h:28
@ MAIN_FORKNUM
Definition: relpath.h:58
IndexBulkDeleteResult * stats
Definition: gistvacuum.c:32
IntegerSet * empty_leaf_set
Definition: gistvacuum.c:42
IntegerSet * internal_page_set
Definition: gistvacuum.c:41
BufferAccessStrategy strategy
Definition: genam.h:76

References Assert(), BufferGetPage(), GistVacState::empty_leaf_set, FirstOffsetNumber, GIST_EXCLUSIVE, GIST_SHARE, GIST_UNLOCK, gistcheckpage(), gistdeletepage(), GistPageIsDeleted, GistPageIsLeaf, i, IndexVacuumInfo::index, GistVacState::internal_page_set, intset_begin_iterate(), intset_is_member(), intset_iterate_next(), intset_num_entries(), ItemPointerGetBlockNumber(), LockBuffer(), MAIN_FORKNUM, MaxOffsetNumber, OffsetNumberNext, PageGetItem(), PageGetItemId(), PageGetMaxOffsetNumber(), PageIsNew(), RBM_NORMAL, ReadBufferExtended(), ReleaseBuffer(), GistVacState::stats, IndexVacuumInfo::strategy, IndexTupleData::t_tid, and UnlockReleaseBuffer().

Referenced by gistvacuumscan().

◆ gistvacuumcleanup()

IndexBulkDeleteResult * gistvacuumcleanup ( IndexVacuumInfo info,
IndexBulkDeleteResult stats 
)

Definition at line 75 of file gistvacuum.c.

76{
77 /* No-op in ANALYZE ONLY mode */
78 if (info->analyze_only)
79 return stats;
80
81 /*
82 * If gistbulkdelete was called, we need not do anything, just return the
83 * stats from the latest gistbulkdelete call. If it wasn't called, we
84 * still need to do a pass over the index, to obtain index statistics.
85 */
86 if (stats == NULL)
87 {
89 gistvacuumscan(info, stats, NULL, NULL);
90 }
91
92 /*
93 * It's quite possible for us to be fooled by concurrent page splits into
94 * double-counting some index tuples, so disbelieve any total that exceeds
95 * the underlying heap's count ... if we know that accurately. Otherwise
96 * this might just make matters worse.
97 */
98 if (!info->estimated_count)
99 {
100 if (stats->num_index_tuples > info->num_heap_tuples)
101 stats->num_index_tuples = info->num_heap_tuples;
102 }
103
104 return stats;
105}
double num_index_tuples
Definition: genam.h:102
double num_heap_tuples
Definition: genam.h:75
bool analyze_only
Definition: genam.h:71
bool estimated_count
Definition: genam.h:73

References IndexVacuumInfo::analyze_only, IndexVacuumInfo::estimated_count, gistvacuumscan(), IndexVacuumInfo::num_heap_tuples, IndexBulkDeleteResult::num_index_tuples, and palloc0().

Referenced by gisthandler().

◆ gistvacuumpage()

static void gistvacuumpage ( GistVacState vstate,
Buffer  buffer 
)
static

Definition at line 308 of file gistvacuum.c.

309{
310 IndexVacuumInfo *info = vstate->info;
312 void *callback_state = vstate->callback_state;
313 Relation rel = info->index;
314 BlockNumber orig_blkno = BufferGetBlockNumber(buffer);
315 Page page;
316 BlockNumber recurse_to;
317
318 /*
319 * orig_blkno is the highest block number reached by the outer
320 * gistvacuumscan() loop. This will be the same as blkno unless we are
321 * recursing to reexamine a previous page.
322 */
323 BlockNumber blkno = orig_blkno;
324
325restart:
326 recurse_to = InvalidBlockNumber;
327
328 /*
329 * We are not going to stay here for a long time, aggressively grab an
330 * exclusive lock.
331 */
332 LockBuffer(buffer, GIST_EXCLUSIVE);
333 page = (Page) BufferGetPage(buffer);
334
335 if (gistPageRecyclable(page))
336 {
337 /* Okay to recycle this page */
338 RecordFreeIndexPage(rel, blkno);
339 vstate->stats->pages_deleted++;
340 vstate->stats->pages_free++;
341 }
342 else if (GistPageIsDeleted(page))
343 {
344 /* Already deleted, but can't recycle yet */
345 vstate->stats->pages_deleted++;
346 }
347 else if (GistPageIsLeaf(page))
348 {
350 int ntodelete = 0;
351 int nremain;
352 GISTPageOpaque opaque = GistPageGetOpaque(page);
354
355 /*
356 * Check whether we need to recurse back to earlier pages. What we
357 * are concerned about is a page split that happened since we started
358 * the vacuum scan. If the split moved some tuples to a lower page
359 * then we might have missed 'em. If so, set up for tail recursion.
360 *
361 * This is similar to the checks we do during searches, when following
362 * a downlink, but we don't need to jump to higher-numbered pages,
363 * because we will process them later, anyway.
364 */
365 if ((GistFollowRight(page) ||
366 vstate->startNSN < GistPageGetNSN(page)) &&
367 (opaque->rightlink != InvalidBlockNumber) &&
368 (opaque->rightlink < orig_blkno))
369 {
370 recurse_to = opaque->rightlink;
371 }
372
373 /*
374 * Scan over all items to see which ones need to be deleted according
375 * to the callback function.
376 */
377 if (callback)
378 {
379 OffsetNumber off;
380
381 for (off = FirstOffsetNumber;
382 off <= maxoff;
383 off = OffsetNumberNext(off))
384 {
385 ItemId iid = PageGetItemId(page, off);
386 IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
387
388 if (callback(&(idxtuple->t_tid), callback_state))
389 todelete[ntodelete++] = off;
390 }
391 }
392
393 /*
394 * Apply any needed deletes. We issue just one WAL record per page,
395 * so as to minimize WAL traffic.
396 */
397 if (ntodelete > 0)
398 {
400
401 MarkBufferDirty(buffer);
402
403 PageIndexMultiDelete(page, todelete, ntodelete);
405
406 if (RelationNeedsWAL(rel))
407 {
408 XLogRecPtr recptr;
409
410 recptr = gistXLogUpdate(buffer,
411 todelete, ntodelete,
412 NULL, 0, InvalidBuffer);
413 PageSetLSN(page, recptr);
414 }
415 else
416 PageSetLSN(page, gistGetFakeLSN(rel));
417
419
420 vstate->stats->tuples_removed += ntodelete;
421 /* must recompute maxoff */
422 maxoff = PageGetMaxOffsetNumber(page);
423 }
424
425 nremain = maxoff - FirstOffsetNumber + 1;
426 if (nremain == 0)
427 {
428 /*
429 * The page is now completely empty. Remember its block number,
430 * so that we will try to delete the page in the second stage.
431 *
432 * Skip this when recursing, because IntegerSet requires that the
433 * values are added in ascending order. The next VACUUM will pick
434 * it up.
435 */
436 if (blkno == orig_blkno)
437 intset_add_member(vstate->empty_leaf_set, blkno);
438 }
439 else
440 vstate->stats->num_index_tuples += nremain;
441 }
442 else
443 {
444 /*
445 * On an internal page, check for "invalid tuples", left behind by an
446 * incomplete page split on PostgreSQL 9.0 or below. These are not
447 * created by newer PostgreSQL versions, but unfortunately, there is
448 * no version number anywhere in a GiST index, so we don't know
449 * whether this index might still contain invalid tuples or not.
450 */
452 OffsetNumber off;
453
454 for (off = FirstOffsetNumber;
455 off <= maxoff;
456 off = OffsetNumberNext(off))
457 {
458 ItemId iid = PageGetItemId(page, off);
459 IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
460
461 if (GistTupleIsInvalid(idxtuple))
462 ereport(LOG,
463 (errmsg("index \"%s\" contains an inner tuple marked as invalid",
465 errdetail("This is caused by an incomplete page split at crash recovery before upgrading to PostgreSQL 9.1."),
466 errhint("Please REINDEX it.")));
467 }
468
469 /*
470 * Remember the block number of this page, so that we can revisit it
471 * later in gistvacuum_delete_empty_pages(), when we search for
472 * parents of empty leaf pages.
473 */
474 if (blkno == orig_blkno)
475 intset_add_member(vstate->internal_page_set, blkno);
476 }
477
478 UnlockReleaseBuffer(buffer);
479
480 /*
481 * This is really tail recursion, but if the compiler is too stupid to
482 * optimize it as such, we'd eat an uncomfortably large amount of stack
483 * space per recursion level (due to the deletable[] array). A failure is
484 * improbable since the number of levels isn't likely to be large ... but
485 * just in case, let's hand-optimize into a loop.
486 */
487 if (recurse_to != InvalidBlockNumber)
488 {
489 blkno = recurse_to;
490
491 /* check for vacuum delay while not holding any buffer lock */
492 vacuum_delay_point(false);
493
494 buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
495 info->strategy);
496 goto restart;
497 }
498}
#define InvalidBlockNumber
Definition: block.h:33
#define InvalidBuffer
Definition: buf.h:25
void PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems)
Definition: bufpage.c:1160
int errdetail(const char *fmt,...)
Definition: elog.c:1204
int errhint(const char *fmt,...)
Definition: elog.c:1318
int errmsg(const char *fmt,...)
Definition: elog.c:1071
#define LOG
Definition: elog.h:31
#define ereport(elevel,...)
Definition: elog.h:149
bool(* IndexBulkDeleteCallback)(ItemPointer itemptr, void *state)
Definition: genam.h:110
#define GistMarkTuplesDeleted(page)
Definition: gist.h:176
#define GistPageGetOpaque(page)
Definition: gist.h:168
#define GistPageGetNSN(page)
Definition: gist.h:187
#define GistTupleIsInvalid(itup)
Definition: gist_private.h:288
bool gistPageRecyclable(Page page)
Definition: gistutil.c:888
XLogRecPtr gistXLogUpdate(Buffer buffer, OffsetNumber *todelete, int ntodelete, IndexTuple *itup, int ituplen, Buffer leftchildbuf)
Definition: gistxlog.c:629
void RecordFreeIndexPage(Relation rel, BlockNumber freeBlock)
Definition: indexfsm.c:52
void intset_add_member(IntegerSet *intset, uint64 x)
Definition: integerset.c:369
#define RelationGetRelationName(relation)
Definition: rel.h:550
BlockNumber rightlink
Definition: gist.h:81
void * callback_state
Definition: gistvacuum.c:34
IndexVacuumInfo * info
Definition: gistvacuum.c:31
IndexBulkDeleteCallback callback
Definition: gistvacuum.c:33
GistNSN startNSN
Definition: gistvacuum.c:35
BlockNumber pages_free
Definition: genam.h:106
double tuples_removed
Definition: genam.h:103
void vacuum_delay_point(bool is_analyze)
Definition: vacuum.c:2404

References BufferGetBlockNumber(), BufferGetPage(), GistVacState::callback, callback(), GistVacState::callback_state, GistVacState::empty_leaf_set, END_CRIT_SECTION, ereport, errdetail(), errhint(), errmsg(), FirstOffsetNumber, GIST_EXCLUSIVE, GistFollowRight, gistGetFakeLSN(), GistMarkTuplesDeleted, GistPageGetNSN, GistPageGetOpaque, GistPageIsDeleted, GistPageIsLeaf, gistPageRecyclable(), GistTupleIsInvalid, gistXLogUpdate(), IndexVacuumInfo::index, GistVacState::info, GistVacState::internal_page_set, intset_add_member(), InvalidBlockNumber, InvalidBuffer, LockBuffer(), LOG, MAIN_FORKNUM, MarkBufferDirty(), MaxOffsetNumber, IndexBulkDeleteResult::num_index_tuples, OffsetNumberNext, PageGetItem(), PageGetItemId(), PageGetMaxOffsetNumber(), PageIndexMultiDelete(), IndexBulkDeleteResult::pages_deleted, IndexBulkDeleteResult::pages_free, PageSetLSN(), RBM_NORMAL, ReadBufferExtended(), RecordFreeIndexPage(), RelationGetRelationName, RelationNeedsWAL, GISTPageOpaqueData::rightlink, START_CRIT_SECTION, GistVacState::startNSN, GistVacState::stats, IndexVacuumInfo::strategy, IndexTupleData::t_tid, IndexBulkDeleteResult::tuples_removed, UnlockReleaseBuffer(), and vacuum_delay_point().

Referenced by gistvacuumscan().

◆ gistvacuumscan()

static void gistvacuumscan ( IndexVacuumInfo info,
IndexBulkDeleteResult stats,
IndexBulkDeleteCallback  callback,
void *  callback_state 
)
static

Definition at line 125 of file gistvacuum.c.

127{
128 Relation rel = info->index;
129 GistVacState vstate;
130 BlockNumber num_pages;
131 bool needLock;
132 MemoryContext oldctx;
134 ReadStream *stream = NULL;
135
136 /*
137 * Reset fields that track information about the entire index now. This
138 * avoids double-counting in the case where a single VACUUM command
139 * requires multiple scans of the index.
140 *
141 * Avoid resetting the tuples_removed and pages_newly_deleted fields here,
142 * since they track information about the VACUUM command, and so must last
143 * across each call to gistvacuumscan().
144 *
145 * (Note that pages_free is treated as state about the whole index, not
146 * the current VACUUM. This is appropriate because RecordFreeIndexPage()
147 * calls are idempotent, and get repeated for the same deleted pages in
148 * some scenarios. The point for us is to track the number of recyclable
149 * pages in the index at the end of the VACUUM command.)
150 */
151 stats->num_pages = 0;
152 stats->estimated_count = false;
153 stats->num_index_tuples = 0;
154 stats->pages_deleted = 0;
155 stats->pages_free = 0;
156
157 /*
158 * Create the integer sets to remember all the internal and the empty leaf
159 * pages in page_set_context. Internally, the integer set will remember
160 * this context so that the subsequent allocations for these integer sets
161 * will be done from the same context.
162 *
163 * XXX the allocation sizes used below pre-date generation context's block
164 * growing code. These values should likely be benchmarked and set to
165 * more suitable values.
166 */
168 "GiST VACUUM page set context",
169 16 * 1024,
170 16 * 1024,
171 16 * 1024);
174 vstate.empty_leaf_set = intset_create();
175 MemoryContextSwitchTo(oldctx);
176
177 /* Set up info to pass down to gistvacuumpage */
178 vstate.info = info;
179 vstate.stats = stats;
180 vstate.callback = callback;
181 vstate.callback_state = callback_state;
182 if (RelationNeedsWAL(rel))
183 vstate.startNSN = GetInsertRecPtr();
184 else
185 vstate.startNSN = gistGetFakeLSN(rel);
186
187 /*
188 * The outer loop iterates over all index pages, in physical order (we
189 * hope the kernel will cooperate in providing read-ahead for speed). It
190 * is critical that we visit all leaf pages, including ones added after we
191 * start the scan, else we might fail to delete some deletable tuples.
192 * Hence, we must repeatedly check the relation length. We must acquire
193 * the relation-extension lock while doing so to avoid a race condition:
194 * if someone else is extending the relation, there is a window where
195 * bufmgr/smgr have created a new all-zero page but it hasn't yet been
196 * write-locked by gistNewBuffer(). If we manage to scan such a page
197 * here, we'll improperly assume it can be recycled. Taking the lock
198 * synchronizes things enough to prevent a problem: either num_pages won't
199 * include the new page, or gistNewBuffer already has write lock on the
200 * buffer and it will be fully initialized before we can examine it. (See
201 * also vacuumlazy.c, which has the same issue.) Also, we need not worry
202 * if a page is added immediately after we look; the page splitting code
203 * already has write-lock on the left page before it adds a right page, so
204 * we must already have processed any tuples due to be moved into such a
205 * page.
206 *
207 * We can skip locking for new or temp relations, however, since no one
208 * else could be accessing them.
209 */
210 needLock = !RELATION_IS_LOCAL(rel);
211
213
214 /*
215 * It is safe to use batchmode as block_range_read_stream_cb takes no
216 * locks.
217 */
221 info->strategy,
222 rel,
225 &p,
226 0);
227 for (;;)
228 {
229 /* Get the current relation length */
230 if (needLock)
232 num_pages = RelationGetNumberOfBlocks(rel);
233 if (needLock)
235
236 /* Quit if we've scanned the whole relation */
237 if (p.current_blocknum >= num_pages)
238 break;
239
240 p.last_exclusive = num_pages;
241
242 /* Iterate over pages, then loop back to recheck relation length */
243 while (true)
244 {
245 Buffer buf;
246
247 /* call vacuum_delay_point while not holding any buffer lock */
248 vacuum_delay_point(false);
249
250 buf = read_stream_next_buffer(stream, NULL);
251
252 if (!BufferIsValid(buf))
253 break;
254
255 gistvacuumpage(&vstate, buf);
256 }
257
258 /*
259 * We have to reset the read stream to use it again. After returning
260 * InvalidBuffer, the read stream API won't invoke our callback again
261 * until the stream has been reset.
262 */
263 read_stream_reset(stream);
264 }
265
266 read_stream_end(stream);
267
268 /*
269 * If we found any recyclable pages (and recorded them in the FSM), then
270 * forcibly update the upper-level FSM pages to ensure that searchers can
271 * find them. It's possible that the pages were also found during
272 * previous scans and so this is a waste of time, but it's cheap enough
273 * relative to scanning the index that it shouldn't matter much, and
274 * making sure that free pages are available sooner not later seems
275 * worthwhile.
276 *
277 * Note that if no recyclable pages exist, we don't bother vacuuming the
278 * FSM at all.
279 */
280 if (stats->pages_free > 0)
282
283 /* update statistics */
284 stats->num_pages = num_pages;
285
286 /*
287 * If we saw any empty pages, try to unlink them from the tree so that
288 * they can be reused.
289 */
290 gistvacuum_delete_empty_pages(info, &vstate);
291
292 /* we don't need the internal and empty page sets anymore */
294 vstate.page_set_context = NULL;
295 vstate.internal_page_set = NULL;
296 vstate.empty_leaf_set = NULL;
297}
#define RelationGetNumberOfBlocks(reln)
Definition: bufmgr.h:283
static bool BufferIsValid(Buffer bufnum)
Definition: bufmgr.h:368
MemoryContext GenerationContextCreate(MemoryContext parent, const char *name, Size minContextSize, Size initBlockSize, Size maxBlockSize)
Definition: generation.c:160
#define GIST_ROOT_BLKNO
Definition: gist_private.h:262
static void gistvacuumpage(GistVacState *vstate, Buffer buffer)
Definition: gistvacuum.c:308
static void gistvacuum_delete_empty_pages(IndexVacuumInfo *info, GistVacState *vstate)
Definition: gistvacuum.c:504
void IndexFreeSpaceMapVacuum(Relation rel)
Definition: indexfsm.c:71
IntegerSet * intset_create(void)
Definition: integerset.c:283
void LockRelationForExtension(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:424
void UnlockRelationForExtension(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:474
#define ExclusiveLock
Definition: lockdefs.h:42
MemoryContext CurrentMemoryContext
Definition: mcxt.c:159
void MemoryContextDelete(MemoryContext context)
Definition: mcxt.c:485
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:124
static char * buf
Definition: pg_test_fsync.c:72
void read_stream_reset(ReadStream *stream)
Definition: read_stream.c:1010
Buffer read_stream_next_buffer(ReadStream *stream, void **per_buffer_data)
Definition: read_stream.c:770
ReadStream * read_stream_begin_relation(int flags, BufferAccessStrategy strategy, Relation rel, ForkNumber forknum, ReadStreamBlockNumberCB callback, void *callback_private_data, size_t per_buffer_data_size)
Definition: read_stream.c:716
void read_stream_end(ReadStream *stream)
Definition: read_stream.c:1055
BlockNumber block_range_read_stream_cb(ReadStream *stream, void *callback_private_data, void *per_buffer_data)
Definition: read_stream.c:162
#define READ_STREAM_MAINTENANCE
Definition: read_stream.h:28
#define READ_STREAM_USE_BATCHING
Definition: read_stream.h:64
#define READ_STREAM_FULL
Definition: read_stream.h:43
#define RELATION_IS_LOCAL(relation)
Definition: rel.h:659
MemoryContext page_set_context
Definition: gistvacuum.c:43
BlockNumber num_pages
Definition: genam.h:100
XLogRecPtr GetInsertRecPtr(void)
Definition: xlog.c:6670

References block_range_read_stream_cb(), buf, BufferIsValid(), GistVacState::callback, callback(), GistVacState::callback_state, BlockRangeReadStreamPrivate::current_blocknum, CurrentMemoryContext, GistVacState::empty_leaf_set, IndexBulkDeleteResult::estimated_count, ExclusiveLock, GenerationContextCreate(), GetInsertRecPtr(), GIST_ROOT_BLKNO, gistGetFakeLSN(), gistvacuum_delete_empty_pages(), gistvacuumpage(), IndexVacuumInfo::index, IndexFreeSpaceMapVacuum(), GistVacState::info, GistVacState::internal_page_set, intset_create(), BlockRangeReadStreamPrivate::last_exclusive, LockRelationForExtension(), MAIN_FORKNUM, MemoryContextDelete(), MemoryContextSwitchTo(), IndexBulkDeleteResult::num_index_tuples, IndexBulkDeleteResult::num_pages, GistVacState::page_set_context, IndexBulkDeleteResult::pages_deleted, IndexBulkDeleteResult::pages_free, read_stream_begin_relation(), read_stream_end(), READ_STREAM_FULL, READ_STREAM_MAINTENANCE, read_stream_next_buffer(), read_stream_reset(), READ_STREAM_USE_BATCHING, RELATION_IS_LOCAL, RelationGetNumberOfBlocks, RelationNeedsWAL, GistVacState::startNSN, GistVacState::stats, IndexVacuumInfo::strategy, UnlockRelationForExtension(), and vacuum_delay_point().

Referenced by gistbulkdelete(), and gistvacuumcleanup().