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 "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 936 of file spgvacuum.c.

937 {
938  return false;
939 }

Referenced by spgvacuumcleanup().

◆ spgAddPendingTID()

static void spgAddPendingTID ( spgBulkDeleteState bds,
ItemPointer  tid 
)
static

Definition at line 63 of file spgvacuum.c.

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

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 916 of file spgvacuum.c.

918 {
919  spgBulkDeleteState bds;
920 
921  /* allocate stats if first time through, else re-use existing struct */
922  if (stats == NULL)
924  bds.info = info;
925  bds.stats = stats;
926  bds.callback = callback;
927  bds.callback_state = callback_state;
928 
929  spgvacuumscan(&bds);
930 
931  return stats;
932 }
void * palloc0(Size size)
Definition: mcxt.c:1347
static void spgvacuumscan(spgBulkDeleteState *bds)
Definition: spgvacuum.c:804
IndexBulkDeleteResult * stats
Definition: spgvacuum.c:44
IndexBulkDeleteCallback callback
Definition: spgvacuum.c:45
void * callback_state
Definition: spgvacuum.c:46
IndexVacuumInfo * info
Definition: spgvacuum.c:43
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 89 of file spgvacuum.c.

90 {
91  spgVacPendingItem *pitem;
92  spgVacPendingItem *nitem;
93 
94  for (pitem = bds->pendingList; pitem != NULL; pitem = nitem)
95  {
96  nitem = pitem->next;
97  /* All items in list should have been dealt with */
98  Assert(pitem->done);
99  pfree(pitem);
100  }
101  bds->pendingList = NULL;
102 }
#define Assert(condition)
Definition: c.h:858
void pfree(void *pointer)
Definition: mcxt.c:1521

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

Referenced by spgprocesspending().

◆ spgprocesspending()

static void spgprocesspending ( spgBulkDeleteState bds)
static

Definition at line 692 of file spgvacuum.c.

693 {
694  Relation index = bds->info->index;
695  spgVacPendingItem *pitem;
696  spgVacPendingItem *nitem;
697  BlockNumber blkno;
698  Buffer buffer;
699  Page page;
700 
701  for (pitem = bds->pendingList; pitem != NULL; pitem = pitem->next)
702  {
703  if (pitem->done)
704  continue; /* ignore already-done items */
705 
706  /* call vacuum_delay_point while not holding any buffer lock */
708 
709  /* examine the referenced page */
710  blkno = ItemPointerGetBlockNumber(&pitem->tid);
711  buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
712  RBM_NORMAL, bds->info->strategy);
714  page = (Page) BufferGetPage(buffer);
715 
716  if (PageIsNew(page) || SpGistPageIsDeleted(page))
717  {
718  /* Probably shouldn't happen, but ignore it */
719  }
720  else if (SpGistPageIsLeaf(page))
721  {
722  if (SpGistBlockIsRoot(blkno))
723  {
724  /* this should definitely not happen */
725  elog(ERROR, "redirection leads to root page of index \"%s\"",
727  }
728 
729  /* deal with any deletable tuples */
730  vacuumLeafPage(bds, index, buffer, true);
731  /* might as well do this while we are here */
733 
734  SpGistSetLastUsedPage(index, buffer);
735 
736  /*
737  * We can mark as done not only this item, but any later ones
738  * pointing at the same page, since we vacuumed the whole page.
739  */
740  pitem->done = true;
741  for (nitem = pitem->next; nitem != NULL; nitem = nitem->next)
742  {
743  if (ItemPointerGetBlockNumber(&nitem->tid) == blkno)
744  nitem->done = true;
745  }
746  }
747  else
748  {
749  /*
750  * On an inner page, visit the referenced inner tuple and add all
751  * its downlinks to the pending list. We might have pending items
752  * for more than one inner tuple on the same page (in fact this is
753  * pretty likely given the way space allocation works), so get
754  * them all while we are here.
755  */
756  for (nitem = pitem; nitem != NULL; nitem = nitem->next)
757  {
758  if (nitem->done)
759  continue;
760  if (ItemPointerGetBlockNumber(&nitem->tid) == blkno)
761  {
762  OffsetNumber offset;
763  SpGistInnerTuple innerTuple;
764 
765  offset = ItemPointerGetOffsetNumber(&nitem->tid);
766  innerTuple = (SpGistInnerTuple) PageGetItem(page,
767  PageGetItemId(page, offset));
768  if (innerTuple->tupstate == SPGIST_LIVE)
769  {
770  SpGistNodeTuple node;
771  int i;
772 
773  SGITITERATE(innerTuple, i, node)
774  {
775  if (ItemPointerIsValid(&node->t_tid))
776  spgAddPendingTID(bds, &node->t_tid);
777  }
778  }
779  else if (innerTuple->tupstate == SPGIST_REDIRECT)
780  {
781  /* transfer attention to redirect point */
782  spgAddPendingTID(bds,
783  &((SpGistDeadTuple) innerTuple)->pointer);
784  }
785  else
786  elog(ERROR, "unexpected SPGiST tuple state: %d",
787  innerTuple->tupstate);
788 
789  nitem->done = true;
790  }
791  }
792  }
793 
794  UnlockReleaseBuffer(buffer);
795  }
796 
797  spgClearPendingList(bds);
798 }
uint32 BlockNumber
Definition: block.h:31
int Buffer
Definition: buf.h:23
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:4953
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:5171
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:820
static Page BufferGetPage(Buffer buffer)
Definition: bufmgr.h:400
#define BUFFER_LOCK_EXCLUSIVE
Definition: bufmgr.h:191
@ RBM_NORMAL
Definition: bufmgr.h:45
Pointer Page
Definition: bufpage.h:81
static Item PageGetItem(Page page, ItemId itemId)
Definition: bufpage.h:354
static ItemId PageGetItemId(Page page, OffsetNumber offsetNumber)
Definition: bufpage.h:243
static bool PageIsNew(Page page)
Definition: bufpage.h:233
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:224
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:539
@ 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:665
static void vacuumRedirectAndPlaceholder(Relation index, Relation heaprel, Buffer buffer)
Definition: spgvacuum.c:493
static void vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer, bool forPending)
Definition: spgvacuum.c:125
static void spgClearPendingList(spgBulkDeleteState *bds)
Definition: spgvacuum.c:89
static void spgAddPendingTID(spgBulkDeleteState *bds, ItemPointer tid)
Definition: spgvacuum.c:63
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:2340

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 947 of file spgvacuum.c.

948 {
949  spgBulkDeleteState bds;
950 
951  /* No-op in ANALYZE ONLY mode */
952  if (info->analyze_only)
953  return stats;
954 
955  /*
956  * We don't need to scan the index if there was a preceding bulkdelete
957  * pass. Otherwise, make a pass that won't delete any live tuples, but
958  * might still accomplish useful stuff with redirect/placeholder cleanup
959  * and/or FSM housekeeping, and in any case will provide stats.
960  */
961  if (stats == NULL)
962  {
964  bds.info = info;
965  bds.stats = stats;
966  bds.callback = dummy_callback;
967  bds.callback_state = NULL;
968 
969  spgvacuumscan(&bds);
970  }
971 
972  /*
973  * It's quite possible for us to be fooled by concurrent tuple moves into
974  * double-counting some index tuples, so disbelieve any total that exceeds
975  * the underlying heap's count ... if we know that accurately. Otherwise
976  * this might just make matters worse.
977  */
978  if (!info->estimated_count)
979  {
980  if (stats->num_index_tuples > info->num_heap_tuples)
981  stats->num_index_tuples = info->num_heap_tuples;
982  }
983 
984  return stats;
985 }
static bool dummy_callback(ItemPointer itemptr, void *state)
Definition: spgvacuum.c:936
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 621 of file spgvacuum.c.

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

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 804 of file spgvacuum.c.

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

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 125 of file spgvacuum.c.

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

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

494 {
495  Page page = BufferGetPage(buffer);
496  SpGistPageOpaque opaque = SpGistPageGetOpaque(page);
497  OffsetNumber i,
498  max = PageGetMaxOffsetNumber(page),
499  firstPlaceholder = InvalidOffsetNumber;
500  bool hasNonPlaceholder = false;
501  bool hasUpdate = false;
502  OffsetNumber itemToPlaceholder[MaxIndexTuplesPerPage];
504  spgxlogVacuumRedirect xlrec;
505  GlobalVisState *vistest;
506 
508  xlrec.nToPlaceholder = 0;
510 
511  vistest = GlobalVisTestFor(heaprel);
512 
514 
515  /*
516  * Scan backwards to convert old redirection tuples to placeholder tuples,
517  * and identify location of last non-placeholder tuple while at it.
518  */
519  for (i = max;
520  i >= FirstOffsetNumber &&
521  (opaque->nRedirection > 0 || !hasNonPlaceholder);
522  i--)
523  {
524  SpGistDeadTuple dt;
525 
526  dt = (SpGistDeadTuple) PageGetItem(page, PageGetItemId(page, i));
527 
528  /*
529  * We can convert a REDIRECT to a PLACEHOLDER if there could no longer
530  * be any index scans "in flight" to it. Such an index scan would
531  * have to be in a transaction whose snapshot sees the REDIRECT's XID
532  * as still running, so comparing the XID against global xmin is a
533  * conservatively safe test. If the XID is invalid, it must have been
534  * inserted by REINDEX CONCURRENTLY, so we can zap it immediately.
535  */
536  if (dt->tupstate == SPGIST_REDIRECT &&
537  (!TransactionIdIsValid(dt->xid) ||
538  GlobalVisTestIsRemovableXid(vistest, dt->xid)))
539  {
541  Assert(opaque->nRedirection > 0);
542  opaque->nRedirection--;
543  opaque->nPlaceholder++;
544 
545  /* remember newest XID among the removed redirects */
548  xlrec.snapshotConflictHorizon = dt->xid;
549 
551 
552  itemToPlaceholder[xlrec.nToPlaceholder] = i;
553  xlrec.nToPlaceholder++;
554 
555  hasUpdate = true;
556  }
557 
558  if (dt->tupstate == SPGIST_PLACEHOLDER)
559  {
560  if (!hasNonPlaceholder)
561  firstPlaceholder = i;
562  }
563  else
564  {
565  hasNonPlaceholder = true;
566  }
567  }
568 
569  /*
570  * Any placeholder tuples at the end of page can safely be removed. We
571  * can't remove ones before the last non-placeholder, though, because we
572  * can't alter the offset numbers of non-placeholder tuples.
573  */
574  if (firstPlaceholder != InvalidOffsetNumber)
575  {
576  /*
577  * We do not store this array to rdata because it's easy to recreate.
578  */
579  for (i = firstPlaceholder; i <= max; i++)
580  itemnos[i - firstPlaceholder] = i;
581 
582  i = max - firstPlaceholder + 1;
583  Assert(opaque->nPlaceholder >= i);
584  opaque->nPlaceholder -= i;
585 
586  /* The array is surely sorted, so can use PageIndexMultiDelete */
587  PageIndexMultiDelete(page, itemnos, i);
588 
589  hasUpdate = true;
590  }
591 
592  xlrec.firstPlaceholder = firstPlaceholder;
593 
594  if (hasUpdate)
595  MarkBufferDirty(buffer);
596 
597  if (hasUpdate && RelationNeedsWAL(index))
598  {
599  XLogRecPtr recptr;
600 
601  XLogBeginInsert();
602 
604  XLogRegisterData((char *) itemToPlaceholder,
605  sizeof(OffsetNumber) * xlrec.nToPlaceholder);
606 
608 
609  recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_VACUUM_REDIRECT);
610 
611  PageSetLSN(page, recptr);
612  }
613 
615 }
static void ItemPointerSetInvalid(ItemPointerData *pointer)
Definition: itemptr.h:184
bool GlobalVisTestIsRemovableXid(GlobalVisState *state, TransactionId xid)
Definition: procarray.c:4268
GlobalVisState * GlobalVisTestFor(Relation rel)
Definition: procarray.c:4111
#define RelationIsAccessibleInLogicalDecoding(relation)
Definition: rel.h:684
#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().