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