TLA 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 CBC 120474 : ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
49 : int nitem, int *nremaining)
50 : {
51 : int i,
52 120474 : remaining = 0;
53 120474 : ItemPointer tmpitems = NULL;
54 :
55 : /*
56 : * Iterate over TIDs array
57 : */
58 512976 : for (i = 0; i < nitem; i++)
59 : {
60 392502 : if (gvs->callback(items + i, gvs->callback_state))
61 : {
62 377433 : gvs->result->tuples_removed += 1;
63 377433 : if (!tmpitems)
64 : {
65 : /*
66 : * First TID to be deleted: allocate memory to hold the
67 : * remaining items.
68 : */
69 120453 : tmpitems = palloc(sizeof(ItemPointerData) * nitem);
70 120453 : memcpy(tmpitems, items, sizeof(ItemPointerData) * i);
71 : }
72 : }
73 : else
74 : {
75 15069 : gvs->result->num_index_tuples += 1;
76 15069 : if (tmpitems)
77 6654 : tmpitems[remaining] = items[i];
78 15069 : remaining++;
79 : }
80 : }
81 :
82 120474 : *nremaining = remaining;
83 120474 : return tmpitems;
84 : }
85 :
86 : /*
87 : * Create a WAL record for vacuuming entry tree leaf page.
88 : */
89 : static void
90 865 : xlogVacuumPage(Relation index, Buffer buffer)
91 : {
92 865 : Page page = BufferGetPage(buffer);
93 : XLogRecPtr recptr;
94 :
95 : /* This is only used for entry tree leaf pages. */
96 865 : Assert(!GinPageIsData(page));
97 865 : Assert(GinPageIsLeaf(page));
98 :
99 865 : if (!RelationNeedsWAL(index))
100 861 : 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 4 : XLogBeginInsert();
107 4 : XLogRegisterBuffer(0, buffer, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);
108 :
109 4 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_VACUUM_PAGE);
110 4 : 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 6 : 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 6 : lBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, leftBlkno,
147 : RBM_NORMAL, gvs->strategy);
148 6 : dBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, deleteBlkno,
149 : RBM_NORMAL, gvs->strategy);
150 6 : pBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, parentBlkno,
151 : RBM_NORMAL, gvs->strategy);
152 :
153 6 : page = BufferGetPage(dBuffer);
154 6 : 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 6 : PredicateLockPageCombine(gvs->index, deleteBlkno, rightlink);
161 :
162 6 : START_CRIT_SECTION();
163 :
164 : /* Unlink the page by changing left sibling's rightlink */
165 6 : page = BufferGetPage(lBuffer);
166 6 : GinPageGetOpaque(page)->rightlink = rightlink;
167 :
168 : /* Delete downlink from parent */
169 6 : parentPage = BufferGetPage(pBuffer);
170 : #ifdef USE_ASSERT_CHECKING
171 : do
172 : {
173 6 : PostingItem *tod = GinDataPageGetPostingItem(parentPage, myoff);
174 :
175 6 : Assert(PostingItemGetBlockNumber(tod) == deleteBlkno);
176 : } while (0);
177 : #endif
178 6 : GinPageDeletePostingItem(parentPage, myoff);
179 :
180 6 : 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 6 : GinPageSetDeleted(page);
192 6 : GinPageSetDeleteXid(page, ReadNextTransactionId());
193 :
194 6 : MarkBufferDirty(pBuffer);
195 6 : MarkBufferDirty(lBuffer);
196 6 : MarkBufferDirty(dBuffer);
197 :
198 6 : 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 UBC 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 CBC 6 : ReleaseBuffer(pBuffer);
229 6 : ReleaseBuffer(lBuffer);
230 6 : ReleaseBuffer(dBuffer);
231 :
232 6 : END_CRIT_SECTION();
233 :
234 6 : gvs->result->pages_newly_deleted++;
235 6 : gvs->result->pages_deleted++;
236 6 : }
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 24 : ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
248 : DataPageDeleteStack *parent, OffsetNumber myoff)
249 : {
250 : DataPageDeleteStack *me;
251 : Buffer buffer;
252 : Page page;
253 24 : bool meDelete = false;
254 : bool isempty;
255 :
256 24 : if (isRoot)
257 : {
258 6 : me = parent;
259 : }
260 : else
261 : {
262 18 : if (!parent->child)
263 : {
264 6 : me = (DataPageDeleteStack *) palloc0(sizeof(DataPageDeleteStack));
265 6 : me->parent = parent;
266 6 : parent->child = me;
267 6 : me->leftBuffer = InvalidBuffer;
268 : }
269 : else
270 12 : me = parent->child;
271 : }
272 :
273 24 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
274 : RBM_NORMAL, gvs->strategy);
275 :
276 24 : if (!isRoot)
277 18 : LockBuffer(buffer, GIN_EXCLUSIVE);
278 :
279 24 : page = BufferGetPage(buffer);
280 :
281 24 : Assert(GinPageIsData(page));
282 :
283 24 : if (!GinPageIsLeaf(page))
284 : {
285 : OffsetNumber i;
286 :
287 6 : me->blkno = blkno;
288 24 : for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
289 : {
290 18 : PostingItem *pitem = GinDataPageGetPostingItem(page, i);
291 :
292 18 : if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), false, me, i))
293 6 : i--;
294 : }
295 :
296 6 : if (GinPageRightMost(page) && BufferIsValid(me->child->leftBuffer))
297 : {
298 6 : UnlockReleaseBuffer(me->child->leftBuffer);
299 6 : me->child->leftBuffer = InvalidBuffer;
300 : }
301 : }
302 :
303 24 : if (GinPageIsLeaf(page))
304 18 : isempty = GinDataLeafPageIsEmpty(page);
305 : else
306 6 : isempty = GinPageGetOpaque(page)->maxoff < FirstOffsetNumber;
307 :
308 24 : if (isempty)
309 : {
310 : /* we never delete the left- or rightmost branch */
311 15 : if (BufferIsValid(me->leftBuffer) && !GinPageRightMost(page))
312 : {
313 6 : Assert(!isRoot);
314 6 : ginDeletePage(gvs, blkno, BufferGetBlockNumber(me->leftBuffer),
315 6 : me->parent->blkno, myoff, me->parent->isRoot);
316 6 : meDelete = true;
317 : }
318 : }
319 :
320 24 : if (!meDelete)
321 : {
322 18 : if (BufferIsValid(me->leftBuffer))
323 6 : UnlockReleaseBuffer(me->leftBuffer);
324 18 : me->leftBuffer = buffer;
325 : }
326 : else
327 : {
328 6 : if (!isRoot)
329 6 : LockBuffer(buffer, GIN_UNLOCK);
330 :
331 6 : ReleaseBuffer(buffer);
332 : }
333 :
334 24 : if (isRoot)
335 6 : ReleaseBuffer(buffer);
336 :
337 24 : 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 9 : ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
347 : {
348 : Buffer buffer;
349 : Page page;
350 9 : 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 6 : {
356 : PostingItem *pitem;
357 :
358 15 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
359 : RBM_NORMAL, gvs->strategy);
360 15 : LockBuffer(buffer, GIN_SHARE);
361 15 : page = BufferGetPage(buffer);
362 :
363 15 : Assert(GinPageIsData(page));
364 :
365 15 : if (GinPageIsLeaf(page))
366 : {
367 9 : LockBuffer(buffer, GIN_UNLOCK);
368 9 : LockBuffer(buffer, GIN_EXCLUSIVE);
369 9 : break;
370 : }
371 :
372 6 : Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
373 :
374 6 : pitem = GinDataPageGetPostingItem(page, FirstOffsetNumber);
375 6 : blkno = PostingItemGetBlockNumber(pitem);
376 6 : Assert(blkno != InvalidBlockNumber);
377 :
378 6 : UnlockReleaseBuffer(buffer);
379 : }
380 :
381 : /* Iterate all posting tree leaves using rightlinks and vacuum them */
382 : while (true)
383 : {
384 21 : oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
385 21 : ginVacuumPostingTreeLeaf(gvs->index, buffer, gvs);
386 21 : MemoryContextSwitchTo(oldCxt);
387 21 : MemoryContextReset(gvs->tmpCxt);
388 :
389 21 : if (GinDataLeafPageIsEmpty(page))
390 15 : hasVoidPage = true;
391 :
392 21 : blkno = GinPageGetOpaque(page)->rightlink;
393 :
394 21 : UnlockReleaseBuffer(buffer);
395 :
396 21 : if (blkno == InvalidBlockNumber)
397 9 : break;
398 :
399 12 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
400 : RBM_NORMAL, gvs->strategy);
401 12 : LockBuffer(buffer, GIN_EXCLUSIVE);
402 12 : page = BufferGetPage(buffer);
403 : }
404 :
405 9 : return hasVoidPage;
406 : }
407 :
408 : static void
409 9 : ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
410 : {
411 9 : 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 6 : 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 6 : LockBufferForCleanup(buffer);
430 :
431 6 : memset(&root, 0, sizeof(DataPageDeleteStack));
432 6 : root.leftBuffer = InvalidBuffer;
433 6 : root.isRoot = true;
434 :
435 6 : ginScanToDelete(gvs, rootBlkno, true, &root, InvalidOffsetNumber);
436 :
437 6 : ptr = root.child;
438 :
439 12 : while (ptr)
440 : {
441 6 : tmp = ptr->child;
442 6 : pfree(ptr);
443 6 : ptr = tmp;
444 : }
445 :
446 6 : UnlockReleaseBuffer(buffer);
447 : }
448 9 : }
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 866 : ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
457 : {
458 866 : Page origpage = BufferGetPage(buffer),
459 : tmppage;
460 : OffsetNumber i,
461 866 : maxoff = PageGetMaxOffsetNumber(origpage);
462 :
463 866 : tmppage = origpage;
464 :
465 866 : *nroot = 0;
466 :
467 120899 : for (i = FirstOffsetNumber; i <= maxoff; i++)
468 : {
469 120033 : IndexTuple itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
470 :
471 120033 : 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 9 : roots[*nroot] = GinGetDownlink(itup);
478 9 : (*nroot)++;
479 : }
480 120024 : 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 120024 : if (GinItupIsCompressed(itup))
489 : {
490 120024 : items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(itup), &nitems);
491 120024 : free_items_orig = true;
492 : }
493 : else
494 : {
495 UBC 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 CBC 120024 : items = ginVacuumItemPointers(gvs, items_orig, nitems, &nitems);
502 :
503 120024 : if (free_items_orig)
504 120024 : pfree(items_orig);
505 :
506 : /* If any item pointers were removed, recreate the tuple. */
507 120024 : if (items)
508 : {
509 : OffsetNumber attnum;
510 : Datum key;
511 : GinNullCategory category;
512 : GinPostingList *plist;
513 : int plistsize;
514 :
515 120012 : if (nitems > 0)
516 : {
517 21 : plist = ginCompressPostingList(items, nitems, GinMaxItemSize, NULL);
518 21 : plistsize = SizeOfGinPostingList(plist);
519 : }
520 : else
521 : {
522 119991 : plist = NULL;
523 119991 : plistsize = 0;
524 : }
525 :
526 : /*
527 : * if we already created a temporary page, make changes in
528 : * place
529 : */
530 120012 : 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 865 : tmppage = PageGetTempPageCopy(origpage);
537 :
538 : /* set itup pointer to new page */
539 865 : itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
540 : }
541 :
542 120012 : attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
543 120012 : key = gintuple_get_key(&gvs->ginstate, itup, &category);
544 120012 : itup = GinFormTuple(&gvs->ginstate, attnum, key, category,
545 : (char *) plist, plistsize,
546 : nitems, true);
547 120012 : if (plist)
548 21 : pfree(plist);
549 120012 : PageIndexTupleDelete(tmppage, i);
550 :
551 120012 : if (PageAddItem(tmppage, (Item) itup, IndexTupleSize(itup), i, false, false) != i)
552 UBC 0 : elog(ERROR, "failed to add item to index page in \"%s\"",
553 : RelationGetRelationName(gvs->index));
554 :
555 CBC 120012 : pfree(itup);
556 120012 : pfree(items);
557 : }
558 : }
559 : }
560 :
561 866 : return (tmppage == origpage) ? NULL : tmppage;
562 : }
563 :
564 : IndexBulkDeleteResult *
565 8 : ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
566 : IndexBulkDeleteCallback callback, void *callback_state)
567 : {
568 8 : Relation index = info->index;
569 8 : BlockNumber blkno = GIN_ROOT_BLKNO;
570 : GinVacuumState gvs;
571 : Buffer buffer;
572 : BlockNumber rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))];
573 : uint32 nRoot;
574 :
575 8 : gvs.tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
576 : "Gin vacuum temporary context",
577 : ALLOCSET_DEFAULT_SIZES);
578 8 : gvs.index = index;
579 8 : gvs.callback = callback;
580 8 : gvs.callback_state = callback_state;
581 8 : gvs.strategy = info->strategy;
582 8 : initGinState(&gvs.ginstate, index);
583 :
584 : /* first time through? */
585 8 : if (stats == NULL)
586 : {
587 : /* Yes, so initialize stats to zeroes */
588 8 : stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
589 :
590 : /*
591 : * and cleanup any pending inserts
592 : */
593 8 : ginInsertCleanup(&gvs.ginstate, !IsAutoVacuumWorkerProcess(),
594 8 : false, true, stats);
595 : }
596 :
597 : /* we'll re-count the tuples each time */
598 8 : stats->num_index_tuples = 0;
599 8 : gvs.result = stats;
600 :
601 8 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
602 : RBM_NORMAL, info->strategy);
603 :
604 : /* find leaf page */
605 : for (;;)
606 3 : {
607 11 : Page page = BufferGetPage(buffer);
608 : IndexTuple itup;
609 :
610 11 : LockBuffer(buffer, GIN_SHARE);
611 :
612 11 : Assert(!GinPageIsData(page));
613 :
614 11 : if (GinPageIsLeaf(page))
615 : {
616 8 : LockBuffer(buffer, GIN_UNLOCK);
617 8 : LockBuffer(buffer, GIN_EXCLUSIVE);
618 :
619 8 : if (blkno == GIN_ROOT_BLKNO && !GinPageIsLeaf(page))
620 : {
621 UBC 0 : LockBuffer(buffer, GIN_UNLOCK);
622 0 : continue; /* check it one more */
623 : }
624 CBC 8 : break;
625 : }
626 :
627 3 : Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
628 :
629 3 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
630 3 : blkno = GinGetDownlink(itup);
631 3 : Assert(blkno != InvalidBlockNumber);
632 :
633 3 : UnlockReleaseBuffer(buffer);
634 3 : 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 858 : {
642 866 : Page page = BufferGetPage(buffer);
643 : Page resPage;
644 : uint32 i;
645 :
646 866 : Assert(!GinPageIsData(page));
647 :
648 866 : resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
649 :
650 866 : blkno = GinPageGetOpaque(page)->rightlink;
651 :
652 866 : if (resPage)
653 : {
654 865 : START_CRIT_SECTION();
655 865 : PageRestoreTempPage(resPage, page);
656 865 : MarkBufferDirty(buffer);
657 865 : xlogVacuumPage(gvs.index, buffer);
658 865 : UnlockReleaseBuffer(buffer);
659 865 : END_CRIT_SECTION();
660 : }
661 : else
662 : {
663 1 : UnlockReleaseBuffer(buffer);
664 : }
665 :
666 866 : vacuum_delay_point();
667 :
668 875 : for (i = 0; i < nRoot; i++)
669 : {
670 9 : ginVacuumPostingTree(&gvs, rootOfPostingTree[i]);
671 9 : vacuum_delay_point();
672 : }
673 :
674 866 : if (blkno == InvalidBlockNumber) /* rightmost page */
675 8 : break;
676 :
677 858 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
678 : RBM_NORMAL, info->strategy);
679 858 : LockBuffer(buffer, GIN_EXCLUSIVE);
680 : }
681 :
682 8 : MemoryContextDelete(gvs.tmpCxt);
683 :
684 8 : return gvs.result;
685 : }
686 :
687 : IndexBulkDeleteResult *
688 34 : ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
689 : {
690 34 : 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 34 : if (info->analyze_only)
703 : {
704 7 : if (IsAutoVacuumWorkerProcess())
705 : {
706 4 : initGinState(&ginstate, index);
707 4 : ginInsertCleanup(&ginstate, false, true, true, stats);
708 : }
709 7 : return stats;
710 : }
711 :
712 : /*
713 : * Set up all-zero stats and cleanup pending inserts if ginbulkdelete
714 : * wasn't called
715 : */
716 27 : if (stats == NULL)
717 : {
718 19 : stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
719 19 : initGinState(&ginstate, index);
720 19 : ginInsertCleanup(&ginstate, !IsAutoVacuumWorkerProcess(),
721 19 : false, true, stats);
722 : }
723 :
724 27 : 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 27 : stats->num_index_tuples = Max(info->num_heap_tuples, 0);
732 27 : stats->estimated_count = info->estimated_count;
733 :
734 : /*
735 : * Need lock unless it's local to this backend.
736 : */
737 27 : needLock = !RELATION_IS_LOCAL(index);
738 :
739 27 : if (needLock)
740 24 : LockRelationForExtension(index, ExclusiveLock);
741 27 : npages = RelationGetNumberOfBlocks(index);
742 27 : if (needLock)
743 24 : UnlockRelationForExtension(index, ExclusiveLock);
744 :
745 27 : totFreePages = 0;
746 :
747 4593 : for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
748 : {
749 : Buffer buffer;
750 : Page page;
751 :
752 4566 : vacuum_delay_point();
753 :
754 4566 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
755 : RBM_NORMAL, info->strategy);
756 4566 : LockBuffer(buffer, GIN_SHARE);
757 4566 : page = (Page) BufferGetPage(buffer);
758 :
759 4566 : if (GinPageIsRecyclable(page))
760 : {
761 2322 : Assert(blkno != GIN_ROOT_BLKNO);
762 2322 : RecordFreeIndexPage(index, blkno);
763 2322 : totFreePages++;
764 : }
765 2244 : else if (GinPageIsData(page))
766 : {
767 105 : idxStat.nDataPages++;
768 : }
769 2139 : else if (!GinPageIsList(page))
770 : {
771 2139 : idxStat.nEntryPages++;
772 :
773 2139 : if (GinPageIsLeaf(page))
774 2124 : idxStat.nEntries += PageGetMaxOffsetNumber(page);
775 : }
776 :
777 4566 : UnlockReleaseBuffer(buffer);
778 : }
779 :
780 : /* Update the metapage with accurate page and entry counts */
781 27 : idxStat.nTotalPages = npages;
782 27 : ginUpdateStats(info->index, &idxStat, false);
783 :
784 : /* Finally, vacuum the FSM */
785 27 : IndexFreeSpaceMapVacuum(info->index);
786 :
787 27 : stats->pages_free = totFreePages;
788 :
789 27 : if (needLock)
790 24 : LockRelationForExtension(index, ExclusiveLock);
791 27 : stats->num_pages = RelationGetNumberOfBlocks(index);
792 27 : if (needLock)
793 24 : UnlockRelationForExtension(index, ExclusiveLock);
794 :
795 27 : return stats;
796 : }
797 :
798 : /*
799 : * Return whether Page can safely be recycled.
800 : */
801 : bool
802 4618 : GinPageIsRecyclable(Page page)
803 : {
804 : TransactionId delete_xid;
805 :
806 4618 : if (PageIsNew(page))
807 UBC 0 : return true;
808 :
809 CBC 4618 : if (!GinPageIsDeleted(page))
810 2238 : return false;
811 :
812 2380 : delete_xid = GinPageGetDeleteXid(page);
813 :
814 2380 : if (!TransactionIdIsValid(delete_xid))
815 2374 : 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 6 : return GlobalVisCheckRemovableXid(NULL, delete_xid);
822 : }
|