PostgreSQL Source Code git master
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
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:1973
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 630 of file gistvacuum.c.

633{
634 Page parentPage = BufferGetPage(parentBuffer);
635 Page leafPage = BufferGetPage(leafBuffer);
636 ItemId iid;
637 IndexTuple idxtuple;
638 XLogRecPtr recptr;
640
641 /*
642 * Check that the leaf is still empty and deletable.
643 */
644 if (!GistPageIsLeaf(leafPage))
645 {
646 /* a leaf page should never become a non-leaf page */
647 Assert(false);
648 return false;
649 }
650
651 if (GistFollowRight(leafPage))
652 return false; /* don't mess with a concurrent page split */
653
655 return false; /* not empty anymore */
656
657 /*
658 * Ok, the leaf is deletable. Is the downlink in the parent page still
659 * valid? It might have been moved by a concurrent insert. We could try
660 * to re-find it by scanning the page again, possibly moving right if the
661 * was split. But for now, let's keep it simple and just give up. The
662 * next VACUUM will pick it up.
663 */
664 if (PageIsNew(parentPage) || GistPageIsDeleted(parentPage) ||
665 GistPageIsLeaf(parentPage))
666 {
667 /* shouldn't happen, internal pages are never deleted */
668 Assert(false);
669 return false;
670 }
671
672 if (PageGetMaxOffsetNumber(parentPage) < downlink
674 return false;
675
676 iid = PageGetItemId(parentPage, downlink);
677 idxtuple = (IndexTuple) PageGetItem(parentPage, iid);
678 if (BufferGetBlockNumber(leafBuffer) !=
679 ItemPointerGetBlockNumber(&(idxtuple->t_tid)))
680 return false;
681
682 /*
683 * All good, proceed with the deletion.
684 *
685 * The page cannot be immediately recycled, because in-progress scans that
686 * saw the downlink might still visit it. Mark the page with the current
687 * next-XID counter, so that we know when it can be recycled. Once that
688 * XID becomes older than GlobalXmin, we know that all scans that are
689 * currently in progress must have ended. (That's much more conservative
690 * than needed, but let's keep it safe and simple.)
691 */
693
695
696 /* mark the page as deleted */
697 MarkBufferDirty(leafBuffer);
698 GistPageSetDeleted(leafPage, txid);
699 stats->pages_newly_deleted++;
700 stats->pages_deleted++;
701
702 /* remove the downlink from the parent */
703 MarkBufferDirty(parentBuffer);
704 PageIndexTupleDelete(parentPage, downlink);
705
706 if (RelationNeedsWAL(info->index))
707 recptr = gistXLogPageDelete(leafBuffer, txid, parentBuffer, downlink);
708 else
709 recptr = gistGetFakeLSN(info->index);
710 PageSetLSN(parentPage, recptr);
711 PageSetLSN(leafPage, recptr);
712
714
715 return true;
716}
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 503 of file gistvacuum.c.

504{
505 Relation rel = info->index;
506 BlockNumber empty_pages_remaining;
507 uint64 blkno;
508
509 /*
510 * Rescan all inner pages to find those that have empty child pages.
511 */
512 empty_pages_remaining = intset_num_entries(vstate->empty_leaf_set);
514 while (empty_pages_remaining > 0 &&
515 intset_iterate_next(vstate->internal_page_set, &blkno))
516 {
517 Buffer buffer;
518 Page page;
519 OffsetNumber off,
520 maxoff;
522 BlockNumber leafs_to_delete[MaxOffsetNumber];
523 int ntodelete;
524 int deleted;
525
526 buffer = ReadBufferExtended(rel, MAIN_FORKNUM, (BlockNumber) blkno,
527 RBM_NORMAL, info->strategy);
528
529 LockBuffer(buffer, GIST_SHARE);
530 page = (Page) BufferGetPage(buffer);
531
532 if (PageIsNew(page) || GistPageIsDeleted(page) || GistPageIsLeaf(page))
533 {
534 /*
535 * This page was an internal page earlier, but now it's something
536 * else. Shouldn't happen...
537 */
538 Assert(false);
539 UnlockReleaseBuffer(buffer);
540 continue;
541 }
542
543 /*
544 * Scan all the downlinks, and see if any of them point to empty leaf
545 * pages.
546 */
547 maxoff = PageGetMaxOffsetNumber(page);
548 ntodelete = 0;
549 for (off = FirstOffsetNumber;
550 off <= maxoff && ntodelete < maxoff - 1;
551 off = OffsetNumberNext(off))
552 {
553 ItemId iid = PageGetItemId(page, off);
554 IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
555 BlockNumber leafblk;
556
557 leafblk = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
558 if (intset_is_member(vstate->empty_leaf_set, leafblk))
559 {
560 leafs_to_delete[ntodelete] = leafblk;
561 todelete[ntodelete++] = off;
562 }
563 }
564
565 /*
566 * In order to avoid deadlock, child page must be locked before
567 * parent, so we must release the lock on the parent, lock the child,
568 * and then re-acquire the lock the parent. (And we wouldn't want to
569 * do I/O, while holding a lock, anyway.)
570 *
571 * At the instant that we're not holding a lock on the parent, the
572 * downlink might get moved by a concurrent insert, so we must
573 * re-check that it still points to the same child page after we have
574 * acquired both locks. Also, another backend might have inserted a
575 * tuple to the page, so that it is no longer empty. gistdeletepage()
576 * re-checks all these conditions.
577 */
578 LockBuffer(buffer, GIST_UNLOCK);
579
580 deleted = 0;
581 for (int i = 0; i < ntodelete; i++)
582 {
583 Buffer leafbuf;
584
585 /*
586 * Don't remove the last downlink from the parent. That would
587 * confuse the insertion code.
588 */
590 break;
591
592 leafbuf = ReadBufferExtended(rel, MAIN_FORKNUM, leafs_to_delete[i],
593 RBM_NORMAL, info->strategy);
594 LockBuffer(leafbuf, GIST_EXCLUSIVE);
595 gistcheckpage(rel, leafbuf);
596
597 LockBuffer(buffer, GIST_EXCLUSIVE);
598 if (gistdeletepage(info, vstate->stats,
599 buffer, todelete[i] - deleted,
600 leafbuf))
601 deleted++;
602 LockBuffer(buffer, GIST_UNLOCK);
603
604 UnlockReleaseBuffer(leafbuf);
605 }
606
607 ReleaseBuffer(buffer);
608
609 /*
610 * We can stop the scan as soon as we have seen the downlinks, even if
611 * we were not able to remove them all.
612 */
613 empty_pages_remaining -= ntodelete;
614 }
615}
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:630
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 307 of file gistvacuum.c.

308{
309 IndexVacuumInfo *info = vstate->info;
311 void *callback_state = vstate->callback_state;
312 Relation rel = info->index;
313 BlockNumber orig_blkno = BufferGetBlockNumber(buffer);
314 Page page;
315 BlockNumber recurse_to;
316
317 /*
318 * orig_blkno is the highest block number reached by the outer
319 * gistvacuumscan() loop. This will be the same as blkno unless we are
320 * recursing to reexamine a previous page.
321 */
322 BlockNumber blkno = orig_blkno;
323
324restart:
325 recurse_to = InvalidBlockNumber;
326
327 /*
328 * We are not going to stay here for a long time, aggressively grab an
329 * exclusive lock.
330 */
331 LockBuffer(buffer, GIST_EXCLUSIVE);
332 page = (Page) BufferGetPage(buffer);
333
334 if (gistPageRecyclable(page))
335 {
336 /* Okay to recycle this page */
337 RecordFreeIndexPage(rel, blkno);
338 vstate->stats->pages_deleted++;
339 vstate->stats->pages_free++;
340 }
341 else if (GistPageIsDeleted(page))
342 {
343 /* Already deleted, but can't recycle yet */
344 vstate->stats->pages_deleted++;
345 }
346 else if (GistPageIsLeaf(page))
347 {
349 int ntodelete = 0;
350 int nremain;
351 GISTPageOpaque opaque = GistPageGetOpaque(page);
353
354 /*
355 * Check whether we need to recurse back to earlier pages. What we
356 * are concerned about is a page split that happened since we started
357 * the vacuum scan. If the split moved some tuples to a lower page
358 * then we might have missed 'em. If so, set up for tail recursion.
359 *
360 * This is similar to the checks we do during searches, when following
361 * a downlink, but we don't need to jump to higher-numbered pages,
362 * because we will process them later, anyway.
363 */
364 if ((GistFollowRight(page) ||
365 vstate->startNSN < GistPageGetNSN(page)) &&
366 (opaque->rightlink != InvalidBlockNumber) &&
367 (opaque->rightlink < orig_blkno))
368 {
369 recurse_to = opaque->rightlink;
370 }
371
372 /*
373 * Scan over all items to see which ones need to be deleted according
374 * to the callback function.
375 */
376 if (callback)
377 {
378 OffsetNumber off;
379
380 for (off = FirstOffsetNumber;
381 off <= maxoff;
382 off = OffsetNumberNext(off))
383 {
384 ItemId iid = PageGetItemId(page, off);
385 IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
386
387 if (callback(&(idxtuple->t_tid), callback_state))
388 todelete[ntodelete++] = off;
389 }
390 }
391
392 /*
393 * Apply any needed deletes. We issue just one WAL record per page,
394 * so as to minimize WAL traffic.
395 */
396 if (ntodelete > 0)
397 {
399
400 MarkBufferDirty(buffer);
401
402 PageIndexMultiDelete(page, todelete, ntodelete);
404
405 if (RelationNeedsWAL(rel))
406 {
407 XLogRecPtr recptr;
408
409 recptr = gistXLogUpdate(buffer,
410 todelete, ntodelete,
411 NULL, 0, InvalidBuffer);
412 PageSetLSN(page, recptr);
413 }
414 else
415 PageSetLSN(page, gistGetFakeLSN(rel));
416
418
419 vstate->stats->tuples_removed += ntodelete;
420 /* must recompute maxoff */
421 maxoff = PageGetMaxOffsetNumber(page);
422 }
423
424 nremain = maxoff - FirstOffsetNumber + 1;
425 if (nremain == 0)
426 {
427 /*
428 * The page is now completely empty. Remember its block number,
429 * so that we will try to delete the page in the second stage.
430 *
431 * Skip this when recursing, because IntegerSet requires that the
432 * values are added in ascending order. The next VACUUM will pick
433 * it up.
434 */
435 if (blkno == orig_blkno)
436 intset_add_member(vstate->empty_leaf_set, blkno);
437 }
438 else
439 vstate->stats->num_index_tuples += nremain;
440 }
441 else
442 {
443 /*
444 * On an internal page, check for "invalid tuples", left behind by an
445 * incomplete page split on PostgreSQL 9.0 or below. These are not
446 * created by newer PostgreSQL versions, but unfortunately, there is
447 * no version number anywhere in a GiST index, so we don't know
448 * whether this index might still contain invalid tuples or not.
449 */
451 OffsetNumber off;
452
453 for (off = FirstOffsetNumber;
454 off <= maxoff;
455 off = OffsetNumberNext(off))
456 {
457 ItemId iid = PageGetItemId(page, off);
458 IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
459
460 if (GistTupleIsInvalid(idxtuple))
461 ereport(LOG,
462 (errmsg("index \"%s\" contains an inner tuple marked as invalid",
464 errdetail("This is caused by an incomplete page split at crash recovery before upgrading to PostgreSQL 9.1."),
465 errhint("Please REINDEX it.")));
466 }
467
468 /*
469 * Remember the block number of this page, so that we can revisit it
470 * later in gistvacuum_delete_empty_pages(), when we search for
471 * parents of empty leaf pages.
472 */
473 if (blkno == orig_blkno)
474 intset_add_member(vstate->internal_page_set, blkno);
475 }
476
477 UnlockReleaseBuffer(buffer);
478
479 /*
480 * This is really tail recursion, but if the compiler is too stupid to
481 * optimize it as such, we'd eat an uncomfortably large amount of stack
482 * space per recursion level (due to the deletable[] array). A failure is
483 * improbable since the number of levels isn't likely to be large ... but
484 * just in case, let's hand-optimize into a loop.
485 */
486 if (recurse_to != InvalidBlockNumber)
487 {
488 blkno = recurse_to;
489
490 /* check for vacuum delay while not holding any buffer lock */
491 vacuum_delay_point(false);
492
493 buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
494 info->strategy);
495 goto restart;
496 }
497}
#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 */
220 info->strategy,
221 rel,
224 &p,
225 0);
226 for (;;)
227 {
228 /* Get the current relation length */
229 if (needLock)
231 num_pages = RelationGetNumberOfBlocks(rel);
232 if (needLock)
234
235 /* Quit if we've scanned the whole relation */
236 if (p.current_blocknum >= num_pages)
237 break;
238
239 p.last_exclusive = num_pages;
240
241 /* Iterate over pages, then loop back to recheck relation length */
242 while (true)
243 {
244 Buffer buf;
245
246 /* call vacuum_delay_point while not holding any buffer lock */
247 vacuum_delay_point(false);
248
249 buf = read_stream_next_buffer(stream, NULL);
250
251 if (!BufferIsValid(buf))
252 break;
253
254 gistvacuumpage(&vstate, buf);
255 }
256
257 /*
258 * We have to reset the read stream to use it again. After returning
259 * InvalidBuffer, the read stream API won't invoke our callback again
260 * until the stream has been reset.
261 */
262 read_stream_reset(stream);
263 }
264
265 read_stream_end(stream);
266
267 /*
268 * If we found any recyclable pages (and recorded them in the FSM), then
269 * forcibly update the upper-level FSM pages to ensure that searchers can
270 * find them. It's possible that the pages were also found during
271 * previous scans and so this is a waste of time, but it's cheap enough
272 * relative to scanning the index that it shouldn't matter much, and
273 * making sure that free pages are available sooner not later seems
274 * worthwhile.
275 *
276 * Note that if no recyclable pages exist, we don't bother vacuuming the
277 * FSM at all.
278 */
279 if (stats->pages_free > 0)
281
282 /* update statistics */
283 stats->num_pages = num_pages;
284
285 /*
286 * If we saw any empty pages, try to unlink them from the tree so that
287 * they can be reused.
288 */
289 gistvacuum_delete_empty_pages(info, &vstate);
290
291 /* we don't need the internal and empty page sets anymore */
293 vstate.page_set_context = NULL;
294 vstate.internal_page_set = NULL;
295 vstate.empty_leaf_set = NULL;
296}
#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:307
static void gistvacuum_delete_empty_pages(IndexVacuumInfo *info, GistVacState *vstate)
Definition: gistvacuum.c:503
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_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_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().