PostgreSQL Source Code git master
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
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 "storage/read_stream.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, Buffer buffer)
 
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 965 of file spgvacuum.c.

966{
967 return false;
968}

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:1317
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 945 of file spgvacuum.c.

947{
949
950 /* allocate stats if first time through, else re-use existing struct */
951 if (stats == NULL)
953 bds.info = info;
954 bds.stats = stats;
955 bds.callback = callback;
956 bds.callback_state = callback_state;
957
958 spgvacuumscan(&bds);
959
960 return stats;
961}
void * palloc0(Size size)
Definition: mcxt.c:1347
static void spgvacuumscan(spgBulkDeleteState *bds)
Definition: spgvacuum.c:800
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(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 688 of file spgvacuum.c.

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

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

977{
979
980 /* No-op in ANALYZE ONLY mode */
981 if (info->analyze_only)
982 return stats;
983
984 /*
985 * We don't need to scan the index if there was a preceding bulkdelete
986 * pass. Otherwise, make a pass that won't delete any live tuples, but
987 * might still accomplish useful stuff with redirect/placeholder cleanup
988 * and/or FSM housekeeping, and in any case will provide stats.
989 */
990 if (stats == NULL)
991 {
993 bds.info = info;
994 bds.stats = stats;
996 bds.callback_state = NULL;
997
998 spgvacuumscan(&bds);
999 }
1000
1001 /*
1002 * It's quite possible for us to be fooled by concurrent tuple moves into
1003 * double-counting some index tuples, so disbelieve any total that exceeds
1004 * the underlying heap's count ... if we know that accurately. Otherwise
1005 * this might just make matters worse.
1006 */
1007 if (!info->estimated_count)
1008 {
1009 if (stats->num_index_tuples > info->num_heap_tuples)
1010 stats->num_index_tuples = info->num_heap_tuples;
1011 }
1012
1013 return stats;
1014}
static bool dummy_callback(ItemPointer itemptr, void *state)
Definition: spgvacuum.c:965
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,
Buffer  buffer 
)
static

Definition at line 622 of file spgvacuum.c.

623{
624 Relation index = bds->info->index;
625 BlockNumber blkno = BufferGetBlockNumber(buffer);
626 Page page;
627
629 page = (Page) BufferGetPage(buffer);
630
631 if (PageIsNew(page))
632 {
633 /*
634 * We found an all-zero page, which could happen if the database
635 * crashed just after extending the file. Recycle it.
636 */
637 }
638 else if (PageIsEmpty(page))
639 {
640 /* nothing to do */
641 }
642 else if (SpGistPageIsLeaf(page))
643 {
644 if (SpGistBlockIsRoot(blkno))
645 {
646 vacuumLeafRoot(bds, index, buffer);
647 /* no need for vacuumRedirectAndPlaceholder */
648 }
649 else
650 {
651 vacuumLeafPage(bds, index, buffer, false);
653 }
654 }
655 else
656 {
657 /* inner page */
659 }
660
661 /*
662 * The root pages must never be deleted, nor marked as available in FSM,
663 * because we don't want them ever returned by a search for a place to put
664 * a new tuple. Otherwise, check for empty page, and make sure the FSM
665 * knows about it.
666 */
667 if (!SpGistBlockIsRoot(blkno))
668 {
669 if (PageIsNew(page) || PageIsEmpty(page))
670 {
672 bds->stats->pages_deleted++;
673 }
674 else
675 {
677 bds->lastFilledBlock = blkno;
678 }
679 }
680
681 UnlockReleaseBuffer(buffer);
682}
BlockNumber BufferGetBlockNumber(Buffer buffer)
Definition: bufmgr.c:3795
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:409
BlockNumber pages_deleted
Definition: genam.h:105
BlockNumber lastFilledBlock
Definition: spgvacuum.c:53

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

Referenced by spgvacuumscan().

◆ spgvacuumscan()

static void spgvacuumscan ( spgBulkDeleteState bds)
static

Definition at line 800 of file spgvacuum.c.

801{
802 Relation index = bds->info->index;
803 bool needLock;
804 BlockNumber num_pages;
806 ReadStream *stream = NULL;
807
808 /* Finish setting up spgBulkDeleteState */
810 bds->pendingList = NULL;
811 bds->myXmin = GetActiveSnapshot()->xmin;
813
814 /*
815 * Reset counts that will be incremented during the scan; needed in case
816 * of multiple scans during a single VACUUM command
817 */
818 bds->stats->estimated_count = false;
819 bds->stats->num_index_tuples = 0;
820 bds->stats->pages_deleted = 0;
821
822 /* We can skip locking for new or temp relations */
823 needLock = !RELATION_IS_LOCAL(index);
826 bds->info->strategy,
827 index,
830 &p,
831 0);
832
833 /*
834 * The outer loop iterates over all index pages except the metapage, in
835 * physical order (we hope the kernel will cooperate in providing
836 * read-ahead for speed). It is critical that we visit all leaf pages,
837 * including ones added after we start the scan, else we might fail to
838 * delete some deletable tuples. See more extensive comments about this
839 * in btvacuumscan().
840 */
841 for (;;)
842 {
843 /* Get the current relation length */
844 if (needLock)
846 num_pages = RelationGetNumberOfBlocks(index);
847 if (needLock)
849
850 /* Quit if we've scanned the whole relation */
851 if (p.current_blocknum >= num_pages)
852 break;
853
854 p.last_exclusive = num_pages;
855
856 /* Iterate over pages, then loop back to recheck length */
857 while (true)
858 {
859 Buffer buf;
860
861 /* call vacuum_delay_point while not holding any buffer lock */
862 vacuum_delay_point(false);
863
864 buf = read_stream_next_buffer(stream, NULL);
865
866 if (!BufferIsValid(buf))
867 break;
868
869 spgvacuumpage(bds, buf);
870
871 /* empty the pending-list after each page */
872 if (bds->pendingList != NULL)
874 }
875
877
878 /*
879 * We have to reset the read stream to use it again. After returning
880 * InvalidBuffer, the read stream API won't invoke our callback again
881 * until the stream has been reset.
882 */
883 read_stream_reset(stream);
884 }
885
886 read_stream_end(stream);
887
888 /* Propagate local lastUsedPages cache to metablock */
890
891 /*
892 * If we found any empty pages (and recorded them in the FSM), then
893 * forcibly update the upper-level FSM pages to ensure that searchers can
894 * find them. It's possible that the pages were also found during
895 * previous scans and so this is a waste of time, but it's cheap enough
896 * relative to scanning the index that it shouldn't matter much, and
897 * making sure that free pages are available sooner not later seems
898 * worthwhile.
899 *
900 * Note that if no empty pages exist, we don't bother vacuuming the FSM at
901 * all.
902 */
903 if (bds->stats->pages_deleted > 0)
905
906 /*
907 * Truncate index if possible
908 *
909 * XXX disabled because it's unsafe due to possible concurrent inserts.
910 * We'd have to rescan the pages to make sure they're still empty, and it
911 * doesn't seem worth it. Note that btree doesn't do this either.
912 *
913 * Another reason not to truncate is that it could invalidate the cached
914 * pages-with-freespace pointers in the metapage and other backends'
915 * relation caches, that is leave them pointing to nonexistent pages.
916 * Adding RelationGetNumberOfBlocks calls to protect the places that use
917 * those pointers would be unduly expensive.
918 */
919#ifdef NOT_USED
920 if (num_pages > bds->lastFilledBlock + 1)
921 {
922 BlockNumber lastBlock = num_pages - 1;
923
924 num_pages = bds->lastFilledBlock + 1;
925 RelationTruncate(index, num_pages);
926 bds->stats->pages_removed += lastBlock - bds->lastFilledBlock;
927 bds->stats->pages_deleted -= lastBlock - bds->lastFilledBlock;
928 }
929#endif
930
931 /* Report final stats */
932 bds->stats->num_pages = num_pages;
934 bds->stats->pages_free = bds->stats->pages_deleted;
935}
#define InvalidBuffer
Definition: buf.h:25
#define RelationGetNumberOfBlocks(reln)
Definition: bufmgr.h:274
static bool BufferIsValid(Buffer bufnum)
Definition: bufmgr.h:352
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
static char * buf
Definition: pg_test_fsync.c:72
void read_stream_reset(ReadStream *stream)
Definition: read_stream.c:978
Buffer read_stream_next_buffer(ReadStream *stream, void **per_buffer_data)
Definition: read_stream.c:742
ReadStream * read_stream_begin_relation(int flags, BufferAccessStrategy strategy, Relation rel, ForkNumber forknum, ReadStreamBlockNumberCB callback, void *callback_private_data, size_t per_buffer_data_size)
Definition: read_stream.c:688
void read_stream_end(ReadStream *stream)
Definition: read_stream.c:1023
BlockNumber block_range_read_stream_cb(ReadStream *stream, void *callback_private_data, void *per_buffer_data)
Definition: read_stream.c:158
#define READ_STREAM_FULL
Definition: read_stream.h:43
#define RELATION_IS_LOCAL(relation)
Definition: rel.h:656
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:688
static void spgvacuumpage(spgBulkDeleteState *bds, Buffer buffer)
Definition: spgvacuum.c:622
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:50
TransactionId myXmin
Definition: spgvacuum.c:52

References Assert(), block_range_read_stream_cb(), buf, BufferIsValid(), BlockRangeReadStreamPrivate::current_blocknum, IndexBulkDeleteResult::estimated_count, ExclusiveLock, GetActiveSnapshot(), IndexVacuumInfo::index, IndexFreeSpaceMapVacuum(), spgBulkDeleteState::info, initSpGistState(), InvalidBuffer, BlockRangeReadStreamPrivate::last_exclusive, spgBulkDeleteState::lastFilledBlock, LockRelationForExtension(), MAIN_FORKNUM, spgBulkDeleteState::myXmin, IndexBulkDeleteResult::num_index_tuples, IndexBulkDeleteResult::num_pages, IndexBulkDeleteResult::pages_deleted, IndexBulkDeleteResult::pages_free, IndexBulkDeleteResult::pages_newly_deleted, spgBulkDeleteState::pendingList, read_stream_begin_relation(), read_stream_end(), READ_STREAM_FULL, read_stream_next_buffer(), read_stream_reset(), RELATION_IS_LOCAL, RelationGetNumberOfBlocks, RelationTruncate(), SPGIST_LAST_FIXED_BLKNO, SPGIST_METAPAGE_BLKNO, SpGistUpdateMetaPage(), spgprocesspending(), spgBulkDeleteState::spgstate, spgvacuumpage(), spgBulkDeleteState::stats, IndexVacuumInfo::strategy, UnlockRelationForExtension(), vacuum_delay_point(), 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;
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 {
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. (If xid is invalid, assume it
193 * must have happened before VACUUM started, since REINDEX
194 * CONCURRENTLY locks out VACUUM.)
195 *
196 * Note: we could make a tighter test by seeing if the xid is
197 * "running" according to the active snapshot; but snapmgr.c
198 * doesn't currently export a suitable API, and it's not entirely
199 * clear that a tighter test is worth the cycles anyway.
200 */
202 spgAddPendingTID(bds, &dt->pointer);
203 }
204 else
205 {
207 }
208 }
209
210 if (nDeletable == 0)
211 return; /* nothing more to do */
212
213 /*----------
214 * Figure out exactly what we have to do. We do this separately from
215 * actually modifying the page, mainly so that we have a representation
216 * that can be dumped into WAL and then the replay code can do exactly
217 * the same thing. The output of this step consists of six arrays
218 * describing four kinds of operations, to be performed in this order:
219 *
220 * toDead[]: tuple numbers to be replaced with DEAD tuples
221 * toPlaceholder[]: tuple numbers to be replaced with PLACEHOLDER tuples
222 * moveSrc[]: tuple numbers that need to be relocated to another offset
223 * (replacing the tuple there) and then replaced with PLACEHOLDER tuples
224 * moveDest[]: new locations for moveSrc tuples
225 * chainSrc[]: tuple numbers whose chain links (nextOffset) need updates
226 * chainDest[]: new values of nextOffset for chainSrc members
227 *
228 * It's easiest to figure out what we have to do by processing tuple
229 * chains, so we iterate over all the tuples (not just the deletable
230 * ones!) to identify chain heads, then chase down each chain and make
231 * work item entries for deletable tuples within the chain.
232 *----------
233 */
234 xlrec.nDead = xlrec.nPlaceholder = xlrec.nMove = xlrec.nChain = 0;
235
236 for (i = FirstOffsetNumber; i <= max; i++)
237 {
238 SpGistLeafTuple head;
239 bool interveningDeletable;
240 OffsetNumber prevLive;
242
243 head = (SpGistLeafTuple) PageGetItem(page,
244 PageGetItemId(page, i));
245 if (head->tupstate != SPGIST_LIVE)
246 continue; /* can't be a chain member */
247 if (predecessor[i] != 0)
248 continue; /* not a chain head */
249
250 /* initialize ... */
251 interveningDeletable = false;
252 prevLive = deletable[i] ? InvalidOffsetNumber : i;
253
254 /* scan down the chain ... */
255 j = SGLT_GET_NEXTOFFSET(head);
256 while (j != InvalidOffsetNumber)
257 {
259
260 lt = (SpGistLeafTuple) PageGetItem(page,
261 PageGetItemId(page, j));
262 if (lt->tupstate != SPGIST_LIVE)
263 {
264 /* all tuples in chain should be live */
265 elog(ERROR, "unexpected SPGiST tuple state: %d",
266 lt->tupstate);
267 }
268
269 if (deletable[j])
270 {
271 /* This tuple should be replaced by a placeholder */
272 toPlaceholder[xlrec.nPlaceholder] = j;
273 xlrec.nPlaceholder++;
274 /* previous live tuple's chain link will need an update */
275 interveningDeletable = true;
276 }
277 else if (prevLive == InvalidOffsetNumber)
278 {
279 /*
280 * This is the first live tuple in the chain. It has to move
281 * to the head position.
282 */
283 moveSrc[xlrec.nMove] = j;
284 moveDest[xlrec.nMove] = i;
285 xlrec.nMove++;
286 /* Chain updates will be applied after the move */
287 prevLive = i;
288 interveningDeletable = false;
289 }
290 else
291 {
292 /*
293 * Second or later live tuple. Arrange to re-chain it to the
294 * previous live one, if there was a gap.
295 */
296 if (interveningDeletable)
297 {
298 chainSrc[xlrec.nChain] = prevLive;
299 chainDest[xlrec.nChain] = j;
300 xlrec.nChain++;
301 }
302 prevLive = j;
303 interveningDeletable = false;
304 }
305
307 }
308
309 if (prevLive == InvalidOffsetNumber)
310 {
311 /* The chain is entirely removable, so we need a DEAD tuple */
312 toDead[xlrec.nDead] = i;
313 xlrec.nDead++;
314 }
315 else if (interveningDeletable)
316 {
317 /* One or more deletions at end of chain, so close it off */
318 chainSrc[xlrec.nChain] = prevLive;
319 chainDest[xlrec.nChain] = InvalidOffsetNumber;
320 xlrec.nChain++;
321 }
322 }
323
324 /* sanity check ... */
325 if (nDeletable != xlrec.nDead + xlrec.nPlaceholder + xlrec.nMove)
326 elog(ERROR, "inconsistent counts of deletable tuples");
327
328 /* Do the updates */
330
332 toDead, xlrec.nDead,
335
337 toPlaceholder, xlrec.nPlaceholder,
340
341 /*
342 * We implement the move step by swapping the line pointers of the source
343 * and target tuples, then replacing the newly-source tuples with
344 * placeholders. This is perhaps unduly friendly with the page data
345 * representation, but it's fast and doesn't risk page overflow when a
346 * tuple to be relocated is large.
347 */
348 for (i = 0; i < xlrec.nMove; i++)
349 {
350 ItemId idSrc = PageGetItemId(page, moveSrc[i]);
351 ItemId idDest = PageGetItemId(page, moveDest[i]);
352 ItemIdData tmp;
353
354 tmp = *idSrc;
355 *idSrc = *idDest;
356 *idDest = tmp;
357 }
358
360 moveSrc, xlrec.nMove,
363
364 for (i = 0; i < xlrec.nChain; i++)
365 {
367
368 lt = (SpGistLeafTuple) PageGetItem(page,
369 PageGetItemId(page, chainSrc[i]));
371 SGLT_SET_NEXTOFFSET(lt, chainDest[i]);
372 }
373
374 MarkBufferDirty(buffer);
375
377 {
378 XLogRecPtr recptr;
379
381
382 STORE_STATE(&bds->spgstate, xlrec.stateSrc);
383
385 /* sizeof(xlrec) should be a multiple of sizeof(OffsetNumber) */
386 XLogRegisterData(toDead, sizeof(OffsetNumber) * xlrec.nDead);
387 XLogRegisterData(toPlaceholder, sizeof(OffsetNumber) * xlrec.nPlaceholder);
388 XLogRegisterData(moveSrc, sizeof(OffsetNumber) * xlrec.nMove);
389 XLogRegisterData(moveDest, sizeof(OffsetNumber) * xlrec.nMove);
390 XLogRegisterData(chainSrc, sizeof(OffsetNumber) * xlrec.nChain);
391 XLogRegisterData(chainDest, sizeof(OffsetNumber) * xlrec.nChain);
392
394
395 recptr = XLogInsert(RM_SPGIST_ID, XLOG_SPGIST_VACUUM_LEAF);
396
397 PageSetLSN(page, recptr);
398 }
399
401}
#define InvalidBlockNumber
Definition: block.h:33
void MarkBufferDirty(Buffer buffer)
Definition: bufmgr.c:2596
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:636
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 409 of file spgvacuum.c.

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

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