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{
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}
Assert(PointerIsAligned(start, uint64))
void pfree(void *pointer)
Definition: mcxt.c:1524

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 */
707 vacuum_delay_point(false);
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
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 */
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
798}
uint32 BlockNumber
Definition: block.h:31
int Buffer
Definition: buf.h:23
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:4869
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:5086
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:795
static Page BufferGetPage(Buffer buffer)
Definition: bufmgr.h:400
#define BUFFER_LOCK_EXCLUSIVE
Definition: bufmgr.h:191
@ RBM_NORMAL
Definition: bufmgr.h:45
static Item PageGetItem(const PageData *page, const ItemIdData *itemId)
Definition: bufpage.h:354
static bool PageIsNew(const PageData *page)
Definition: bufpage.h:234
static ItemId PageGetItemId(Page page, OffsetNumber offsetNumber)
Definition: bufpage.h:244
PageData * Page
Definition: bufpage.h:82
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:225
int i
Definition: isn.c:74
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:546
@ MAIN_FORKNUM
Definition: relpath.h:58
#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:673
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:69
BufferAccessStrategy strategy
Definition: genam.h:76
Relation heaprel
Definition: genam.h:70
Definition: type.h:96
void vacuum_delay_point(bool is_analyze)
Definition: vacuum.c:2393

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{
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;
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:102
double num_heap_tuples
Definition: genam.h:75
bool analyze_only
Definition: genam.h:71
bool estimated_count
Definition: genam.h:73

References IndexVacuumInfo::analyze_only, 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 */
628 vacuum_delay_point(false);
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 {
676 bds->stats->pages_deleted++;
677 }
678 else
679 {
681 bds->lastFilledBlock = blkno;
682 }
683 }
684
685 UnlockReleaseBuffer(buffer);
686}
static bool PageIsEmpty(const PageData *page)
Definition: bufpage.h:224
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:105
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)
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:424
void UnlockRelationForExtension(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:474
#define ExclusiveLock
Definition: lockdefs.h:42
#define RELATION_IS_LOCAL(relation)
Definition: rel.h:655
Snapshot GetActiveSnapshot(void)
Definition: snapmgr.c:787
#define SPGIST_METAPAGE_BLKNO
#define SPGIST_LAST_FIXED_BLKNO
void initSpGistState(SpGistState *state, Relation index)
Definition: spgutils.c:348
void SpGistUpdateMetaPage(Relation index)
Definition: spgutils.c:450
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
BlockNumber pages_newly_deleted
Definition: genam.h:104
BlockNumber pages_free
Definition: genam.h:106
BlockNumber num_pages
Definition: genam.h:100
TransactionId xmin
Definition: snapshot.h:153
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;
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 {
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 */
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;
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 {
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
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
331 toDead, xlrec.nDead,
334
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
359 moveSrc, xlrec.nMove,
362
363 for (i = 0; i < xlrec.nChain; i++)
364 {
366
367 lt = (SpGistLeafTuple) PageGetItem(page,
368 PageGetItemId(page, chainSrc[i]));
370 SGLT_SET_NEXTOFFSET(lt, chainDest[i]);
371 }
372
373 MarkBufferDirty(buffer);
374
376 {
377 XLogRecPtr recptr;
378
380
381 STORE_STATE(&bds->spgstate, xlrec.stateSrc);
382
384 /* sizeof(xlrec) should be a multiple of sizeof(OffsetNumber) */
385 XLogRegisterData(toDead, sizeof(OffsetNumber) * xlrec.nDead);
386 XLogRegisterData(toPlaceholder, sizeof(OffsetNumber) * xlrec.nPlaceholder);
387 XLogRegisterData(moveSrc, sizeof(OffsetNumber) * xlrec.nMove);
388 XLogRegisterData(moveDest, sizeof(OffsetNumber) * xlrec.nMove);
389 XLogRegisterData(chainSrc, sizeof(OffsetNumber) * xlrec.nChain);
390 XLogRegisterData(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:3730
void MarkBufferDirty(Buffer buffer)
Definition: bufmgr.c:2531
static void PageSetLSN(Page page, XLogRecPtr lsn)
Definition: bufpage.h:391
static OffsetNumber PageGetMaxOffsetNumber(const PageData *page)
Definition: bufpage.h:372
int j
Definition: isn.c:75
#define MaxIndexTuplesPerPage
Definition: itup.h:181
#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:635
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:103
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
XLogRecPtr XLogInsert(RmgrId rmid, uint8 info)
Definition: xloginsert.c:474
void XLogRegisterData(const void *data, uint32 len)
Definition: xloginsert.c:364
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:35

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;
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 {
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
460 {
461 XLogRecPtr recptr;
462
464
465 /* Prepare WAL record */
466 STORE_STATE(&bds->spgstate, xlrec.stateSrc);
467
469 /* sizeof(xlrec) should be a multiple of sizeof(OffsetNumber) */
470 XLogRegisterData(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:1150
#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);
498 max = PageGetMaxOffsetNumber(page),
499 firstPlaceholder = InvalidOffsetNumber;
500 bool hasNonPlaceholder = false;
501 bool hasUpdate = false;
502 OffsetNumber itemToPlaceholder[MaxIndexTuplesPerPage];
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 {
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
602
604 XLogRegisterData(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:4264
GlobalVisState * GlobalVisTestFor(Relation rel)
Definition: procarray.c:4107
#define RelationIsAccessibleInLogicalDecoding(relation)
Definition: rel.h:691
#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().