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