PostgreSQL Source Code git master
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
spgvacuum.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * spgvacuum.c
4 * vacuum for SP-GiST
5 *
6 *
7 * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
8 * Portions Copyright (c) 1994, Regents of the University of California
9 *
10 * IDENTIFICATION
11 * src/backend/access/spgist/spgvacuum.c
12 *
13 *-------------------------------------------------------------------------
14 */
15
16#include "postgres.h"
17
18#include "access/genam.h"
20#include "access/spgxlog.h"
21#include "access/transam.h"
22#include "access/xloginsert.h"
23#include "commands/vacuum.h"
24#include "miscadmin.h"
25#include "storage/bufmgr.h"
26#include "storage/indexfsm.h"
27#include "storage/lmgr.h"
28#include "utils/snapmgr.h"
29
30
31/* Entry in pending-list of TIDs we need to revisit */
32typedef struct spgVacPendingItem
33{
34 ItemPointerData tid; /* redirection target to visit */
35 bool done; /* have we dealt with this? */
36 struct spgVacPendingItem *next; /* list link */
38
39/* Local state for vacuum operations */
40typedef struct spgBulkDeleteState
41{
42 /* Parameters passed in to spgvacuumscan */
47
48 /* Additional working state */
49 SpGistState spgstate; /* for SPGiST operations that need one */
50 spgVacPendingItem *pendingList; /* TIDs we need to (re)visit */
51 TransactionId myXmin; /* for detecting newly-added redirects */
52 BlockNumber lastFilledBlock; /* last non-deletable block */
54
55
56/*
57 * Add TID to pendingList, but only if not already present.
58 *
59 * Note that new items are always appended at the end of the list; this
60 * ensures that scans of the list don't miss items added during the scan.
61 */
62static void
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}
84
85/*
86 * Clear pendingList
87 */
88static void
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}
103
104/*
105 * Vacuum a regular (non-root) leaf page
106 *
107 * We must delete tuples that are targeted for deletion by the VACUUM,
108 * but not move any tuples that are referenced by outside links; we assume
109 * those are the ones that are heads of chains.
110 *
111 * If we find a REDIRECT that was made by a concurrently-running transaction,
112 * we must add its target TID to pendingList. (We don't try to visit the
113 * target immediately, first because we don't want VACUUM locking more than
114 * one buffer at a time, and second because the duplicate-filtering logic
115 * in spgAddPendingTID is useful to ensure we can't get caught in an infinite
116 * loop in the face of continuous concurrent insertions.)
117 *
118 * If forPending is true, we are examining the page as a consequence of
119 * chasing a redirect link, not as part of the normal sequential scan.
120 * We still vacuum the page normally, but we don't increment the stats
121 * about live tuples; else we'd double-count those tuples, since the page
122 * has been or will be visited in the sequential scan as well.
123 */
124static void
126 bool forPending)
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((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}
401
402/*
403 * Vacuum a root page when it is also a leaf
404 *
405 * On the root, we just delete any dead leaf tuples; no fancy business
406 */
407static void
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((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}
482
483/*
484 * Clean up redirect and placeholder tuples on the given page
485 *
486 * Redirect tuples can be marked placeholder once they're old enough.
487 * Placeholder tuples can be removed if it won't change the offsets of
488 * non-placeholder ones.
489 *
490 * Unlike the routines above, this works on both leaf and inner pages.
491 */
492static void
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((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}
616
617/*
618 * Process one page during a bulkdelete scan
619 */
620static void
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 {
676 bds->stats->pages_deleted++;
677 }
678 else
679 {
681 bds->lastFilledBlock = blkno;
682 }
683 }
684
685 UnlockReleaseBuffer(buffer);
686}
687
688/*
689 * Process the pending-TID list between pages of the main scan
690 */
691static void
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
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}
799
800/*
801 * Perform a bulkdelete scan
802 */
803static void
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}
907
908/*
909 * Bulk deletion of all index entries pointing to a set of heap tuples.
910 * The set of target tuples is specified via a callback routine that tells
911 * whether any given heap tuple (identified by ItemPointer) is being deleted.
912 *
913 * Result: a palloc'd struct containing statistical info for VACUUM displays.
914 */
917 IndexBulkDeleteCallback callback, void *callback_state)
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}
933
934/* Dummy callback to delete no tuples during spgvacuumcleanup */
935static bool
937{
938 return false;
939}
940
941/*
942 * Post-VACUUM cleanup.
943 *
944 * Result: a palloc'd struct containing statistical info for VACUUM displays.
945 */
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}
uint32 BlockNumber
Definition: block.h:31
#define InvalidBlockNumber
Definition: block.h:33
int Buffer
Definition: buf.h:23
BlockNumber BufferGetBlockNumber(Buffer buffer)
Definition: bufmgr.c:3724
void UnlockReleaseBuffer(Buffer buffer)
Definition: bufmgr.c:4941
void MarkBufferDirty(Buffer buffer)
Definition: bufmgr.c:2532
void LockBuffer(Buffer buffer, int mode)
Definition: bufmgr.c:5158
Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy)
Definition: bufmgr.c:793
#define RelationGetNumberOfBlocks(reln)
Definition: bufmgr.h:273
static Page BufferGetPage(Buffer buffer)
Definition: bufmgr.h:400
#define BUFFER_LOCK_EXCLUSIVE
Definition: bufmgr.h:191
@ RBM_NORMAL
Definition: bufmgr.h:45
void PageIndexMultiDelete(Page page, OffsetNumber *itemnos, int nitems)
Definition: bufpage.c:1150
static bool PageIsEmpty(Page page)
Definition: bufpage.h:223
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
static void PageSetLSN(Page page, XLogRecPtr lsn)
Definition: bufpage.h:391
static OffsetNumber PageGetMaxOffsetNumber(Page page)
Definition: bufpage.h:372
#define Assert(condition)
Definition: c.h:812
uint32 TransactionId
Definition: c.h:606
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:225
bool(* IndexBulkDeleteCallback)(ItemPointer itemptr, void *state)
Definition: genam.h:89
void IndexFreeSpaceMapVacuum(Relation rel)
Definition: indexfsm.c:71
void RecordFreeIndexPage(Relation rel, BlockNumber freeBlock)
Definition: indexfsm.c:52
int j
Definition: isn.c:73
int i
Definition: isn.c:72
bool ItemPointerEquals(ItemPointer pointer1, ItemPointer pointer2)
Definition: itemptr.c:35
static void ItemPointerSetInvalid(ItemPointerData *pointer)
Definition: itemptr.h:184
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
#define MaxIndexTuplesPerPage
Definition: itup.h:167
void LockRelationForExtension(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:419
void UnlockRelationForExtension(Relation relation, LOCKMODE lockmode)
Definition: lmgr.c:469
#define ExclusiveLock
Definition: lockdefs.h:42
void pfree(void *pointer)
Definition: mcxt.c:1521
void * palloc0(Size size)
Definition: mcxt.c:1347
void * palloc(Size size)
Definition: mcxt.c:1317
#define START_CRIT_SECTION()
Definition: miscadmin.h:149
#define END_CRIT_SECTION()
Definition: miscadmin.h:151
#define InvalidOffsetNumber
Definition: off.h:26
uint16 OffsetNumber
Definition: off.h:24
#define FirstOffsetNumber
Definition: off.h:27
bool GlobalVisTestIsRemovableXid(GlobalVisState *state, TransactionId xid)
Definition: procarray.c:4264
GlobalVisState * GlobalVisTestFor(Relation rel)
Definition: procarray.c:4107
#define RELATION_IS_LOCAL(relation)
Definition: rel.h:648
#define RelationGetRelationName(relation)
Definition: rel.h:539
#define RelationIsAccessibleInLogicalDecoding(relation)
Definition: rel.h:684
#define RelationNeedsWAL(relation)
Definition: rel.h:628
@ MAIN_FORKNUM
Definition: relpath.h:58
Snapshot GetActiveSnapshot(void)
Definition: snapmgr.c:728
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 SPGIST_REDIRECT
SpGistInnerTupleData * SpGistInnerTuple
#define SPGIST_LIVE
#define SGLT_GET_NEXTOFFSET(spgLeafTuple)
#define SPGIST_PLACEHOLDER
#define SGITITERATE(x, i, nt)
#define SPGIST_DEAD
#define SpGistPageIsLeaf(page)
#define SPGIST_METAPAGE_BLKNO
#define SpGistPageIsDeleted(page)
#define SGLT_SET_NEXTOFFSET(spgLeafTuple, offsetNumber)
#define SpGistBlockIsRoot(blkno)
struct SpGistLeafTupleData * SpGistLeafTuple
#define STORE_STATE(s, d)
#define SpGistPageGetOpaque(page)
#define SPGIST_LAST_FIXED_BLKNO
void initSpGistState(SpGistState *state, Relation index)
Definition: spgutils.c:343
void SpGistUpdateMetaPage(Relation index)
Definition: spgutils.c:445
void SpGistSetLastUsedPage(Relation index, Buffer buffer)
Definition: spgutils.c:668
struct spgBulkDeleteState spgBulkDeleteState
static void vacuumRedirectAndPlaceholder(Relation index, Relation heaprel, Buffer buffer)
Definition: spgvacuum.c:493
IndexBulkDeleteResult * spgbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats, IndexBulkDeleteCallback callback, void *callback_state)
Definition: spgvacuum.c:916
static bool dummy_callback(ItemPointer itemptr, void *state)
Definition: spgvacuum.c:936
struct spgVacPendingItem spgVacPendingItem
static void vacuumLeafPage(spgBulkDeleteState *bds, Relation index, Buffer buffer, bool forPending)
Definition: spgvacuum.c:125
IndexBulkDeleteResult * spgvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
Definition: spgvacuum.c:947
static void spgvacuumscan(spgBulkDeleteState *bds)
Definition: spgvacuum.c:804
static void spgprocesspending(spgBulkDeleteState *bds)
Definition: spgvacuum.c:692
static void vacuumLeafRoot(spgBulkDeleteState *bds, Relation index, Buffer buffer)
Definition: spgvacuum.c:408
static void spgClearPendingList(spgBulkDeleteState *bds)
Definition: spgvacuum.c:89
static void spgvacuumpage(spgBulkDeleteState *bds, BlockNumber blkno)
Definition: spgvacuum.c:621
static void spgAddPendingTID(spgBulkDeleteState *bds, ItemPointer tid)
Definition: spgvacuum.c:63
#define XLOG_SPGIST_VACUUM_ROOT
Definition: spgxlog.h:28
#define SizeOfSpgxlogVacuumLeaf
Definition: spgxlog.h:223
#define SizeOfSpgxlogVacuumRedirect
Definition: spgxlog.h:250
#define XLOG_SPGIST_VACUUM_LEAF
Definition: spgxlog.h:27
#define XLOG_SPGIST_VACUUM_REDIRECT
Definition: spgxlog.h:29
#define SizeOfSpgxlogVacuumRoot
Definition: spgxlog.h:236
void RelationTruncate(Relation rel, BlockNumber nblocks)
Definition: storage.c:288
bool estimated_count
Definition: genam.h:80
BlockNumber pages_deleted
Definition: genam.h:84
BlockNumber pages_newly_deleted
Definition: genam.h:83
BlockNumber pages_free
Definition: genam.h:85
BlockNumber num_pages
Definition: genam.h:79
double tuples_removed
Definition: genam.h:82
double num_index_tuples
Definition: genam.h:81
ItemPointerData t_tid
Definition: itup.h:37
Relation index
Definition: genam.h:48
double num_heap_tuples
Definition: genam.h:54
bool analyze_only
Definition: genam.h:50
BufferAccessStrategy strategy
Definition: genam.h:55
Relation heaprel
Definition: genam.h:49
bool estimated_count
Definition: genam.h:52
TransactionId xmin
Definition: snapshot.h:153
unsigned int tupstate
ItemPointerData pointer
unsigned int tupstate
ItemPointerData heapPtr
Definition: type.h:96
IndexBulkDeleteResult * stats
Definition: spgvacuum.c:44
IndexBulkDeleteCallback callback
Definition: spgvacuum.c:45
spgVacPendingItem * pendingList
Definition: spgvacuum.c:50
SpGistState spgstate
Definition: spgvacuum.c:49
void * callback_state
Definition: spgvacuum.c:46
TransactionId myXmin
Definition: spgvacuum.c:51
BlockNumber lastFilledBlock
Definition: spgvacuum.c:52
IndexVacuumInfo * info
Definition: spgvacuum.c:43
ItemPointerData tid
Definition: spgvacuum.c:34
struct spgVacPendingItem * next
Definition: spgvacuum.c:36
spgxlogState stateSrc
Definition: spgxlog.h:208
uint16 nPlaceholder
Definition: spgxlog.h:204
OffsetNumber firstPlaceholder
Definition: spgxlog.h:241
TransactionId snapshotConflictHorizon
Definition: spgxlog.h:242
spgxlogState stateSrc
Definition: spgxlog.h:230
uint16 nDelete
Definition: spgxlog.h:228
Definition: regguts.h:323
static void callback(struct sockaddr *addr, struct sockaddr *mask, void *unused)
Definition: test_ifaddrs.c:46
bool TransactionIdPrecedes(TransactionId id1, TransactionId id2)
Definition: transam.c:280
bool TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2)
Definition: transam.c:329
#define InvalidTransactionId
Definition: transam.h:31
#define TransactionIdIsValid(xid)
Definition: transam.h:41
void vacuum_delay_point(void)
Definition: vacuum.c:2362
uint64 XLogRecPtr
Definition: xlogdefs.h:21
XLogRecPtr XLogInsert(RmgrId rmid, uint8 info)
Definition: xloginsert.c:474
void XLogRegisterData(const char *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:34