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 "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, BlockNumber blkno, BlockNumber orig_blkno)
 
static void gistvacuum_delete_empty_pages (IndexVacuumInfo *info, GistVacState *vstate)
 
static bool gistdeletepage (IndexVacuumInfo *info, IndexBulkDeleteResult *stats, Buffer buffer, 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.

References gistvacuumscan(), and palloc0().

Referenced by gisthandler().

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 callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
Definition: test_ifaddrs.c:48
void * palloc0(Size size)
Definition: mcxt.c:1093
static void gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
Definition: gistvacuum.c:125

◆ gistdeletepage()

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

Definition at line 582 of file gistvacuum.c.

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().

585 {
586  Page parentPage = BufferGetPage(parentBuffer);
587  Page leafPage = BufferGetPage(leafBuffer);
588  ItemId iid;
589  IndexTuple idxtuple;
590  XLogRecPtr recptr;
591  FullTransactionId txid;
592 
593  /*
594  * Check that the leaf is still empty and deletable.
595  */
596  if (!GistPageIsLeaf(leafPage))
597  {
598  /* a leaf page should never become a non-leaf page */
599  Assert(false);
600  return false;
601  }
602 
603  if (GistFollowRight(leafPage))
604  return false; /* don't mess with a concurrent page split */
605 
607  return false; /* not empty anymore */
608 
609  /*
610  * Ok, the leaf is deletable. Is the downlink in the parent page still
611  * valid? It might have been moved by a concurrent insert. We could try
612  * to re-find it by scanning the page again, possibly moving right if the
613  * was split. But for now, let's keep it simple and just give up. The
614  * next VACUUM will pick it up.
615  */
616  if (PageIsNew(parentPage) || GistPageIsDeleted(parentPage) ||
617  GistPageIsLeaf(parentPage))
618  {
619  /* shouldn't happen, internal pages are never deleted */
620  Assert(false);
621  return false;
622  }
623 
624  if (PageGetMaxOffsetNumber(parentPage) < downlink
625  || PageGetMaxOffsetNumber(parentPage) <= FirstOffsetNumber)
626  return false;
627 
628  iid = PageGetItemId(parentPage, downlink);
629  idxtuple = (IndexTuple) PageGetItem(parentPage, iid);
630  if (BufferGetBlockNumber(leafBuffer) !=
631  ItemPointerGetBlockNumber(&(idxtuple->t_tid)))
632  return false;
633 
634  /*
635  * All good, proceed with the deletion.
636  *
637  * The page cannot be immediately recycled, because in-progress scans that
638  * saw the downlink might still visit it. Mark the page with the current
639  * next-XID counter, so that we know when it can be recycled. Once that
640  * XID becomes older than GlobalXmin, we know that all scans that are
641  * currently in progress must have ended. (That's much more conservative
642  * than needed, but let's keep it safe and simple.)
643  */
644  txid = ReadNextFullTransactionId();
645 
647 
648  /* mark the page as deleted */
649  MarkBufferDirty(leafBuffer);
650  GistPageSetDeleted(leafPage, txid);
651  stats->pages_newly_deleted++;
652  stats->pages_deleted++;
653 
654  /* remove the downlink from the parent */
655  MarkBufferDirty(parentBuffer);
656  PageIndexTupleDelete(parentPage, downlink);
657 
658  if (RelationNeedsWAL(info->index))
659  recptr = gistXLogPageDelete(leafBuffer, txid, parentBuffer, downlink);
660  else
661  recptr = gistGetFakeLSN(info->index);
662  PageSetLSN(parentPage, recptr);
663  PageSetLSN(leafPage, recptr);
664 
666 
667  return true;
668 }
#define GistFollowRight(page)
Definition: gist.h:182
#define GistPageIsDeleted(page)
Definition: gist.h:172
void PageIndexTupleDelete(Page page, OffsetNumber offnum)
Definition: bufpage.c:1045
void MarkBufferDirty(Buffer buffer)
Definition: bufmgr.c:1556
ItemPointerData t_tid
Definition: itup.h:37
#define END_CRIT_SECTION()
Definition: miscadmin.h:149
#define START_CRIT_SECTION()
Definition: miscadmin.h:147
Relation index
Definition: genam.h:46
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:357
XLogRecPtr gistGetFakeLSN(Relation rel)
Definition: gistutil.c:1024
#define FirstOffsetNumber
Definition: off.h:27
IndexTupleData * IndexTuple
Definition: itup.h:53
BlockNumber pages_deleted
Definition: genam.h:81
#define BufferGetPage(buffer)
Definition: bufmgr.h:169
static void GistPageSetDeleted(Page page, FullTransactionId deletexid)
Definition: gist.h:204
FullTransactionId ReadNextFullTransactionId(void)
Definition: varsup.c:261
#define GistPageIsLeaf(page)
Definition: gist.h:169
#define PageGetItemId(page, offsetNumber)
Definition: bufpage.h:235
#define InvalidOffsetNumber
Definition: off.h:26
uint64 XLogRecPtr
Definition: xlogdefs.h:21
#define Assert(condition)
Definition: c.h:804
#define RelationNeedsWAL(relation)
Definition: rel.h:601
BlockNumber BufferGetBlockNumber(Buffer buffer)
Definition: bufmgr.c:2752
#define PageIsNew(page)
Definition: bufpage.h:229
XLogRecPtr gistXLogPageDelete(Buffer buffer, FullTransactionId xid, Buffer parentBuffer, OffsetNumber downlinkOffset)
Definition: gistxlog.c:557
BlockNumber pages_newly_deleted
Definition: genam.h:80
#define ItemPointerGetBlockNumber(pointer)
Definition: itemptr.h:98
#define PageSetLSN(page, lsn)
Definition: bufpage.h:368
#define PageGetItem(page, itemId)
Definition: bufpage.h:340
Pointer Page
Definition: bufpage.h:78

◆ gistvacuum_delete_empty_pages()

static void gistvacuum_delete_empty_pages ( IndexVacuumInfo info,
GistVacState vstate 
)
static

Definition at line 455 of file gistvacuum.c.

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, GistSortedBuildPageState::page, PageGetItem, PageGetItemId, PageGetMaxOffsetNumber, PageIsNew, RBM_NORMAL, ReadBufferExtended(), ReleaseBuffer(), GistVacState::stats, IndexVacuumInfo::strategy, IndexTupleData::t_tid, and UnlockReleaseBuffer().

Referenced by gistvacuumscan().

456 {
457  Relation rel = info->index;
458  BlockNumber empty_pages_remaining;
459  uint64 blkno;
460 
461  /*
462  * Rescan all inner pages to find those that have empty child pages.
463  */
464  empty_pages_remaining = intset_num_entries(vstate->empty_leaf_set);
466  while (empty_pages_remaining > 0 &&
467  intset_iterate_next(vstate->internal_page_set, &blkno))
468  {
469  Buffer buffer;
470  Page page;
471  OffsetNumber off,
472  maxoff;
473  OffsetNumber todelete[MaxOffsetNumber];
474  BlockNumber leafs_to_delete[MaxOffsetNumber];
475  int ntodelete;
476  int deleted;
477 
478  buffer = ReadBufferExtended(rel, MAIN_FORKNUM, (BlockNumber) blkno,
479  RBM_NORMAL, info->strategy);
480 
481  LockBuffer(buffer, GIST_SHARE);
482  page = (Page) BufferGetPage(buffer);
483 
484  if (PageIsNew(page) || GistPageIsDeleted(page) || GistPageIsLeaf(page))
485  {
486  /*
487  * This page was an internal page earlier, but now it's something
488  * else. Shouldn't happen...
489  */
490  Assert(false);
491  UnlockReleaseBuffer(buffer);
492  continue;
493  }
494 
495  /*
496  * Scan all the downlinks, and see if any of them point to empty leaf
497  * pages.
498  */
499  maxoff = PageGetMaxOffsetNumber(page);
500  ntodelete = 0;
501  for (off = FirstOffsetNumber;
502  off <= maxoff && ntodelete < maxoff - 1;
503  off = OffsetNumberNext(off))
504  {
505  ItemId iid = PageGetItemId(page, off);
506  IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
507  BlockNumber leafblk;
508 
509  leafblk = ItemPointerGetBlockNumber(&(idxtuple->t_tid));
510  if (intset_is_member(vstate->empty_leaf_set, leafblk))
511  {
512  leafs_to_delete[ntodelete] = leafblk;
513  todelete[ntodelete++] = off;
514  }
515  }
516 
517  /*
518  * In order to avoid deadlock, child page must be locked before
519  * parent, so we must release the lock on the parent, lock the child,
520  * and then re-acquire the lock the parent. (And we wouldn't want to
521  * do I/O, while holding a lock, anyway.)
522  *
523  * At the instant that we're not holding a lock on the parent, the
524  * downlink might get moved by a concurrent insert, so we must
525  * re-check that it still points to the same child page after we have
526  * acquired both locks. Also, another backend might have inserted a
527  * tuple to the page, so that it is no longer empty. gistdeletepage()
528  * re-checks all these conditions.
529  */
530  LockBuffer(buffer, GIST_UNLOCK);
531 
532  deleted = 0;
533  for (int i = 0; i < ntodelete; i++)
534  {
535  Buffer leafbuf;
536 
537  /*
538  * Don't remove the last downlink from the parent. That would
539  * confuse the insertion code.
540  */
542  break;
543 
544  leafbuf = ReadBufferExtended(rel, MAIN_FORKNUM, leafs_to_delete[i],
545  RBM_NORMAL, info->strategy);
546  LockBuffer(leafbuf, GIST_EXCLUSIVE);
547  gistcheckpage(rel, leafbuf);
548 
549  LockBuffer(buffer, GIST_EXCLUSIVE);
550  if (gistdeletepage(info, vstate->stats,
551  buffer, todelete[i] - deleted,
552  leafbuf))
553  deleted++;
554  LockBuffer(buffer, GIST_UNLOCK);
555 
556  UnlockReleaseBuffer(leafbuf);
557  }
558 
559  ReleaseBuffer(buffer);
560 
561  /*
562  * We can stop the scan as soon as we have seen the downlinks, even if
563  * we were not able to remove them all.
564  */
565  empty_pages_remaining -= ntodelete;
566  }
567 }
#define GistPageIsDeleted(page)
Definition: gist.h:172
#define MaxOffsetNumber
Definition: off.h:28
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:741
ItemPointerData t_tid
Definition: itup.h:37
BufferAccessStrategy strategy
Definition: genam.h:52
bool intset_iterate_next(IntegerSet *intset, uint64 *next)
Definition: integerset.c:646
Relation index
Definition: genam.h:46
#define GIST_UNLOCK
Definition: gist_private.h:44
uint32 BlockNumber
Definition: block.h:31
void ReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3772
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:357
uint16 OffsetNumber
Definition: off.h:24
IndexBulkDeleteResult * stats
Definition: gistvacuum.c:31
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3795
static bool gistdeletepage(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, Buffer buffer, OffsetNumber downlink, Buffer leafBuffer)
Definition: gistvacuum.c:582
#define FirstOffsetNumber
Definition: off.h:27
IndexTupleData * IndexTuple
Definition: itup.h:53
IntegerSet * empty_leaf_set
Definition: gistvacuum.c:41
#define BufferGetPage(buffer)
Definition: bufmgr.h:169
void intset_begin_iterate(IntegerSet *intset)
Definition: integerset.c:627
#define GistPageIsLeaf(page)
Definition: gist.h:169
#define PageGetItemId(page, offsetNumber)
Definition: bufpage.h:235
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:4011
#define Assert(condition)
Definition: c.h:804
IntegerSet * internal_page_set
Definition: gistvacuum.c:40
void gistcheckpage(Relation rel, Buffer buf)
Definition: gistutil.c:784
#define OffsetNumberNext(offsetNumber)
Definition: off.h:52
#define GIST_SHARE
Definition: gist_private.h:42
#define PageIsNew(page)
Definition: bufpage.h:229
int i
bool intset_is_member(IntegerSet *intset, uint64 x)
Definition: integerset.c:555
#define ItemPointerGetBlockNumber(pointer)
Definition: itemptr.h:98
#define GIST_EXCLUSIVE
Definition: gist_private.h:43
int Buffer
Definition: buf.h:23
#define PageGetItem(page, itemId)
Definition: bufpage.h:340
Pointer Page
Definition: bufpage.h:78
uint64 intset_num_entries(IntegerSet *intset)
Definition: integerset.c:351

◆ gistvacuumcleanup()

IndexBulkDeleteResult* gistvacuumcleanup ( IndexVacuumInfo info,
IndexBulkDeleteResult stats 
)

Definition at line 75 of file gistvacuum.c.

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

Referenced by gisthandler().

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 }
bool analyze_only
Definition: genam.h:47
void * palloc0(Size size)
Definition: mcxt.c:1093
double num_heap_tuples
Definition: genam.h:51
double num_index_tuples
Definition: genam.h:78
bool estimated_count
Definition: genam.h:49
static void gistvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
Definition: gistvacuum.c:125

◆ gistvacuumpage()

static void gistvacuumpage ( GistVacState vstate,
BlockNumber  blkno,
BlockNumber  orig_blkno 
)
static

Definition at line 266 of file gistvacuum.c.

References 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, GistSortedBuildPageState::page, 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().

267 {
268  IndexVacuumInfo *info = vstate->info;
270  void *callback_state = vstate->callback_state;
271  Relation rel = info->index;
272  Buffer buffer;
273  Page page;
274  BlockNumber recurse_to;
275 
276 restart:
277  recurse_to = InvalidBlockNumber;
278 
279  /* call vacuum_delay_point while not holding any buffer lock */
281 
282  buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
283  info->strategy);
284 
285  /*
286  * We are not going to stay here for a long time, aggressively grab an
287  * exclusive lock.
288  */
289  LockBuffer(buffer, GIST_EXCLUSIVE);
290  page = (Page) BufferGetPage(buffer);
291 
292  if (gistPageRecyclable(page))
293  {
294  /* Okay to recycle this page */
295  RecordFreeIndexPage(rel, blkno);
296  vstate->stats->pages_deleted++;
297  vstate->stats->pages_free++;
298  }
299  else if (GistPageIsDeleted(page))
300  {
301  /* Already deleted, but can't recycle yet */
302  vstate->stats->pages_deleted++;
303  }
304  else if (GistPageIsLeaf(page))
305  {
306  OffsetNumber todelete[MaxOffsetNumber];
307  int ntodelete = 0;
308  int nremain;
309  GISTPageOpaque opaque = GistPageGetOpaque(page);
310  OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
311 
312  /*
313  * Check whether we need to recurse back to earlier pages. What we
314  * are concerned about is a page split that happened since we started
315  * the vacuum scan. If the split moved some tuples to a lower page
316  * then we might have missed 'em. If so, set up for tail recursion.
317  *
318  * This is similar to the checks we do during searches, when following
319  * a downlink, but we don't need to jump to higher-numbered pages,
320  * because we will process them later, anyway.
321  */
322  if ((GistFollowRight(page) ||
323  vstate->startNSN < GistPageGetNSN(page)) &&
324  (opaque->rightlink != InvalidBlockNumber) &&
325  (opaque->rightlink < orig_blkno))
326  {
327  recurse_to = opaque->rightlink;
328  }
329 
330  /*
331  * Scan over all items to see which ones need to be deleted according
332  * to the callback function.
333  */
334  if (callback)
335  {
336  OffsetNumber off;
337 
338  for (off = FirstOffsetNumber;
339  off <= maxoff;
340  off = OffsetNumberNext(off))
341  {
342  ItemId iid = PageGetItemId(page, off);
343  IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
344 
345  if (callback(&(idxtuple->t_tid), callback_state))
346  todelete[ntodelete++] = off;
347  }
348  }
349 
350  /*
351  * Apply any needed deletes. We issue just one WAL record per page,
352  * so as to minimize WAL traffic.
353  */
354  if (ntodelete > 0)
355  {
357 
358  MarkBufferDirty(buffer);
359 
360  PageIndexMultiDelete(page, todelete, ntodelete);
361  GistMarkTuplesDeleted(page);
362 
363  if (RelationNeedsWAL(rel))
364  {
365  XLogRecPtr recptr;
366 
367  recptr = gistXLogUpdate(buffer,
368  todelete, ntodelete,
369  NULL, 0, InvalidBuffer);
370  PageSetLSN(page, recptr);
371  }
372  else
373  PageSetLSN(page, gistGetFakeLSN(rel));
374 
376 
377  vstate->stats->tuples_removed += ntodelete;
378  /* must recompute maxoff */
379  maxoff = PageGetMaxOffsetNumber(page);
380  }
381 
382  nremain = maxoff - FirstOffsetNumber + 1;
383  if (nremain == 0)
384  {
385  /*
386  * The page is now completely empty. Remember its block number,
387  * so that we will try to delete the page in the second stage.
388  *
389  * Skip this when recursing, because IntegerSet requires that the
390  * values are added in ascending order. The next VACUUM will pick
391  * it up.
392  */
393  if (blkno == orig_blkno)
394  intset_add_member(vstate->empty_leaf_set, blkno);
395  }
396  else
397  vstate->stats->num_index_tuples += nremain;
398  }
399  else
400  {
401  /*
402  * On an internal page, check for "invalid tuples", left behind by an
403  * incomplete page split on PostgreSQL 9.0 or below. These are not
404  * created by newer PostgreSQL versions, but unfortunately, there is
405  * no version number anywhere in a GiST index, so we don't know
406  * whether this index might still contain invalid tuples or not.
407  */
408  OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
409  OffsetNumber off;
410 
411  for (off = FirstOffsetNumber;
412  off <= maxoff;
413  off = OffsetNumberNext(off))
414  {
415  ItemId iid = PageGetItemId(page, off);
416  IndexTuple idxtuple = (IndexTuple) PageGetItem(page, iid);
417 
418  if (GistTupleIsInvalid(idxtuple))
419  ereport(LOG,
420  (errmsg("index \"%s\" contains an inner tuple marked as invalid",
422  errdetail("This is caused by an incomplete page split at crash recovery before upgrading to PostgreSQL 9.1."),
423  errhint("Please REINDEX it.")));
424  }
425 
426  /*
427  * Remember the block number of this page, so that we can revisit it
428  * later in gistvacuum_delete_empty_pages(), when we search for
429  * parents of empty leaf pages.
430  */
431  if (blkno == orig_blkno)
432  intset_add_member(vstate->internal_page_set, blkno);
433  }
434 
435  UnlockReleaseBuffer(buffer);
436 
437  /*
438  * This is really tail recursion, but if the compiler is too stupid to
439  * optimize it as such, we'd eat an uncomfortably large amount of stack
440  * space per recursion level (due to the deletable[] array). A failure is
441  * improbable since the number of levels isn't likely to be large ... but
442  * just in case, let's hand-optimize into a loop.
443  */
444  if (recurse_to != InvalidBlockNumber)
445  {
446  blkno = recurse_to;
447  goto restart;
448  }
449 }
#define GistFollowRight(page)
Definition: gist.h:182
#define GistPageGetNSN(page)
Definition: gist.h:186
#define GistPageIsDeleted(page)
Definition: gist.h:172
int errhint(const char *fmt,...)
Definition: elog.c:1156
double tuples_removed
Definition: genam.h:79
void MarkBufferDirty(Buffer buffer)
Definition: bufmgr.c:1556
#define MaxOffsetNumber
Definition: off.h:28
#define GistMarkTuplesDeleted(page)
Definition: gist.h:175
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:741
ItemPointerData t_tid
Definition: itup.h:37
#define END_CRIT_SECTION()
Definition: miscadmin.h:149
BufferAccessStrategy strategy
Definition: genam.h:52
#define InvalidBuffer
Definition: buf.h:25
#define START_CRIT_SECTION()
Definition: miscadmin.h:147
Relation index
Definition: genam.h:46
#define GistTupleIsInvalid(itup)
Definition: gist_private.h:288
uint32 BlockNumber
Definition: block.h:31
#define LOG
Definition: elog.h:26
#define PageGetMaxOffsetNumber(page)
Definition: bufpage.h:357
XLogRecPtr gistGetFakeLSN(Relation rel)
Definition: gistutil.c:1024
uint16 OffsetNumber
Definition: off.h:24
IndexBulkDeleteResult * stats
Definition: gistvacuum.c:31
void intset_add_member(IntegerSet *intset, uint64 x)
Definition: integerset.c:371
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:3795
void * callback_state
Definition: gistvacuum.c:33
BlockNumber pages_free
Definition: genam.h:82
static void callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
Definition: test_ifaddrs.c:48
#define FirstOffsetNumber
Definition: off.h:27
IndexTupleData * IndexTuple
Definition: itup.h:53
int errdetail(const char *fmt,...)
Definition: elog.c:1042
#define RelationGetRelationName(relation)
Definition: rel.h:511
IntegerSet * empty_leaf_set
Definition: gistvacuum.c:41
BlockNumber pages_deleted
Definition: genam.h:81
#define BufferGetPage(buffer)
Definition: bufmgr.h:169
#define GistPageIsLeaf(page)
Definition: gist.h:169
#define PageGetItemId(page, offsetNumber)
Definition: bufpage.h:235
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:4011
IndexVacuumInfo * info
Definition: gistvacuum.c:30
XLogRecPtr gistXLogUpdate(Buffer buffer, OffsetNumber *todelete, int ntodelete, IndexTuple *itup, int ituplen, Buffer leftchildbuf)
Definition: gistxlog.c:632
#define ereport(elevel,...)
Definition: elog.h:157
#define GistPageGetOpaque(page)
Definition: gist.h:167
uint64 XLogRecPtr
Definition: xlogdefs.h:21
IntegerSet * internal_page_set
Definition: gistvacuum.c:40
void PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems)
Definition: bufpage.c:1154
#define OffsetNumberNext(offsetNumber)
Definition: off.h:52
#define InvalidBlockNumber
Definition: block.h:33
#define RelationNeedsWAL(relation)
Definition: rel.h:601
bool gistPageRecyclable(Page page)
Definition: gistutil.c:896
int errmsg(const char *fmt,...)
Definition: elog.c:909
GistNSN startNSN
Definition: gistvacuum.c:34
BlockNumber rightlink
Definition: gist.h:80
IndexBulkDeleteCallback callback
Definition: gistvacuum.c:32
void vacuum_delay_point(void)
Definition: vacuum.c:2149
#define GIST_EXCLUSIVE
Definition: gist_private.h:43
void RecordFreeIndexPage(Relation rel, BlockNumber freeBlock)
Definition: indexfsm.c:52
#define PageSetLSN(page, lsn)
Definition: bufpage.h:368
double num_index_tuples
Definition: genam.h:78
int Buffer
Definition: buf.h:23
#define PageGetItem(page, itemId)
Definition: bufpage.h:340
Pointer Page
Definition: bufpage.h:78
bool(* IndexBulkDeleteCallback)(ItemPointer itemptr, void *state)
Definition: genam.h:86

◆ gistvacuumscan()

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

Definition at line 125 of file gistvacuum.c.

References GistVacState::callback, callback(), GistVacState::callback_state, 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(), LockRelationForExtension(), MemoryContextDelete(), MemoryContextSwitchTo(), IndexBulkDeleteResult::num_index_tuples, IndexBulkDeleteResult::num_pages, GistVacState::page_set_context, IndexBulkDeleteResult::pages_deleted, IndexBulkDeleteResult::pages_free, RELATION_IS_LOCAL, RelationGetNumberOfBlocks, RelationNeedsWAL, GistVacState::startNSN, GistVacState::stats, and UnlockRelationForExtension().

Referenced by gistbulkdelete(), and gistvacuumcleanup().

127 {
128  Relation rel = info->index;
129  GistVacState vstate;
130  BlockNumber num_pages;
131  bool needLock;
132  BlockNumber blkno;
133  MemoryContext oldctx;
134 
135  /*
136  * Reset fields that track information about the entire index now. This
137  * avoids double-counting in the case where a single VACUUM command
138  * requires multiple scans of the index.
139  *
140  * Avoid resetting the tuples_removed and pages_newly_deleted fields here,
141  * since they track information about the VACUUM command, and so must last
142  * across each call to gistvacuumscan().
143  *
144  * (Note that pages_free is treated as state about the whole index, not
145  * the current VACUUM. This is appropriate because RecordFreeIndexPage()
146  * calls are idempotent, and get repeated for the same deleted pages in
147  * some scenarios. The point for us is to track the number of recyclable
148  * pages in the index at the end of the VACUUM command.)
149  */
150  stats->num_pages = 0;
151  stats->estimated_count = false;
152  stats->num_index_tuples = 0;
153  stats->pages_deleted = 0;
154  stats->pages_free = 0;
155 
156  /*
157  * Create the integer sets to remember all the internal and the empty leaf
158  * pages in page_set_context. Internally, the integer set will remember
159  * this context so that the subsequent allocations for these integer sets
160  * will be done from the same context.
161  */
163  "GiST VACUUM page set context",
164  16 * 1024);
165  oldctx = MemoryContextSwitchTo(vstate.page_set_context);
166  vstate.internal_page_set = intset_create();
167  vstate.empty_leaf_set = intset_create();
168  MemoryContextSwitchTo(oldctx);
169 
170  /* Set up info to pass down to gistvacuumpage */
171  vstate.info = info;
172  vstate.stats = stats;
173  vstate.callback = callback;
174  vstate.callback_state = callback_state;
175  if (RelationNeedsWAL(rel))
176  vstate.startNSN = GetInsertRecPtr();
177  else
178  vstate.startNSN = gistGetFakeLSN(rel);
179 
180  /*
181  * The outer loop iterates over all index pages, in physical order (we
182  * hope the kernel will cooperate in providing read-ahead for speed). It
183  * is critical that we visit all leaf pages, including ones added after we
184  * start the scan, else we might fail to delete some deletable tuples.
185  * Hence, we must repeatedly check the relation length. We must acquire
186  * the relation-extension lock while doing so to avoid a race condition:
187  * if someone else is extending the relation, there is a window where
188  * bufmgr/smgr have created a new all-zero page but it hasn't yet been
189  * write-locked by gistNewBuffer(). If we manage to scan such a page
190  * here, we'll improperly assume it can be recycled. Taking the lock
191  * synchronizes things enough to prevent a problem: either num_pages won't
192  * include the new page, or gistNewBuffer already has write lock on the
193  * buffer and it will be fully initialized before we can examine it. (See
194  * also vacuumlazy.c, which has the same issue.) Also, we need not worry
195  * if a page is added immediately after we look; the page splitting code
196  * already has write-lock on the left page before it adds a right page, so
197  * we must already have processed any tuples due to be moved into such a
198  * page.
199  *
200  * We can skip locking for new or temp relations, however, since no one
201  * else could be accessing them.
202  */
203  needLock = !RELATION_IS_LOCAL(rel);
204 
205  blkno = GIST_ROOT_BLKNO;
206  for (;;)
207  {
208  /* Get the current relation length */
209  if (needLock)
211  num_pages = RelationGetNumberOfBlocks(rel);
212  if (needLock)
214 
215  /* Quit if we've scanned the whole relation */
216  if (blkno >= num_pages)
217  break;
218  /* Iterate over pages, then loop back to recheck length */
219  for (; blkno < num_pages; blkno++)
220  gistvacuumpage(&vstate, blkno, blkno);
221  }
222 
223  /*
224  * If we found any recyclable pages (and recorded them in the FSM), then
225  * forcibly update the upper-level FSM pages to ensure that searchers can
226  * find them. It's possible that the pages were also found during
227  * previous scans and so this is a waste of time, but it's cheap enough
228  * relative to scanning the index that it shouldn't matter much, and
229  * making sure that free pages are available sooner not later seems
230  * worthwhile.
231  *
232  * Note that if no recyclable pages exist, we don't bother vacuuming the
233  * FSM at all.
234  */
235  if (stats->pages_free > 0)
237 
238  /* update statistics */
239  stats->num_pages = num_pages;
240 
241  /*
242  * If we saw any empty pages, try to unlink them from the tree so that
243  * they can be reused.
244  */
245  gistvacuum_delete_empty_pages(info, &vstate);
246 
247  /* we don't need the internal and empty page sets anymore */
249  vstate.page_set_context = NULL;
250  vstate.internal_page_set = NULL;
251  vstate.empty_leaf_set = NULL;
252 }
void MemoryContextDelete(MemoryContext context)
Definition: mcxt.c:218
XLogRecPtr GetInsertRecPtr(void)
Definition: xlog.c:8547
static void gistvacuum_delete_empty_pages(IndexVacuumInfo *info, GistVacState *vstate)
Definition: gistvacuum.c:455
#define ExclusiveLock
Definition: lockdefs.h:44
IntegerSet * intset_create(void)
Definition: integerset.c:285
#define RELATION_IS_LOCAL(relation)
Definition: rel.h:621
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:109
Relation index
Definition: genam.h:46
uint32 BlockNumber
Definition: block.h:31
static void gistvacuumpage(GistVacState *vstate, BlockNumber blkno, BlockNumber orig_blkno)
Definition: gistvacuum.c:266
XLogRecPtr gistGetFakeLSN(Relation rel)
Definition: gistutil.c:1024
IndexBulkDeleteResult * stats
Definition: gistvacuum.c:31
void * callback_state
Definition: gistvacuum.c:33
BlockNumber num_pages
Definition: genam.h:76
BlockNumber pages_free
Definition: genam.h:82
static void callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
Definition: test_ifaddrs.c:48
MemoryContext CurrentMemoryContext
Definition: mcxt.c:42
IntegerSet * empty_leaf_set
Definition: gistvacuum.c:41
BlockNumber pages_deleted
Definition: genam.h:81
void LockRelationForExtension(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:403
MemoryContext GenerationContextCreate(MemoryContext parent, const char *name, Size blockSize)
Definition: generation.c:197
void UnlockRelationForExtension(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:453
MemoryContext page_set_context
Definition: gistvacuum.c:42
#define RelationGetNumberOfBlocks(reln)
Definition: bufmgr.h:213
IndexVacuumInfo * info
Definition: gistvacuum.c:30
IntegerSet * internal_page_set
Definition: gistvacuum.c:40
void IndexFreeSpaceMapVacuum(Relation rel)
Definition: indexfsm.c:71
#define RelationNeedsWAL(relation)
Definition: rel.h:601
#define GIST_ROOT_BLKNO
Definition: gist_private.h:262
GistNSN startNSN
Definition: gistvacuum.c:34
IndexBulkDeleteCallback callback
Definition: gistvacuum.c:32
double num_index_tuples
Definition: genam.h:78
bool estimated_count
Definition: genam.h:77