PostgreSQL Source Code  git master
spgvacuum.c File Reference
#include "postgres.h"
#include "access/genam.h"
#include "access/spgist_private.h"
#include "access/spgxlog.h"
#include "access/transam.h"
#include "access/xloginsert.h"
#include "catalog/storage_xlog.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/indexfsm.h"
#include "storage/lmgr.h"
#include "utils/snapmgr.h"
Include dependency graph for spgvacuum.c:

Go to the source code of this file.

Data Structures

struct  spgVacPendingItem
 
struct  spgBulkDeleteState
 

Typedefs

typedef struct spgVacPendingItem spgVacPendingItem
 
typedef struct spgBulkDeleteState spgBulkDeleteState
 

Functions

static void spgAddPendingTID (spgBulkDeleteState *bds, ItemPointer tid)
 
static void spgClearPendingList (spgBulkDeleteState *bds)
 
static void vacuumLeafPage (spgBulkDeleteState *bds, Relation index, Buffer buffer, bool forPending)
 
static void vacuumLeafRoot (spgBulkDeleteState *bds, Relation index, Buffer buffer)
 
static void vacuumRedirectAndPlaceholder (Relation index, Relation heaprel, Buffer buffer)
 
static void spgvacuumpage (spgBulkDeleteState *bds, BlockNumber blkno)
 
static void spgprocesspending (spgBulkDeleteState *bds)
 
static void spgvacuumscan (spgBulkDeleteState *bds)
 
IndexBulkDeleteResultspgbulkdelete (IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
 
static bool dummy_callback (ItemPointer itemptr, void *state)
 
IndexBulkDeleteResultspgvacuumcleanup (IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 

Typedef Documentation

◆ spgBulkDeleteState

◆ spgVacPendingItem

Function Documentation

◆ dummy_callback()

static bool dummy_callback ( ItemPointer  itemptr,
void *  state 
)
static

Definition at line 926 of file spgvacuum.c.

927 {
928  return false;
929 }

Referenced by spgvacuumcleanup().

◆ spgAddPendingTID()

static void spgAddPendingTID ( spgBulkDeleteState bds,
ItemPointer  tid 
)
static

Definition at line 64 of file spgvacuum.c.

65 {
66  spgVacPendingItem *pitem;
67  spgVacPendingItem **listLink;
68 
69  /* search the list for pre-existing entry */
70  listLink = &bds->pendingList;
71  while (*listLink != NULL)
72  {
73  pitem = *listLink;
74  if (ItemPointerEquals(tid, &pitem->tid))
75  return; /* already in list, do nothing */
76  listLink = &pitem->next;
77  }
78  /* not there, so append new entry */
79  pitem = (spgVacPendingItem *) palloc(sizeof(spgVacPendingItem));
80  pitem->tid = *tid;
81  pitem->done = false;
82  pitem->next = NULL;
83  *listLink = pitem;
84 }
bool ItemPointerEquals(ItemPointer pointer1, ItemPointer pointer2)
Definition: itemptr.c:35
void * palloc(Size size)
Definition: mcxt.c:1226
spgVacPendingItem * pendingList
Definition: spgvacuum.c:51
ItemPointerData tid
Definition: spgvacuum.c:35
struct spgVacPendingItem * next
Definition: spgvacuum.c:37

References spgVacPendingItem::done, ItemPointerEquals(), spgVacPendingItem::next, palloc(), spgBulkDeleteState::pendingList, and spgVacPendingItem::tid.

Referenced by spgprocesspending(), and vacuumLeafPage().

◆ spgbulkdelete()

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

Definition at line 906 of file spgvacuum.c.

908 {
909  spgBulkDeleteState bds;
910 
911  /* allocate stats if first time through, else re-use existing struct */
912  if (stats == NULL)
914  bds.info = info;
915  bds.stats = stats;
916  bds.callback = callback;
917  bds.callback_state = callback_state;
918 
919  spgvacuumscan(&bds);
920 
921  return stats;
922 }
void * palloc0(Size size)
Definition: mcxt.c:1257
static void spgvacuumscan(spgBulkDeleteState *bds)
Definition: spgvacuum.c:794
IndexBulkDeleteResult * stats
Definition: spgvacuum.c:45
IndexBulkDeleteCallback callback
Definition: spgvacuum.c:46
void * callback_state
Definition: spgvacuum.c:47
IndexVacuumInfo * info
Definition: spgvacuum.c:44
static void callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
Definition: test_ifaddrs.c:46

References spgBulkDeleteState::callback, callback(), spgBulkDeleteState::callback_state, spgBulkDeleteState::info, palloc0(), spgvacuumscan(), and spgBulkDeleteState::stats.

Referenced by spghandler().

◆ spgClearPendingList()

static void spgClearPendingList ( spgBulkDeleteState bds)
static

Definition at line 90 of file spgvacuum.c.

91 {
92  spgVacPendingItem *pitem;
93  spgVacPendingItem *nitem;
94 
95  for (pitem = bds->pendingList; pitem != NULL; pitem = nitem)
96  {
97  nitem = pitem->next;
98  /* All items in list should have been dealt with */
99  Assert(pitem->done);
100  pfree(pitem);
101  }
102  bds->pendingList = NULL;
103 }
Assert(fmt[strlen(fmt) - 1] !='\n')
void pfree(void *pointer)
Definition: mcxt.c:1456

References Assert(), spgVacPendingItem::done, spgVacPendingItem::next, spgBulkDeleteState::pendingList, and pfree().

Referenced by spgprocesspending().

◆ spgprocesspending()

static void spgprocesspending ( spgBulkDeleteState bds)
static

Definition at line 682 of file spgvacuum.c.

683 {
684  Relation index = bds->info->index;
685  spgVacPendingItem *pitem;
686  spgVacPendingItem *nitem;
687  BlockNumber blkno;
688  Buffer buffer;
689  Page page;
690 
691  for (pitem = bds->pendingList; pitem != NULL; pitem = pitem->next)
692  {
693  if (pitem->done)
694  continue; /* ignore already-done items */
695 
696  /* call vacuum_delay_point while not holding any buffer lock */
698 
699  /* examine the referenced page */
700  blkno = ItemPointerGetBlockNumber(&pitem->tid);
701  buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
702  RBM_NORMAL, bds->info->strategy);
704  page = (Page) BufferGetPage(buffer);
705 
706  if (PageIsNew(page) || SpGistPageIsDeleted(page))
707  {
708  /* Probably shouldn't happen, but ignore it */
709  }
710  else if (SpGistPageIsLeaf(page))
711  {
712  if (SpGistBlockIsRoot(blkno))
713  {
714  /* this should definitely not happen */
715  elog(ERROR, "redirection leads to root page of index \"%s\"",
717  }
718 
719  /* deal with any deletable tuples */
720  vacuumLeafPage(bds, index, buffer, true);
721  /* might as well do this while we are here */
723 
724  SpGistSetLastUsedPage(index, buffer);
725 
726  /*
727  * We can mark as done not only this item, but any later ones
728  * pointing at the same page, since we vacuumed the whole page.
729  */
730  pitem->done = true;
731  for (nitem = pitem->next; nitem != NULL; nitem = nitem->next)
732  {
733  if (ItemPointerGetBlockNumber(&nitem->tid) == blkno)
734  nitem->done = true;
735  }
736  }
737  else
738  {
739  /*
740  * On an inner page, visit the referenced inner tuple and add all
741  * its downlinks to the pending list. We might have pending items
742  * for more than one inner tuple on the same page (in fact this is
743  * pretty likely given the way space allocation works), so get
744  * them all while we are here.
745  */
746  for (nitem = pitem; nitem != NULL; nitem = nitem->next)
747  {
748  if (nitem->done)
749  continue;
750  if (ItemPointerGetBlockNumber(&nitem->tid) == blkno)
751  {
752  OffsetNumber offset;
753  SpGistInnerTuple innerTuple;
754 
755  offset = ItemPointerGetOffsetNumber(&nitem->tid);
756  innerTuple = (SpGistInnerTuple) PageGetItem(page,
757  PageGetItemId(page, offset));
758  if (innerTuple->tupstate == SPGIST_LIVE)
759  {
760  SpGistNodeTuple node;
761  int i;
762 
763  SGITITERATE(innerTuple, i, node)
764  {
765  if (ItemPointerIsValid(&node->t_tid))
766  spgAddPendingTID(bds, &node->t_tid);
767  }
768  }
769  else if (innerTuple->tupstate == SPGIST_REDIRECT)
770  {
771  /* transfer attention to redirect point */
772  spgAddPendingTID(bds,
773  &((SpGistDeadTuple) innerTuple)->pointer);
774  }
775  else
776  elog(ERROR, "unexpected SPGiST tuple state: %d",
777  innerTuple->tupstate);
778 
779  nitem->done = true;
780  }
781  }
782  }
783 
784  UnlockReleaseBuffer(buffer);
785  }
786 
787  spgClearPendingList(bds);
788 }
uint32 BlockNumber
Definition: block.h:31
int Buffer
Definition: buf.h:23
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:4590
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:4808
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:782
static Page BufferGetPage(Buffer buffer)
Definition: bufmgr.h:350
#define BUFFER_LOCK_EXCLUSIVE
Definition: bufmgr.h:159
@ RBM_NORMAL
Definition: bufmgr.h:44
Pointer Page
Definition: bufpage.h:78
static Item PageGetItem(Page page, ItemId itemId)
Definition: bufpage.h:351
static ItemId PageGetItemId(Page page, OffsetNumber offsetNumber)
Definition: bufpage.h:240
static bool PageIsNew(Page page)
Definition: bufpage.h:230
#define ERROR
Definition: elog.h:39
int i
Definition: isn.c:73
static OffsetNumber ItemPointerGetOffsetNumber(const ItemPointerData *pointer)
Definition: itemptr.h:124
static BlockNumber ItemPointerGetBlockNumber(const ItemPointerData *pointer)
Definition: itemptr.h:103
static bool ItemPointerIsValid(const ItemPointerData *pointer)
Definition: itemptr.h:83
uint16 OffsetNumber
Definition: off.h:24
#define RelationGetRelationName(relation)
Definition: rel.h:538
@ MAIN_FORKNUM
Definition: relpath.h:50
#define SPGIST_REDIRECT
SpGistInnerTupleData * SpGistInnerTuple
#define SPGIST_LIVE
#define SGITITERATE(x, i, nt)
#define SpGistPageIsLeaf(page)
#define SpGistPageIsDeleted(page)
#define SpGistBlockIsRoot(blkno)
void SpGistSetLastUsedPage(Relation index, Buffer buffer)
Definition: spgutils.c:646
static void vacuumRedirectAndPlaceholder(Relation index, Relation heaprel, Buffer buffer)
Definition: spgvacuum.c:492
static void vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer, bool forPending)
Definition: spgvacuum.c:126
static void spgClearPendingList(spgBulkDeleteState *bds)
Definition: spgvacuum.c:90
static void spgAddPendingTID(spgBulkDeleteState *bds, ItemPointer tid)
Definition: spgvacuum.c:64
ItemPointerData t_tid
Definition: itup.h:37
Relation index
Definition: genam.h:46
BufferAccessStrategy strategy
Definition: genam.h:53
Relation heaprel
Definition: genam.h:47
Definition: type.h:95
void vacuum_delay_point(void)
Definition: vacuum.c:2322

References BUFFER_LOCK_EXCLUSIVE, BufferGetPage(), spgVacPendingItem::done, elog(), ERROR, IndexVacuumInfo::heaprel, i, IndexVacuumInfo::index, spgBulkDeleteState::info, ItemPointerGetBlockNumber(), ItemPointerGetOffsetNumber(), ItemPointerIsValid(), LockBuffer(), MAIN_FORKNUM, spgVacPendingItem::next, PageGetItem(), PageGetItemId(), PageIsNew(), spgBulkDeleteState::pendingList, RBM_NORMAL, ReadBufferExtended(), RelationGetRelationName, SGITITERATE, spgAddPendingTID(), spgClearPendingList(), SPGIST_LIVE, SPGIST_REDIRECT, SpGistBlockIsRoot, SpGistPageIsDeleted, SpGistPageIsLeaf, SpGistSetLastUsedPage(), IndexVacuumInfo::strategy, IndexTupleData::t_tid, spgVacPendingItem::tid, SpGistInnerTupleData::tupstate, UnlockReleaseBuffer(), vacuum_delay_point(), vacuumLeafPage(), and vacuumRedirectAndPlaceholder().

Referenced by spgvacuumscan().

◆ spgvacuumcleanup()

IndexBulkDeleteResult* spgvacuumcleanup ( IndexVacuumInfo info,
IndexBulkDeleteResult stats 
)

Definition at line 937 of file spgvacuum.c.

938 {
939  spgBulkDeleteState bds;
940 
941  /* No-op in ANALYZE ONLY mode */
942  if (info->analyze_only)
943  return stats;
944 
945  /*
946  * We don't need to scan the index if there was a preceding bulkdelete
947  * pass. Otherwise, make a pass that won't delete any live tuples, but
948  * might still accomplish useful stuff with redirect/placeholder cleanup
949  * and/or FSM housekeeping, and in any case will provide stats.
950  */
951  if (stats == NULL)
952  {
954  bds.info = info;
955  bds.stats = stats;
956  bds.callback = dummy_callback;
957  bds.callback_state = NULL;
958 
959  spgvacuumscan(&bds);
960  }
961 
962  /*
963  * It's quite possible for us to be fooled by concurrent tuple moves into
964  * double-counting some index tuples, so disbelieve any total that exceeds
965  * the underlying heap's count ... if we know that accurately. Otherwise
966  * this might just make matters worse.
967  */
968  if (!info->estimated_count)
969  {
970  if (stats->num_index_tuples > info->num_heap_tuples)
971  stats->num_index_tuples = info->num_heap_tuples;
972  }
973 
974  return stats;
975 }
static bool dummy_callback(ItemPointer itemptr, void *state)
Definition: spgvacuum.c:926
double num_index_tuples
Definition: genam.h:79
double num_heap_tuples
Definition: genam.h:52
bool analyze_only
Definition: genam.h:48
bool estimated_count
Definition: genam.h:50

References IndexVacuumInfo::analyze_only, spgBulkDeleteState::callback, spgBulkDeleteState::callback_state, dummy_callback(), IndexVacuumInfo::estimated_count, spgBulkDeleteState::info, IndexVacuumInfo::num_heap_tuples, IndexBulkDeleteResult::num_index_tuples, palloc0(), spgvacuumscan(), and spgBulkDeleteState::stats.

Referenced by spghandler().

◆ spgvacuumpage()

static void spgvacuumpage ( spgBulkDeleteState bds,
BlockNumber  blkno 
)
static

Definition at line 611 of file spgvacuum.c.

612 {
613  Relation index = bds->info->index;
614  Buffer buffer;
615  Page page;
616 
617  /* call vacuum_delay_point while not holding any buffer lock */
619 
620  buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
621  RBM_NORMAL, bds->info->strategy);
623  page = (Page) BufferGetPage(buffer);
624 
625  if (PageIsNew(page))
626  {
627  /*
628  * We found an all-zero page, which could happen if the database
629  * crashed just after extending the file. Recycle it.
630  */
631  }
632  else if (PageIsEmpty(page))
633  {
634  /* nothing to do */
635  }
636  else if (SpGistPageIsLeaf(page))
637  {
638  if (SpGistBlockIsRoot(blkno))
639  {
640  vacuumLeafRoot(bds, index, buffer);
641  /* no need for vacuumRedirectAndPlaceholder */
642  }
643  else
644  {
645  vacuumLeafPage(bds, index, buffer, false);
647  }
648  }
649  else
650  {
651  /* inner page */
653  }
654 
655  /*
656  * The root pages must never be deleted, nor marked as available in FSM,
657  * because we don't want them ever returned by a search for a place to put
658  * a new tuple. Otherwise, check for empty page, and make sure the FSM
659  * knows about it.
660  */
661  if (!SpGistBlockIsRoot(blkno))
662  {
663  if (PageIsNew(page) || PageIsEmpty(page))
664  {
665  RecordFreeIndexPage(index, blkno);
666  bds->stats->pages_deleted++;
667  }
668  else
669  {
670  SpGistSetLastUsedPage(index, buffer);
671  bds->lastFilledBlock = blkno;
672  }
673  }
674 
675  UnlockReleaseBuffer(buffer);
676 }
static bool PageIsEmpty(Page page)
Definition: bufpage.h:220
void RecordFreeIndexPage(Relation rel, BlockNumber freeBlock)
Definition: indexfsm.c:52
static void vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
Definition: spgvacuum.c:407
BlockNumber pages_deleted
Definition: genam.h:82
BlockNumber lastFilledBlock
Definition: spgvacuum.c:53

References BUFFER_LOCK_EXCLUSIVE, BufferGetPage(), IndexVacuumInfo::heaprel, IndexVacuumInfo::index, spgBulkDeleteState::info, spgBulkDeleteState::lastFilledBlock, LockBuffer(), MAIN_FORKNUM, PageIsEmpty(), PageIsNew(), IndexBulkDeleteResult::pages_deleted, RBM_NORMAL, ReadBufferExtended(), RecordFreeIndexPage(), SpGistBlockIsRoot, SpGistPageIsLeaf, SpGistSetLastUsedPage(), spgBulkDeleteState::stats, IndexVacuumInfo::strategy, UnlockReleaseBuffer(), vacuum_delay_point(), vacuumLeafPage(), vacuumLeafRoot(), and vacuumRedirectAndPlaceholder().

Referenced by spgvacuumscan().

◆ spgvacuumscan()

static void spgvacuumscan ( spgBulkDeleteState bds)
static

Definition at line 794 of file spgvacuum.c.

795 {
796  Relation index = bds->info->index;
797  bool needLock;
798  BlockNumber num_pages,
799  blkno;
800 
801  /* Finish setting up spgBulkDeleteState */
803  bds->pendingList = NULL;
804  bds->myXmin = GetActiveSnapshot()->xmin;
806 
807  /*
808  * Reset counts that will be incremented during the scan; needed in case
809  * of multiple scans during a single VACUUM command
810  */
811  bds->stats->estimated_count = false;
812  bds->stats->num_index_tuples = 0;
813  bds->stats->pages_deleted = 0;
814 
815  /* We can skip locking for new or temp relations */
816  needLock = !RELATION_IS_LOCAL(index);
817 
818  /*
819  * The outer loop iterates over all index pages except the metapage, in
820  * physical order (we hope the kernel will cooperate in providing
821  * read-ahead for speed). It is critical that we visit all leaf pages,
822  * including ones added after we start the scan, else we might fail to
823  * delete some deletable tuples. See more extensive comments about this
824  * in btvacuumscan().
825  */
826  blkno = SPGIST_METAPAGE_BLKNO + 1;
827  for (;;)
828  {
829  /* Get the current relation length */
830  if (needLock)
832  num_pages = RelationGetNumberOfBlocks(index);
833  if (needLock)
835 
836  /* Quit if we've scanned the whole relation */
837  if (blkno >= num_pages)
838  break;
839  /* Iterate over pages, then loop back to recheck length */
840  for (; blkno < num_pages; blkno++)
841  {
842  spgvacuumpage(bds, blkno);
843  /* empty the pending-list after each page */
844  if (bds->pendingList != NULL)
845  spgprocesspending(bds);
846  }
847  }
848 
849  /* Propagate local lastUsedPages cache to metablock */
851 
852  /*
853  * If we found any empty pages (and recorded them in the FSM), then
854  * forcibly update the upper-level FSM pages to ensure that searchers can
855  * find them. It's possible that the pages were also found during
856  * previous scans and so this is a waste of time, but it's cheap enough
857  * relative to scanning the index that it shouldn't matter much, and
858  * making sure that free pages are available sooner not later seems
859  * worthwhile.
860  *
861  * Note that if no empty pages exist, we don't bother vacuuming the FSM at
862  * all.
863  */
864  if (bds->stats->pages_deleted > 0)
866 
867  /*
868  * Truncate index if possible
869  *
870  * XXX disabled because it's unsafe due to possible concurrent inserts.
871  * We'd have to rescan the pages to make sure they're still empty, and it
872  * doesn't seem worth it. Note that btree doesn't do this either.
873  *
874  * Another reason not to truncate is that it could invalidate the cached
875  * pages-with-freespace pointers in the metapage and other backends'
876  * relation caches, that is leave them pointing to nonexistent pages.
877  * Adding RelationGetNumberOfBlocks calls to protect the places that use
878  * those pointers would be unduly expensive.
879  */
880 #ifdef NOT_USED
881  if (num_pages > bds->lastFilledBlock + 1)
882  {
883  BlockNumber lastBlock = num_pages - 1;
884 
885  num_pages = bds->lastFilledBlock + 1;
886  RelationTruncate(index, num_pages);
887  bds->stats->pages_removed += lastBlock - bds->lastFilledBlock;
888  bds->stats->pages_deleted -= lastBlock - bds->lastFilledBlock;
889  }
890 #endif
891 
892  /* Report final stats */
893  bds->stats->num_pages = num_pages;
895  bds->stats->pages_free = bds->stats->pages_deleted;
896 }
#define RelationGetNumberOfBlocks(reln)
Definition: bufmgr.h:229
void IndexFreeSpaceMapVacuum(Relation rel)
Definition: indexfsm.c:71
void LockRelationForExtension(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:431
void UnlockRelationForExtension(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:481
#define ExclusiveLock
Definition: lockdefs.h:42
#define RELATION_IS_LOCAL(relation)
Definition: rel.h:649
Snapshot GetActiveSnapshot(void)
Definition: snapmgr.c:777
#define SPGIST_METAPAGE_BLKNO
#define SPGIST_LAST_FIXED_BLKNO
void initSpGistState(SpGistState *state, Relation index)
Definition: spgutils.c:332
void SpGistUpdateMetaPage(Relation index)
Definition: spgutils.c:423
static void spgprocesspending(spgBulkDeleteState *bds)
Definition: spgvacuum.c:682
static void spgvacuumpage(spgBulkDeleteState *bds, BlockNumber blkno)
Definition: spgvacuum.c:611
void RelationTruncate(Relation rel, BlockNumber nblocks)
Definition: storage.c:287
bool estimated_count
Definition: genam.h:78
BlockNumber pages_newly_deleted
Definition: genam.h:81
BlockNumber pages_free
Definition: genam.h:83
BlockNumber num_pages
Definition: genam.h:77
TransactionId xmin
Definition: snapshot.h:157
SpGistState spgstate
Definition: spgvacuum.c:50
TransactionId myXmin
Definition: spgvacuum.c:52

References IndexBulkDeleteResult::estimated_count, ExclusiveLock, GetActiveSnapshot(), IndexVacuumInfo::index, IndexFreeSpaceMapVacuum(), spgBulkDeleteState::info, initSpGistState(), spgBulkDeleteState::lastFilledBlock, LockRelationForExtension(), spgBulkDeleteState::myXmin, IndexBulkDeleteResult::num_index_tuples, IndexBulkDeleteResult::num_pages, IndexBulkDeleteResult::pages_deleted, IndexBulkDeleteResult::pages_free, IndexBulkDeleteResult::pages_newly_deleted, spgBulkDeleteState::pendingList, RELATION_IS_LOCAL, RelationGetNumberOfBlocks, RelationTruncate(), SPGIST_LAST_FIXED_BLKNO, SPGIST_METAPAGE_BLKNO, SpGistUpdateMetaPage(), spgprocesspending(), spgBulkDeleteState::spgstate, spgvacuumpage(), spgBulkDeleteState::stats, UnlockRelationForExtension(), and SnapshotData::xmin.

Referenced by spgbulkdelete(), and spgvacuumcleanup().

◆ vacuumLeafPage()

static void vacuumLeafPage ( spgBulkDeleteState bds,
Relation  index,
Buffer  buffer,
bool  forPending 
)
static

Definition at line 126 of file spgvacuum.c.

128 {
129  Page page = BufferGetPage(buffer);
130  spgxlogVacuumLeaf xlrec;
132  OffsetNumber toPlaceholder[MaxIndexTuplesPerPage];
137  OffsetNumber predecessor[MaxIndexTuplesPerPage + 1];
138  bool deletable[MaxIndexTuplesPerPage + 1];
139  int nDeletable;
140  OffsetNumber i,
141  max = PageGetMaxOffsetNumber(page);
142 
143  memset(predecessor, 0, sizeof(predecessor));
144  memset(deletable, 0, sizeof(deletable));
145  nDeletable = 0;
146 
147  /* Scan page, identify tuples to delete, accumulate stats */
148  for (i = FirstOffsetNumber; i <= max; i++)
149  {
150  SpGistLeafTuple lt;
151 
152  lt = (SpGistLeafTuple) PageGetItem(page,
153  PageGetItemId(page, i));
154  if (lt->tupstate == SPGIST_LIVE)
155  {
157 
158  if (bds->callback(&lt->heapPtr, bds->callback_state))
159  {
160  bds->stats->tuples_removed += 1;
161  deletable[i] = true;
162  nDeletable++;
163  }
164  else
165  {
166  if (!forPending)
167  bds->stats->num_index_tuples += 1;
168  }
169 
170  /* Form predecessor map, too */
172  {
173  /* paranoia about corrupted chain links */
175  SGLT_GET_NEXTOFFSET(lt) > max ||
176  predecessor[SGLT_GET_NEXTOFFSET(lt)] != InvalidOffsetNumber)
177  elog(ERROR, "inconsistent tuple chain links in page %u of index \"%s\"",
178  BufferGetBlockNumber(buffer),
180  predecessor[SGLT_GET_NEXTOFFSET(lt)] = i;
181  }
182  }
183  else if (lt->tupstate == SPGIST_REDIRECT)
184  {
186 
189 
190  /*
191  * Add target TID to pending list if the redirection could have
192  * happened since VACUUM started.
193  *
194  * Note: we could make a tighter test by seeing if the xid is
195  * "running" according to the active snapshot; but snapmgr.c
196  * doesn't currently export a suitable API, and it's not entirely
197  * clear that a tighter test is worth the cycles anyway.
198  */
199  if (TransactionIdFollowsOrEquals(dt->xid, bds->myXmin))
200  spgAddPendingTID(bds, &dt->pointer);
201  }
202  else
203  {
205  }
206  }
207 
208  if (nDeletable == 0)
209  return; /* nothing more to do */
210 
211  /*----------
212  * Figure out exactly what we have to do. We do this separately from
213  * actually modifying the page, mainly so that we have a representation
214  * that can be dumped into WAL and then the replay code can do exactly
215  * the same thing. The output of this step consists of six arrays
216  * describing four kinds of operations, to be performed in this order:
217  *
218  * toDead[]: tuple numbers to be replaced with DEAD tuples
219  * toPlaceholder[]: tuple numbers to be replaced with PLACEHOLDER tuples
220  * moveSrc[]: tuple numbers that need to be relocated to another offset
221  * (replacing the tuple there) and then replaced with PLACEHOLDER tuples
222  * moveDest[]: new locations for moveSrc tuples
223  * chainSrc[]: tuple numbers whose chain links (nextOffset) need updates
224  * chainDest[]: new values of nextOffset for chainSrc members
225  *
226  * It's easiest to figure out what we have to do by processing tuple
227  * chains, so we iterate over all the tuples (not just the deletable
228  * ones!) to identify chain heads, then chase down each chain and make
229  * work item entries for deletable tuples within the chain.
230  *----------
231  */
232  xlrec.nDead = xlrec.nPlaceholder = xlrec.nMove = xlrec.nChain = 0;
233 
234  for (i = FirstOffsetNumber; i <= max; i++)
235  {
236  SpGistLeafTuple head;
237  bool interveningDeletable;
238  OffsetNumber prevLive;
239  OffsetNumber j;
240 
241  head = (SpGistLeafTuple) PageGetItem(page,
242  PageGetItemId(page, i));
243  if (head->tupstate != SPGIST_LIVE)
244  continue; /* can't be a chain member */
245  if (predecessor[i] != 0)
246  continue; /* not a chain head */
247 
248  /* initialize ... */
249  interveningDeletable = false;
250  prevLive = deletable[i] ? InvalidOffsetNumber : i;
251 
252  /* scan down the chain ... */
253  j = SGLT_GET_NEXTOFFSET(head);
254  while (j != InvalidOffsetNumber)
255  {
256  SpGistLeafTuple lt;
257 
258  lt = (SpGistLeafTuple) PageGetItem(page,
259  PageGetItemId(page, j));
260  if (lt->tupstate != SPGIST_LIVE)
261  {
262  /* all tuples in chain should be live */
263  elog(ERROR, "unexpected SPGiST tuple state: %d",
264  lt->tupstate);
265  }
266 
267  if (deletable[j])
268  {
269  /* This tuple should be replaced by a placeholder */
270  toPlaceholder[xlrec.nPlaceholder] = j;
271  xlrec.nPlaceholder++;
272  /* previous live tuple's chain link will need an update */
273  interveningDeletable = true;
274  }
275  else if (prevLive == InvalidOffsetNumber)
276  {
277  /*
278  * This is the first live tuple in the chain. It has to move
279  * to the head position.
280  */
281  moveSrc[xlrec.nMove] = j;
282  moveDest[xlrec.nMove] = i;
283  xlrec.nMove++;
284  /* Chain updates will be applied after the move */
285  prevLive = i;
286  interveningDeletable = false;
287  }
288  else
289  {
290  /*
291  * Second or later live tuple. Arrange to re-chain it to the
292  * previous live one, if there was a gap.
293  */
294  if (interveningDeletable)
295  {
296  chainSrc[xlrec.nChain] = prevLive;
297  chainDest[xlrec.nChain] = j;
298  xlrec.nChain++;
299  }
300  prevLive = j;
301  interveningDeletable = false;
302  }
303 
304  j = SGLT_GET_NEXTOFFSET(lt);
305  }
306 
307  if (prevLive == InvalidOffsetNumber)
308  {
309  /* The chain is entirely removable, so we need a DEAD tuple */
310  toDead[xlrec.nDead] = i;
311  xlrec.nDead++;
312  }
313  else if (interveningDeletable)
314  {
315  /* One or more deletions at end of chain, so close it off */
316  chainSrc[xlrec.nChain] = prevLive;
317  chainDest[xlrec.nChain] = InvalidOffsetNumber;
318  xlrec.nChain++;
319  }
320  }
321 
322  /* sanity check ... */
323  if (nDeletable != xlrec.nDead + xlrec.nPlaceholder + xlrec.nMove)
324  elog(ERROR, "inconsistent counts of deletable tuples");
325 
326  /* Do the updates */
328 
329  spgPageIndexMultiDelete(&bds->spgstate, page,
330  toDead, xlrec.nDead,
333 
334  spgPageIndexMultiDelete(&bds->spgstate, page,
335  toPlaceholder, xlrec.nPlaceholder,
338 
339  /*
340  * We implement the move step by swapping the line pointers of the source
341  * and target tuples, then replacing the newly-source tuples with
342  * placeholders. This is perhaps unduly friendly with the page data
343  * representation, but it's fast and doesn't risk page overflow when a
344  * tuple to be relocated is large.
345  */
346  for (i = 0; i < xlrec.nMove; i++)
347  {
348  ItemId idSrc = PageGetItemId(page, moveSrc[i]);
349  ItemId idDest = PageGetItemId(page, moveDest[i]);
350  ItemIdData tmp;
351 
352  tmp = *idSrc;
353  *idSrc = *idDest;
354  *idDest = tmp;
355  }
356 
357  spgPageIndexMultiDelete(&bds->spgstate, page,
358  moveSrc, xlrec.nMove,
361 
362  for (i = 0; i < xlrec.nChain; i++)
363  {
364  SpGistLeafTuple lt;
365 
366  lt = (SpGistLeafTuple) PageGetItem(page,
367  PageGetItemId(page, chainSrc[i]));
368  Assert(lt->tupstate == SPGIST_LIVE);
369  SGLT_SET_NEXTOFFSET(lt, chainDest[i]);
370  }
371 
372  MarkBufferDirty(buffer);
373 
374  if (RelationNeedsWAL(index))
375  {
376  XLogRecPtr recptr;
377 
378  XLogBeginInsert();
379 
380  STORE_STATE(&bds->spgstate, xlrec.stateSrc);
381 
382  XLogRegisterData((char *) &xlrec, SizeOfSpgxlogVacuumLeaf);
383  /* sizeof(xlrec) should be a multiple of sizeof(OffsetNumber) */
384  XLogRegisterData((char *) toDead, sizeof(OffsetNumber) * xlrec.nDead);
385  XLogRegisterData((char *) toPlaceholder, sizeof(OffsetNumber) * xlrec.nPlaceholder);
386  XLogRegisterData((char *) moveSrc, sizeof(OffsetNumber) * xlrec.nMove);
387  XLogRegisterData((char *) moveDest, sizeof(OffsetNumber) * xlrec.nMove);
388  XLogRegisterData((char *) chainSrc, sizeof(OffsetNumber) * xlrec.nChain);
389  XLogRegisterData((char *) chainDest, sizeof(OffsetNumber) * xlrec.nChain);
390 
392 
393  recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_VACUUM_LEAF);
394 
395  PageSetLSN(page, recptr);
396  }
397 
399 }
#define InvalidBlockNumber
Definition: block.h:33
BlockNumber BufferGetBlockNumber(Buffer buffer)
Definition: bufmgr.c:3386
void MarkBufferDirty(Buffer buffer)
Definition: bufmgr.c:2198
static void PageSetLSN(Page page, XLogRecPtr lsn)
Definition: bufpage.h:388
static OffsetNumber PageGetMaxOffsetNumber(Page page)
Definition: bufpage.h:369
int j
Definition: isn.c:74
#define MaxIndexTuplesPerPage
Definition: itup.h:165
#define START_CRIT_SECTION()
Definition: miscadmin.h:148
#define END_CRIT_SECTION()
Definition: miscadmin.h:150
#define InvalidOffsetNumber
Definition: off.h:26
#define FirstOffsetNumber
Definition: off.h:27
#define RelationNeedsWAL(relation)
Definition: rel.h:629
void spgPageIndexMultiDelete(SpGistState *state, Page page, OffsetNumber *itemnos, int nitems, int firststate, int reststate, BlockNumber blkno, OffsetNumber offnum)
Definition: spgdoinsert.c:132
SpGistDeadTupleData * SpGistDeadTuple
#define SGLT_GET_NEXTOFFSET(spgLeafTuple)
#define SPGIST_PLACEHOLDER
#define SPGIST_DEAD
#define SGLT_SET_NEXTOFFSET(spgLeafTuple, offsetNumber)
struct SpGistLeafTupleData * SpGistLeafTuple
#define STORE_STATE(s, d)
#define SizeOfSpgxlogVacuumLeaf
Definition: spgxlog.h:223
#define XLOG_SPGIST_VACUUM_LEAF
Definition: spgxlog.h:27
double tuples_removed
Definition: genam.h:80
ItemPointerData pointer
unsigned int tupstate
ItemPointerData heapPtr
spgxlogState stateSrc
Definition: spgxlog.h:208
uint16 nPlaceholder
Definition: spgxlog.h:204
bool TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2)
Definition: transam.c:329
uint64 XLogRecPtr
Definition: xlogdefs.h:21
void XLogRegisterData(char *data, uint32 len)
Definition: xloginsert.c:365
XLogRecPtr XLogInsert(RmgrId rmid, uint8 info)
Definition: xloginsert.c:475
void XLogRegisterBuffer(uint8 block_id, Buffer buffer, uint8 flags)
Definition: xloginsert.c:243
void XLogBeginInsert(void)
Definition: xloginsert.c:150
#define REGBUF_STANDARD
Definition: xloginsert.h:34

References Assert(), BufferGetBlockNumber(), BufferGetPage(), spgBulkDeleteState::callback, spgBulkDeleteState::callback_state, elog(), END_CRIT_SECTION, ERROR, FirstOffsetNumber, SpGistLeafTupleData::heapPtr, i, InvalidBlockNumber, InvalidOffsetNumber, ItemPointerIsValid(), j, MarkBufferDirty(), MaxIndexTuplesPerPage, spgBulkDeleteState::myXmin, spgxlogVacuumLeaf::nChain, spgxlogVacuumLeaf::nDead, spgxlogVacuumLeaf::nMove, spgxlogVacuumLeaf::nPlaceholder, IndexBulkDeleteResult::num_index_tuples, PageGetItem(), PageGetItemId(), PageGetMaxOffsetNumber(), PageSetLSN(), SpGistDeadTupleData::pointer, REGBUF_STANDARD, RelationGetRelationName, RelationNeedsWAL, SGLT_GET_NEXTOFFSET, SGLT_SET_NEXTOFFSET, SizeOfSpgxlogVacuumLeaf, spgAddPendingTID(), SPGIST_DEAD, SPGIST_LIVE, SPGIST_PLACEHOLDER, SPGIST_REDIRECT, spgPageIndexMultiDelete(), spgBulkDeleteState::spgstate, START_CRIT_SECTION, spgxlogVacuumLeaf::stateSrc, spgBulkDeleteState::stats, STORE_STATE, TransactionIdFollowsOrEquals(), IndexBulkDeleteResult::tuples_removed, SpGistLeafTupleData::tupstate, SpGistDeadTupleData::xid, XLOG_SPGIST_VACUUM_LEAF, XLogBeginInsert(), XLogInsert(), XLogRegisterBuffer(), and XLogRegisterData().

Referenced by spgprocesspending(), and spgvacuumpage().

◆ vacuumLeafRoot()

static void vacuumLeafRoot ( spgBulkDeleteState bds,
Relation  index,
Buffer  buffer 
)
static

Definition at line 407 of file spgvacuum.c.

408 {
409  Page page = BufferGetPage(buffer);
410  spgxlogVacuumRoot xlrec;
412  OffsetNumber i,
413  max = PageGetMaxOffsetNumber(page);
414 
415  xlrec.nDelete = 0;
416 
417  /* Scan page, identify tuples to delete, accumulate stats */
418  for (i = FirstOffsetNumber; i <= max; i++)
419  {
420  SpGistLeafTuple lt;
421 
422  lt = (SpGistLeafTuple) PageGetItem(page,
423  PageGetItemId(page, i));
424  if (lt->tupstate == SPGIST_LIVE)
425  {
427 
428  if (bds->callback(&lt->heapPtr, bds->callback_state))
429  {
430  bds->stats->tuples_removed += 1;
431  toDelete[xlrec.nDelete] = i;
432  xlrec.nDelete++;
433  }
434  else
435  {
436  bds->stats->num_index_tuples += 1;
437  }
438  }
439  else
440  {
441  /* all tuples on root should be live */
442  elog(ERROR, "unexpected SPGiST tuple state: %d",
443  lt->tupstate);
444  }
445  }
446 
447  if (xlrec.nDelete == 0)
448  return; /* nothing more to do */
449 
450  /* Do the update */
452 
453  /* The tuple numbers are in order, so we can use PageIndexMultiDelete */
454  PageIndexMultiDelete(page, toDelete, xlrec.nDelete);
455 
456  MarkBufferDirty(buffer);
457 
458  if (RelationNeedsWAL(index))
459  {
460  XLogRecPtr recptr;
461 
462  XLogBeginInsert();
463 
464  /* Prepare WAL record */
465  STORE_STATE(&bds->spgstate, xlrec.stateSrc);
466 
467  XLogRegisterData((char *) &xlrec, SizeOfSpgxlogVacuumRoot);
468  /* sizeof(xlrec) should be a multiple of sizeof(OffsetNumber) */
469  XLogRegisterData((char *) toDelete,
470  sizeof(OffsetNumber) * xlrec.nDelete);
471 
473 
474  recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_VACUUM_ROOT);
475 
476  PageSetLSN(page, recptr);
477  }
478 
480 }
void PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems)
Definition: bufpage.c:1161
#define XLOG_SPGIST_VACUUM_ROOT
Definition: spgxlog.h:28
#define SizeOfSpgxlogVacuumRoot
Definition: spgxlog.h:236
spgxlogState stateSrc
Definition: spgxlog.h:230
uint16 nDelete
Definition: spgxlog.h:228

References Assert(), BufferGetPage(), spgBulkDeleteState::callback, spgBulkDeleteState::callback_state, elog(), END_CRIT_SECTION, ERROR, FirstOffsetNumber, SpGistLeafTupleData::heapPtr, i, ItemPointerIsValid(), MarkBufferDirty(), MaxIndexTuplesPerPage, spgxlogVacuumRoot::nDelete, IndexBulkDeleteResult::num_index_tuples, PageGetItem(), PageGetItemId(), PageGetMaxOffsetNumber(), PageIndexMultiDelete(), PageSetLSN(), REGBUF_STANDARD, RelationNeedsWAL, SizeOfSpgxlogVacuumRoot, SPGIST_LIVE, spgBulkDeleteState::spgstate, START_CRIT_SECTION, spgxlogVacuumRoot::stateSrc, spgBulkDeleteState::stats, STORE_STATE, IndexBulkDeleteResult::tuples_removed, SpGistLeafTupleData::tupstate, XLOG_SPGIST_VACUUM_ROOT, XLogBeginInsert(), XLogInsert(), XLogRegisterBuffer(), and XLogRegisterData().

Referenced by spgvacuumpage().

◆ vacuumRedirectAndPlaceholder()

static void vacuumRedirectAndPlaceholder ( Relation  index,
Relation  heaprel,
Buffer  buffer 
)
static

Definition at line 492 of file spgvacuum.c.

493 {
494  Page page = BufferGetPage(buffer);
495  SpGistPageOpaque opaque = SpGistPageGetOpaque(page);
496  OffsetNumber i,
497  max = PageGetMaxOffsetNumber(page),
498  firstPlaceholder = InvalidOffsetNumber;
499  bool hasNonPlaceholder = false;
500  bool hasUpdate = false;
501  OffsetNumber itemToPlaceholder[MaxIndexTuplesPerPage];
503  spgxlogVacuumRedirect xlrec;
504  GlobalVisState *vistest;
505 
507  xlrec.nToPlaceholder = 0;
509 
510  vistest = GlobalVisTestFor(heaprel);
511 
513 
514  /*
515  * Scan backwards to convert old redirection tuples to placeholder tuples,
516  * and identify location of last non-placeholder tuple while at it.
517  */
518  for (i = max;
519  i >= FirstOffsetNumber &&
520  (opaque->nRedirection > 0 || !hasNonPlaceholder);
521  i--)
522  {
523  SpGistDeadTuple dt;
524 
525  dt = (SpGistDeadTuple) PageGetItem(page, PageGetItemId(page, i));
526 
527  if (dt->tupstate == SPGIST_REDIRECT &&
528  GlobalVisTestIsRemovableXid(vistest, dt->xid))
529  {
531  Assert(opaque->nRedirection > 0);
532  opaque->nRedirection--;
533  opaque->nPlaceholder++;
534 
535  /* remember newest XID among the removed redirects */
538  xlrec.snapshotConflictHorizon = dt->xid;
539 
541 
542  itemToPlaceholder[xlrec.nToPlaceholder] = i;
543  xlrec.nToPlaceholder++;
544 
545  hasUpdate = true;
546  }
547 
548  if (dt->tupstate == SPGIST_PLACEHOLDER)
549  {
550  if (!hasNonPlaceholder)
551  firstPlaceholder = i;
552  }
553  else
554  {
555  hasNonPlaceholder = true;
556  }
557  }
558 
559  /*
560  * Any placeholder tuples at the end of page can safely be removed. We
561  * can't remove ones before the last non-placeholder, though, because we
562  * can't alter the offset numbers of non-placeholder tuples.
563  */
564  if (firstPlaceholder != InvalidOffsetNumber)
565  {
566  /*
567  * We do not store this array to rdata because it's easy to recreate.
568  */
569  for (i = firstPlaceholder; i <= max; i++)
570  itemnos[i - firstPlaceholder] = i;
571 
572  i = max - firstPlaceholder + 1;
573  Assert(opaque->nPlaceholder >= i);
574  opaque->nPlaceholder -= i;
575 
576  /* The array is surely sorted, so can use PageIndexMultiDelete */
577  PageIndexMultiDelete(page, itemnos, i);
578 
579  hasUpdate = true;
580  }
581 
582  xlrec.firstPlaceholder = firstPlaceholder;
583 
584  if (hasUpdate)
585  MarkBufferDirty(buffer);
586 
587  if (hasUpdate && RelationNeedsWAL(index))
588  {
589  XLogRecPtr recptr;
590 
591  XLogBeginInsert();
592 
594  XLogRegisterData((char *) itemToPlaceholder,
595  sizeof(OffsetNumber) * xlrec.nToPlaceholder);
596 
598 
599  recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_VACUUM_REDIRECT);
600 
601  PageSetLSN(page, recptr);
602  }
603 
605 }
static void ItemPointerSetInvalid(ItemPointerData *pointer)
Definition: itemptr.h:184
bool GlobalVisTestIsRemovableXid(GlobalVisState *state, TransactionId xid)
Definition: procarray.c:4167
GlobalVisState * GlobalVisTestFor(Relation rel)
Definition: procarray.c:4010
#define RelationIsAccessibleInLogicalDecoding(relation)
Definition: rel.h:685
#define SpGistPageGetOpaque(page)
#define SizeOfSpgxlogVacuumRedirect
Definition: spgxlog.h:250
#define XLOG_SPGIST_VACUUM_REDIRECT
Definition: spgxlog.h:29
unsigned int tupstate
OffsetNumber firstPlaceholder
Definition: spgxlog.h:241
TransactionId snapshotConflictHorizon
Definition: spgxlog.h:242
bool TransactionIdPrecedes(TransactionId id1, TransactionId id2)
Definition: transam.c:280
#define InvalidTransactionId
Definition: transam.h:31
#define TransactionIdIsValid(xid)
Definition: transam.h:41

References Assert(), BufferGetPage(), END_CRIT_SECTION, FirstOffsetNumber, spgxlogVacuumRedirect::firstPlaceholder, GlobalVisTestFor(), GlobalVisTestIsRemovableXid(), i, InvalidOffsetNumber, InvalidTransactionId, spgxlogVacuumRedirect::isCatalogRel, ItemPointerSetInvalid(), MarkBufferDirty(), MaxIndexTuplesPerPage, SpGistPageOpaqueData::nPlaceholder, SpGistPageOpaqueData::nRedirection, spgxlogVacuumRedirect::nToPlaceholder, PageGetItem(), PageGetItemId(), PageGetMaxOffsetNumber(), PageIndexMultiDelete(), PageSetLSN(), SpGistDeadTupleData::pointer, REGBUF_STANDARD, RelationIsAccessibleInLogicalDecoding, RelationNeedsWAL, SizeOfSpgxlogVacuumRedirect, spgxlogVacuumRedirect::snapshotConflictHorizon, SPGIST_PLACEHOLDER, SPGIST_REDIRECT, SpGistPageGetOpaque, START_CRIT_SECTION, TransactionIdIsValid, TransactionIdPrecedes(), SpGistDeadTupleData::tupstate, SpGistDeadTupleData::xid, XLOG_SPGIST_VACUUM_REDIRECT, XLogBeginInsert(), XLogInsert(), XLogRegisterBuffer(), and XLogRegisterData().

Referenced by spgprocesspending(), and spgvacuumpage().