Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * ginvacuum.c
4 : * delete & vacuum routines for the postgres GIN
5 : *
6 : *
7 : * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
8 : * Portions Copyright (c) 1994, Regents of the University of California
9 : *
10 : * IDENTIFICATION
11 : * src/backend/access/gin/ginvacuum.c
12 : *-------------------------------------------------------------------------
13 : */
14 :
15 : #include "postgres.h"
16 :
17 : #include "access/gin_private.h"
18 : #include "access/ginxlog.h"
19 : #include "access/xloginsert.h"
20 : #include "commands/vacuum.h"
21 : #include "miscadmin.h"
22 : #include "postmaster/autovacuum.h"
23 : #include "storage/indexfsm.h"
24 : #include "storage/lmgr.h"
25 : #include "storage/predicate.h"
26 : #include "utils/memutils.h"
27 :
28 : struct GinVacuumState
29 : {
30 : Relation index;
31 : IndexBulkDeleteResult *result;
32 : IndexBulkDeleteCallback callback;
33 : void *callback_state;
34 : GinState ginstate;
35 : BufferAccessStrategy strategy;
36 : MemoryContext tmpCxt;
37 : };
38 :
39 : /*
40 : * Vacuums an uncompressed posting list. The size of the must can be specified
41 : * in number of items (nitems).
42 : *
43 : * If none of the items need to be removed, returns NULL. Otherwise returns
44 : * a new palloc'd array with the remaining items. The number of remaining
45 : * items is returned in *nremaining.
46 : */
47 : ItemPointer
48 240984 : ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
49 : int nitem, int *nremaining)
50 : {
51 : int i,
52 240984 : remaining = 0;
53 240984 : ItemPointer tmpitems = NULL;
54 :
55 : /*
56 : * Iterate over TIDs array
57 : */
58 996804 : for (i = 0; i < nitem; i++)
59 : {
60 755820 : if (gvs->callback(items + i, gvs->callback_state))
61 : {
62 737850 : gvs->result->tuples_removed += 1;
63 737850 : if (!tmpitems)
64 : {
65 : /*
66 : * First TID to be deleted: allocate memory to hold the
67 : * remaining items.
68 : */
69 240900 : tmpitems = palloc(sizeof(ItemPointerData) * nitem);
70 240900 : memcpy(tmpitems, items, sizeof(ItemPointerData) * i);
71 : }
72 : }
73 : else
74 : {
75 17970 : gvs->result->num_index_tuples += 1;
76 17970 : if (tmpitems)
77 1416 : tmpitems[remaining] = items[i];
78 17970 : remaining++;
79 : }
80 : }
81 :
82 240984 : *nremaining = remaining;
83 240984 : return tmpitems;
84 : }
85 :
86 : /*
87 : * Create a WAL record for vacuuming entry tree leaf page.
88 : */
89 : static void
90 1722 : xlogVacuumPage(Relation index, Buffer buffer)
91 : {
92 1722 : Page page = BufferGetPage(buffer);
93 : XLogRecPtr recptr;
94 :
95 : /* This is only used for entry tree leaf pages. */
96 : Assert(!GinPageIsData(page));
97 : Assert(GinPageIsLeaf(page));
98 :
99 1722 : if (!RelationNeedsWAL(index))
100 1722 : return;
101 :
102 : /*
103 : * Always create a full image, we don't track the changes on the page at
104 : * any more fine-grained level. This could obviously be improved...
105 : */
106 0 : XLogBeginInsert();
107 0 : XLogRegisterBuffer(0, buffer, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);
108 :
109 0 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_VACUUM_PAGE);
110 0 : PageSetLSN(page, recptr);
111 : }
112 :
113 :
114 : typedef struct DataPageDeleteStack
115 : {
116 : struct DataPageDeleteStack *child;
117 : struct DataPageDeleteStack *parent;
118 :
119 : BlockNumber blkno; /* current block number */
120 : Buffer leftBuffer; /* pinned and locked rightest non-deleted page
121 : * on left */
122 : bool isRoot;
123 : } DataPageDeleteStack;
124 :
125 :
126 : /*
127 : * Delete a posting tree page.
128 : */
129 : static void
130 12 : ginDeletePage(GinVacuumState *gvs, BlockNumber deleteBlkno, BlockNumber leftBlkno,
131 : BlockNumber parentBlkno, OffsetNumber myoff, bool isParentRoot)
132 : {
133 : Buffer dBuffer;
134 : Buffer lBuffer;
135 : Buffer pBuffer;
136 : Page page,
137 : parentPage;
138 : BlockNumber rightlink;
139 :
140 : /*
141 : * This function MUST be called only if someone of parent pages hold
142 : * exclusive cleanup lock. This guarantees that no insertions currently
143 : * happen in this subtree. Caller also acquires Exclusive locks on
144 : * deletable, parent and left pages.
145 : */
146 12 : lBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, leftBlkno,
147 : RBM_NORMAL, gvs->strategy);
148 12 : dBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, deleteBlkno,
149 : RBM_NORMAL, gvs->strategy);
150 12 : pBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, parentBlkno,
151 : RBM_NORMAL, gvs->strategy);
152 :
153 12 : page = BufferGetPage(dBuffer);
154 12 : rightlink = GinPageGetOpaque(page)->rightlink;
155 :
156 : /*
157 : * Any insert which would have gone on the leaf block will now go to its
158 : * right sibling.
159 : */
160 12 : PredicateLockPageCombine(gvs->index, deleteBlkno, rightlink);
161 :
162 12 : START_CRIT_SECTION();
163 :
164 : /* Unlink the page by changing left sibling's rightlink */
165 12 : page = BufferGetPage(lBuffer);
166 12 : GinPageGetOpaque(page)->rightlink = rightlink;
167 :
168 : /* Delete downlink from parent */
169 12 : parentPage = BufferGetPage(pBuffer);
170 : #ifdef USE_ASSERT_CHECKING
171 : do
172 : {
173 : PostingItem *tod = GinDataPageGetPostingItem(parentPage, myoff);
174 :
175 : Assert(PostingItemGetBlockNumber(tod) == deleteBlkno);
176 : } while (0);
177 : #endif
178 12 : GinPageDeletePostingItem(parentPage, myoff);
179 :
180 12 : page = BufferGetPage(dBuffer);
181 :
182 : /*
183 : * we shouldn't change rightlink field to save workability of running
184 : * search scan
185 : */
186 :
187 : /*
188 : * Mark page as deleted, and remember last xid which could know its
189 : * address.
190 : */
191 12 : GinPageSetDeleted(page);
192 12 : GinPageSetDeleteXid(page, ReadNextTransactionId());
193 :
194 12 : MarkBufferDirty(pBuffer);
195 12 : MarkBufferDirty(lBuffer);
196 12 : MarkBufferDirty(dBuffer);
197 :
198 12 : if (RelationNeedsWAL(gvs->index))
199 : {
200 : XLogRecPtr recptr;
201 : ginxlogDeletePage data;
202 :
203 : /*
204 : * We can't pass REGBUF_STANDARD for the deleted page, because we
205 : * didn't set pd_lower on pre-9.4 versions. The page might've been
206 : * binary-upgraded from an older version, and hence not have pd_lower
207 : * set correctly. Ditto for the left page, but removing the item from
208 : * the parent updated its pd_lower, so we know that's OK at this
209 : * point.
210 : */
211 0 : XLogBeginInsert();
212 0 : XLogRegisterBuffer(0, dBuffer, 0);
213 0 : XLogRegisterBuffer(1, pBuffer, REGBUF_STANDARD);
214 0 : XLogRegisterBuffer(2, lBuffer, 0);
215 :
216 0 : data.parentOffset = myoff;
217 0 : data.rightLink = GinPageGetOpaque(page)->rightlink;
218 0 : data.deleteXid = GinPageGetDeleteXid(page);
219 :
220 0 : XLogRegisterData((char *) &data, sizeof(ginxlogDeletePage));
221 :
222 0 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_DELETE_PAGE);
223 0 : PageSetLSN(page, recptr);
224 0 : PageSetLSN(parentPage, recptr);
225 0 : PageSetLSN(BufferGetPage(lBuffer), recptr);
226 : }
227 :
228 12 : ReleaseBuffer(pBuffer);
229 12 : ReleaseBuffer(lBuffer);
230 12 : ReleaseBuffer(dBuffer);
231 :
232 12 : END_CRIT_SECTION();
233 :
234 12 : gvs->result->pages_newly_deleted++;
235 12 : gvs->result->pages_deleted++;
236 12 : }
237 :
238 :
239 : /*
240 : * Scans posting tree and deletes empty pages. Caller must lock root page for
241 : * cleanup. During scan path from root to current page is kept exclusively
242 : * locked. Also keep left page exclusively locked, because ginDeletePage()
243 : * needs it. If we try to relock left page later, it could deadlock with
244 : * ginStepRight().
245 : */
246 : static bool
247 48 : ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
248 : DataPageDeleteStack *parent, OffsetNumber myoff)
249 : {
250 : DataPageDeleteStack *me;
251 : Buffer buffer;
252 : Page page;
253 48 : bool meDelete = false;
254 : bool isempty;
255 :
256 48 : if (isRoot)
257 : {
258 12 : me = parent;
259 : }
260 : else
261 : {
262 36 : if (!parent->child)
263 : {
264 12 : me = (DataPageDeleteStack *) palloc0(sizeof(DataPageDeleteStack));
265 12 : me->parent = parent;
266 12 : parent->child = me;
267 12 : me->leftBuffer = InvalidBuffer;
268 : }
269 : else
270 24 : me = parent->child;
271 : }
272 :
273 48 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
274 : RBM_NORMAL, gvs->strategy);
275 :
276 48 : if (!isRoot)
277 36 : LockBuffer(buffer, GIN_EXCLUSIVE);
278 :
279 48 : page = BufferGetPage(buffer);
280 :
281 : Assert(GinPageIsData(page));
282 :
283 48 : if (!GinPageIsLeaf(page))
284 : {
285 : OffsetNumber i;
286 :
287 12 : me->blkno = blkno;
288 48 : for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
289 : {
290 36 : PostingItem *pitem = GinDataPageGetPostingItem(page, i);
291 :
292 36 : if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), false, me, i))
293 12 : i--;
294 : }
295 :
296 12 : if (GinPageRightMost(page) && BufferIsValid(me->child->leftBuffer))
297 : {
298 12 : UnlockReleaseBuffer(me->child->leftBuffer);
299 12 : me->child->leftBuffer = InvalidBuffer;
300 : }
301 : }
302 :
303 48 : if (GinPageIsLeaf(page))
304 36 : isempty = GinDataLeafPageIsEmpty(page);
305 : else
306 12 : isempty = GinPageGetOpaque(page)->maxoff < FirstOffsetNumber;
307 :
308 48 : if (isempty)
309 : {
310 : /* we never delete the left- or rightmost branch */
311 30 : if (BufferIsValid(me->leftBuffer) && !GinPageRightMost(page))
312 : {
313 : Assert(!isRoot);
314 12 : ginDeletePage(gvs, blkno, BufferGetBlockNumber(me->leftBuffer),
315 12 : me->parent->blkno, myoff, me->parent->isRoot);
316 12 : meDelete = true;
317 : }
318 : }
319 :
320 48 : if (!meDelete)
321 : {
322 36 : if (BufferIsValid(me->leftBuffer))
323 12 : UnlockReleaseBuffer(me->leftBuffer);
324 36 : me->leftBuffer = buffer;
325 : }
326 : else
327 : {
328 12 : if (!isRoot)
329 12 : LockBuffer(buffer, GIN_UNLOCK);
330 :
331 12 : ReleaseBuffer(buffer);
332 : }
333 :
334 48 : if (isRoot)
335 12 : ReleaseBuffer(buffer);
336 :
337 48 : return meDelete;
338 : }
339 :
340 :
341 : /*
342 : * Scan through posting tree leafs, delete empty tuples. Returns true if there
343 : * is at least one empty page.
344 : */
345 : static bool
346 24 : ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
347 : {
348 : Buffer buffer;
349 : Page page;
350 24 : bool hasVoidPage = false;
351 : MemoryContext oldCxt;
352 :
353 : /* Find leftmost leaf page of posting tree and lock it in exclusive mode */
354 : while (true)
355 12 : {
356 : PostingItem *pitem;
357 :
358 36 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
359 : RBM_NORMAL, gvs->strategy);
360 36 : LockBuffer(buffer, GIN_SHARE);
361 36 : page = BufferGetPage(buffer);
362 :
363 : Assert(GinPageIsData(page));
364 :
365 36 : if (GinPageIsLeaf(page))
366 : {
367 24 : LockBuffer(buffer, GIN_UNLOCK);
368 24 : LockBuffer(buffer, GIN_EXCLUSIVE);
369 24 : break;
370 : }
371 :
372 : Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
373 :
374 12 : pitem = GinDataPageGetPostingItem(page, FirstOffsetNumber);
375 12 : blkno = PostingItemGetBlockNumber(pitem);
376 : Assert(blkno != InvalidBlockNumber);
377 :
378 12 : UnlockReleaseBuffer(buffer);
379 : }
380 :
381 : /* Iterate all posting tree leaves using rightlinks and vacuum them */
382 : while (true)
383 : {
384 48 : oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
385 48 : ginVacuumPostingTreeLeaf(gvs->index, buffer, gvs);
386 48 : MemoryContextSwitchTo(oldCxt);
387 48 : MemoryContextReset(gvs->tmpCxt);
388 :
389 48 : if (GinDataLeafPageIsEmpty(page))
390 30 : hasVoidPage = true;
391 :
392 48 : blkno = GinPageGetOpaque(page)->rightlink;
393 :
394 48 : UnlockReleaseBuffer(buffer);
395 :
396 48 : if (blkno == InvalidBlockNumber)
397 24 : break;
398 :
399 24 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
400 : RBM_NORMAL, gvs->strategy);
401 24 : LockBuffer(buffer, GIN_EXCLUSIVE);
402 24 : page = BufferGetPage(buffer);
403 : }
404 :
405 24 : return hasVoidPage;
406 : }
407 :
408 : static void
409 24 : ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
410 : {
411 24 : if (ginVacuumPostingTreeLeaves(gvs, rootBlkno))
412 : {
413 : /*
414 : * There is at least one empty page. So we have to rescan the tree
415 : * deleting empty pages.
416 : */
417 : Buffer buffer;
418 : DataPageDeleteStack root,
419 : *ptr,
420 : *tmp;
421 :
422 12 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, rootBlkno,
423 : RBM_NORMAL, gvs->strategy);
424 :
425 : /*
426 : * Lock posting tree root for cleanup to ensure there are no
427 : * concurrent inserts.
428 : */
429 12 : LockBufferForCleanup(buffer);
430 :
431 12 : memset(&root, 0, sizeof(DataPageDeleteStack));
432 12 : root.leftBuffer = InvalidBuffer;
433 12 : root.isRoot = true;
434 :
435 12 : ginScanToDelete(gvs, rootBlkno, true, &root, InvalidOffsetNumber);
436 :
437 12 : ptr = root.child;
438 :
439 24 : while (ptr)
440 : {
441 12 : tmp = ptr->child;
442 12 : pfree(ptr);
443 12 : ptr = tmp;
444 : }
445 :
446 12 : UnlockReleaseBuffer(buffer);
447 : }
448 24 : }
449 :
450 : /*
451 : * returns modified page or NULL if page isn't modified.
452 : * Function works with original page until first change is occurred,
453 : * then page is copied into temporary one.
454 : */
455 : static Page
456 1726 : ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
457 : {
458 1726 : Page origpage = BufferGetPage(buffer),
459 : tmppage;
460 : OffsetNumber i,
461 1726 : maxoff = PageGetMaxOffsetNumber(origpage);
462 :
463 1726 : tmppage = origpage;
464 :
465 1726 : *nroot = 0;
466 :
467 241774 : for (i = FirstOffsetNumber; i <= maxoff; i++)
468 : {
469 240048 : IndexTuple itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
470 :
471 240048 : if (GinIsPostingTree(itup))
472 : {
473 : /*
474 : * store posting tree's roots for further processing, we can't
475 : * vacuum it just now due to risk of deadlocks with scans/inserts
476 : */
477 24 : roots[*nroot] = GinGetDownlink(itup);
478 24 : (*nroot)++;
479 : }
480 240024 : else if (GinGetNPosting(itup) > 0)
481 : {
482 : int nitems;
483 : ItemPointer items_orig;
484 : bool free_items_orig;
485 : ItemPointer items;
486 :
487 : /* Get list of item pointers from the tuple. */
488 240024 : if (GinItupIsCompressed(itup))
489 : {
490 240024 : items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(itup), &nitems);
491 240024 : free_items_orig = true;
492 : }
493 : else
494 : {
495 0 : items_orig = (ItemPointer) GinGetPosting(itup);
496 0 : nitems = GinGetNPosting(itup);
497 0 : free_items_orig = false;
498 : }
499 :
500 : /* Remove any items from the list that need to be vacuumed. */
501 240024 : items = ginVacuumItemPointers(gvs, items_orig, nitems, &nitems);
502 :
503 240024 : if (free_items_orig)
504 240024 : pfree(items_orig);
505 :
506 : /* If any item pointers were removed, recreate the tuple. */
507 240024 : if (items)
508 : {
509 : OffsetNumber attnum;
510 : Datum key;
511 : GinNullCategory category;
512 : GinPostingList *plist;
513 : int plistsize;
514 :
515 240000 : if (nitems > 0)
516 : {
517 18 : plist = ginCompressPostingList(items, nitems, GinMaxItemSize, NULL);
518 18 : plistsize = SizeOfGinPostingList(plist);
519 : }
520 : else
521 : {
522 239982 : plist = NULL;
523 239982 : plistsize = 0;
524 : }
525 :
526 : /*
527 : * if we already created a temporary page, make changes in
528 : * place
529 : */
530 240000 : if (tmppage == origpage)
531 : {
532 : /*
533 : * On first difference, create a temporary copy of the
534 : * page and copy the tuple's posting list to it.
535 : */
536 1722 : tmppage = PageGetTempPageCopy(origpage);
537 :
538 : /* set itup pointer to new page */
539 1722 : itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
540 : }
541 :
542 240000 : attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
543 240000 : key = gintuple_get_key(&gvs->ginstate, itup, &category);
544 240000 : itup = GinFormTuple(&gvs->ginstate, attnum, key, category,
545 : (char *) plist, plistsize,
546 : nitems, true);
547 240000 : if (plist)
548 18 : pfree(plist);
549 240000 : PageIndexTupleDelete(tmppage, i);
550 :
551 240000 : if (PageAddItem(tmppage, (Item) itup, IndexTupleSize(itup), i, false, false) != i)
552 0 : elog(ERROR, "failed to add item to index page in \"%s\"",
553 : RelationGetRelationName(gvs->index));
554 :
555 240000 : pfree(itup);
556 240000 : pfree(items);
557 : }
558 : }
559 : }
560 :
561 1726 : return (tmppage == origpage) ? NULL : tmppage;
562 : }
563 :
564 : IndexBulkDeleteResult *
565 10 : ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
566 : IndexBulkDeleteCallback callback, void *callback_state)
567 : {
568 10 : Relation index = info->index;
569 10 : BlockNumber blkno = GIN_ROOT_BLKNO;
570 : GinVacuumState gvs;
571 : Buffer buffer;
572 : BlockNumber rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))];
573 : uint32 nRoot;
574 :
575 10 : gvs.tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
576 : "Gin vacuum temporary context",
577 : ALLOCSET_DEFAULT_SIZES);
578 10 : gvs.index = index;
579 10 : gvs.callback = callback;
580 10 : gvs.callback_state = callback_state;
581 10 : gvs.strategy = info->strategy;
582 10 : initGinState(&gvs.ginstate, index);
583 :
584 : /* first time through? */
585 10 : if (stats == NULL)
586 : {
587 : /* Yes, so initialize stats to zeroes */
588 10 : stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
589 :
590 : /*
591 : * and cleanup any pending inserts
592 : */
593 10 : ginInsertCleanup(&gvs.ginstate, !IsAutoVacuumWorkerProcess(),
594 10 : false, true, stats);
595 : }
596 :
597 : /* we'll re-count the tuples each time */
598 10 : stats->num_index_tuples = 0;
599 10 : gvs.result = stats;
600 :
601 10 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
602 : RBM_NORMAL, info->strategy);
603 :
604 : /* find leaf page */
605 : for (;;)
606 6 : {
607 16 : Page page = BufferGetPage(buffer);
608 : IndexTuple itup;
609 :
610 16 : LockBuffer(buffer, GIN_SHARE);
611 :
612 : Assert(!GinPageIsData(page));
613 :
614 16 : if (GinPageIsLeaf(page))
615 : {
616 10 : LockBuffer(buffer, GIN_UNLOCK);
617 10 : LockBuffer(buffer, GIN_EXCLUSIVE);
618 :
619 10 : if (blkno == GIN_ROOT_BLKNO && !GinPageIsLeaf(page))
620 : {
621 0 : LockBuffer(buffer, GIN_UNLOCK);
622 0 : continue; /* check it one more */
623 : }
624 10 : break;
625 : }
626 :
627 : Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
628 :
629 6 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
630 6 : blkno = GinGetDownlink(itup);
631 : Assert(blkno != InvalidBlockNumber);
632 :
633 6 : UnlockReleaseBuffer(buffer);
634 6 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
635 : RBM_NORMAL, info->strategy);
636 : }
637 :
638 : /* right now we found leftmost page in entry's BTree */
639 :
640 : for (;;)
641 1716 : {
642 1726 : Page page = BufferGetPage(buffer);
643 : Page resPage;
644 : uint32 i;
645 :
646 : Assert(!GinPageIsData(page));
647 :
648 1726 : resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
649 :
650 1726 : blkno = GinPageGetOpaque(page)->rightlink;
651 :
652 1726 : if (resPage)
653 : {
654 1722 : START_CRIT_SECTION();
655 1722 : PageRestoreTempPage(resPage, page);
656 1722 : MarkBufferDirty(buffer);
657 1722 : xlogVacuumPage(gvs.index, buffer);
658 1722 : UnlockReleaseBuffer(buffer);
659 1722 : END_CRIT_SECTION();
660 : }
661 : else
662 : {
663 4 : UnlockReleaseBuffer(buffer);
664 : }
665 :
666 1726 : vacuum_delay_point();
667 :
668 1750 : for (i = 0; i < nRoot; i++)
669 : {
670 24 : ginVacuumPostingTree(&gvs, rootOfPostingTree[i]);
671 24 : vacuum_delay_point();
672 : }
673 :
674 1726 : if (blkno == InvalidBlockNumber) /* rightmost page */
675 10 : break;
676 :
677 1716 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
678 : RBM_NORMAL, info->strategy);
679 1716 : LockBuffer(buffer, GIN_EXCLUSIVE);
680 : }
681 :
682 10 : MemoryContextDelete(gvs.tmpCxt);
683 :
684 10 : return gvs.result;
685 : }
686 :
687 : IndexBulkDeleteResult *
688 68 : ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
689 : {
690 68 : Relation index = info->index;
691 : bool needLock;
692 : BlockNumber npages,
693 : blkno;
694 : BlockNumber totFreePages;
695 : GinState ginstate;
696 : GinStatsData idxStat;
697 :
698 : /*
699 : * In an autovacuum analyze, we want to clean up pending insertions.
700 : * Otherwise, an ANALYZE-only call is a no-op.
701 : */
702 68 : if (info->analyze_only)
703 : {
704 14 : if (IsAutoVacuumWorkerProcess())
705 : {
706 8 : initGinState(&ginstate, index);
707 8 : ginInsertCleanup(&ginstate, false, true, true, stats);
708 : }
709 14 : return stats;
710 : }
711 :
712 : /*
713 : * Set up all-zero stats and cleanup pending inserts if ginbulkdelete
714 : * wasn't called
715 : */
716 54 : if (stats == NULL)
717 : {
718 44 : stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
719 44 : initGinState(&ginstate, index);
720 44 : ginInsertCleanup(&ginstate, !IsAutoVacuumWorkerProcess(),
721 44 : false, true, stats);
722 : }
723 :
724 54 : memset(&idxStat, 0, sizeof(idxStat));
725 :
726 : /*
727 : * XXX we always report the heap tuple count as the number of index
728 : * entries. This is bogus if the index is partial, but it's real hard to
729 : * tell how many distinct heap entries are referenced by a GIN index.
730 : */
731 54 : stats->num_index_tuples = Max(info->num_heap_tuples, 0);
732 54 : stats->estimated_count = info->estimated_count;
733 :
734 : /*
735 : * Need lock unless it's local to this backend.
736 : */
737 54 : needLock = !RELATION_IS_LOCAL(index);
738 :
739 54 : if (needLock)
740 48 : LockRelationForExtension(index, ExclusiveLock);
741 54 : npages = RelationGetNumberOfBlocks(index);
742 54 : if (needLock)
743 48 : UnlockRelationForExtension(index, ExclusiveLock);
744 :
745 54 : totFreePages = 0;
746 :
747 9198 : for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
748 : {
749 : Buffer buffer;
750 : Page page;
751 :
752 9144 : vacuum_delay_point();
753 :
754 9144 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
755 : RBM_NORMAL, info->strategy);
756 9144 : LockBuffer(buffer, GIN_SHARE);
757 9144 : page = (Page) BufferGetPage(buffer);
758 :
759 9144 : if (GinPageIsRecyclable(page))
760 : {
761 : Assert(blkno != GIN_ROOT_BLKNO);
762 4644 : RecordFreeIndexPage(index, blkno);
763 4644 : totFreePages++;
764 : }
765 4500 : else if (GinPageIsData(page))
766 : {
767 222 : idxStat.nDataPages++;
768 : }
769 4278 : else if (!GinPageIsList(page))
770 : {
771 4278 : idxStat.nEntryPages++;
772 :
773 4278 : if (GinPageIsLeaf(page))
774 4248 : idxStat.nEntries += PageGetMaxOffsetNumber(page);
775 : }
776 :
777 9144 : UnlockReleaseBuffer(buffer);
778 : }
779 :
780 : /* Update the metapage with accurate page and entry counts */
781 54 : idxStat.nTotalPages = npages;
782 54 : ginUpdateStats(info->index, &idxStat, false);
783 :
784 : /* Finally, vacuum the FSM */
785 54 : IndexFreeSpaceMapVacuum(info->index);
786 :
787 54 : stats->pages_free = totFreePages;
788 :
789 54 : if (needLock)
790 48 : LockRelationForExtension(index, ExclusiveLock);
791 54 : stats->num_pages = RelationGetNumberOfBlocks(index);
792 54 : if (needLock)
793 48 : UnlockRelationForExtension(index, ExclusiveLock);
794 :
795 54 : return stats;
796 : }
797 :
798 : /*
799 : * Return whether Page can safely be recycled.
800 : */
801 : bool
802 9252 : GinPageIsRecyclable(Page page)
803 : {
804 : TransactionId delete_xid;
805 :
806 9252 : if (PageIsNew(page))
807 0 : return true;
808 :
809 9252 : if (!GinPageIsDeleted(page))
810 4488 : return false;
811 :
812 4764 : delete_xid = GinPageGetDeleteXid(page);
813 :
814 4764 : if (!TransactionIdIsValid(delete_xid))
815 4752 : return true;
816 :
817 : /*
818 : * If no backend still could view delete_xid as in running, all scans
819 : * concurrent with ginDeletePage() must have finished.
820 : */
821 12 : return GlobalVisCheckRemovableXid(NULL, delete_xid);
822 : }
|