Age Owner 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
3364 heikki.linnakangas 48 CBC 120474 : ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
49 : int nitem, int *nremaining)
50 : {
51 : int i,
52 120474 : remaining = 0;
3260 bruce 53 120474 : ItemPointer tmpitems = NULL;
54 :
55 : /*
56 : * Iterate over TIDs array
57 : */
6031 58 512976 : for (i = 0; i < nitem; i++)
59 : {
60 392502 : if (gvs->callback(items + i, gvs->callback_state))
61 : {
6186 teodor 62 377433 : gvs->result->tuples_removed += 1;
3364 heikki.linnakangas 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 : {
6186 teodor 75 15069 : gvs->result->num_index_tuples += 1;
3364 heikki.linnakangas 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
6031 bruce 90 865 : xlogVacuumPage(Relation index, Buffer buffer)
91 : {
2545 kgrittn 92 865 : Page page = BufferGetPage(buffer);
93 : XLogRecPtr recptr;
94 :
95 : /* This is only used for entry tree leaf pages. */
3364 heikki.linnakangas 96 865 : Assert(!GinPageIsData(page));
6031 bruce 97 865 : Assert(GinPageIsLeaf(page));
98 :
4500 rhaas 99 865 : if (!RelationNeedsWAL(index))
6031 bruce 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 : */
3062 heikki.linnakangas 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);
6186 teodor 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
6031 bruce 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 : */
3439 heikki.linnakangas 146 6 : lBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, leftBlkno,
147 : RBM_NORMAL, gvs->strategy);
5273 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 :
1836 teodor 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 :
6186 162 6 : START_CRIT_SECTION();
163 :
164 : /* Unlink the page by changing left sibling's rightlink */
2545 kgrittn 165 6 : page = BufferGetPage(lBuffer);
3439 heikki.linnakangas 166 6 : GinPageGetOpaque(page)->rightlink = rightlink;
167 :
168 : /* Delete downlink from parent */
2545 kgrittn 169 6 : parentPage = BufferGetPage(pBuffer);
170 : #ifdef USE_ASSERT_CHECKING
171 : do
172 : {
3475 heikki.linnakangas 173 6 : PostingItem *tod = GinDataPageGetPostingItem(parentPage, myoff);
174 :
5624 bruce 175 6 : Assert(PostingItemGetBlockNumber(tod) == deleteBlkno);
176 : } while (0);
177 : #endif
4557 tgl 178 6 : GinPageDeletePostingItem(parentPage, myoff);
179 :
2545 kgrittn 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 : */
1237 akorotkov 191 6 : GinPageSetDeleted(page);
783 tmunro 192 6 : GinPageSetDeleteXid(page, ReadNextTransactionId());
193 :
5787 teodor 194 6 : MarkBufferDirty(pBuffer);
3282 heikki.linnakangas 195 6 : MarkBufferDirty(lBuffer);
5787 teodor 196 6 : MarkBufferDirty(dBuffer);
197 :
4500 rhaas 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 : */
3062 heikki.linnakangas 211 UBC 0 : XLogBeginInsert();
212 0 : XLogRegisterBuffer(0, dBuffer, 0);
213 0 : XLogRegisterBuffer(1, pBuffer, REGBUF_STANDARD);
214 0 : XLogRegisterBuffer(2, lBuffer, 0);
215 :
6186 teodor 216 0 : data.parentOffset = myoff;
6031 bruce 217 0 : data.rightLink = GinPageGetOpaque(page)->rightlink;
1578 akorotkov 218 0 : data.deleteXid = GinPageGetDeleteXid(page);
219 :
3062 heikki.linnakangas 220 0 : XLogRegisterData((char *) &data, sizeof(ginxlogDeletePage));
221 :
222 0 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_DELETE_PAGE);
6186 teodor 223 0 : PageSetLSN(page, recptr);
224 0 : PageSetLSN(parentPage, recptr);
2545 kgrittn 225 0 : PageSetLSN(BufferGetPage(lBuffer), recptr);
226 : }
227 :
6031 bruce 228 CBC 6 : ReleaseBuffer(pBuffer);
1237 akorotkov 229 6 : ReleaseBuffer(lBuffer);
2208 teodor 230 6 : ReleaseBuffer(dBuffer);
231 :
6186 232 6 : END_CRIT_SECTION();
233 :
773 pg 234 6 : gvs->result->pages_newly_deleted++;
6186 teodor 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
3441 heikki.linnakangas 247 24 : ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
248 : DataPageDeleteStack *parent, OffsetNumber myoff)
249 : {
250 : DataPageDeleteStack *me;
251 : Buffer buffer;
252 : Page page;
2062 peter_e 253 24 : bool meDelete = false;
254 : bool isempty;
255 :
6031 bruce 256 24 : if (isRoot)
257 : {
6186 teodor 258 6 : me = parent;
259 : }
260 : else
261 : {
6031 bruce 262 18 : if (!parent->child)
263 : {
264 6 : me = (DataPageDeleteStack *) palloc0(sizeof(DataPageDeleteStack));
265 6 : me->parent = parent;
6186 teodor 266 6 : parent->child = me;
1237 akorotkov 267 6 : me->leftBuffer = InvalidBuffer;
268 : }
269 : else
6186 teodor 270 12 : me = parent->child;
271 : }
272 :
5273 heikki.linnakangas 273 24 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
274 : RBM_NORMAL, gvs->strategy);
275 :
2153 bruce 276 24 : if (!isRoot)
2208 teodor 277 18 : LockBuffer(buffer, GIN_EXCLUSIVE);
278 :
2545 kgrittn 279 24 : page = BufferGetPage(buffer);
280 :
6031 bruce 281 24 : Assert(GinPageIsData(page));
282 :
283 24 : if (!GinPageIsLeaf(page))
284 : {
285 : OffsetNumber i;
286 :
5974 teodor 287 6 : me->blkno = blkno;
6031 bruce 288 24 : for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
289 : {
3475 heikki.linnakangas 290 18 : PostingItem *pitem = GinDataPageGetPostingItem(page, i);
291 :
2062 peter_e 292 18 : if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), false, me, i))
6186 teodor 293 6 : i--;
294 : }
295 :
1237 akorotkov 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 :
3364 heikki.linnakangas 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 */
1237 akorotkov 311 15 : if (BufferIsValid(me->leftBuffer) && !GinPageRightMost(page))
312 : {
6031 bruce 313 6 : Assert(!isRoot);
1237 akorotkov 314 6 : ginDeletePage(gvs, blkno, BufferGetBlockNumber(me->leftBuffer),
315 6 : me->parent->blkno, myoff, me->parent->isRoot);
2062 peter_e 316 6 : meDelete = true;
317 : }
318 : }
319 :
1237 akorotkov 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 :
6186 teodor 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
1578 akorotkov 346 9 : ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
347 : {
348 : Buffer buffer;
349 : Page page;
2062 peter_e 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)
1578 akorotkov 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 : {
2208 teodor 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))
2062 peter_e 390 15 : hasVoidPage = true;
391 :
1578 akorotkov 392 21 : blkno = GinPageGetOpaque(page)->rightlink;
393 :
2208 teodor 394 21 : UnlockReleaseBuffer(buffer);
395 :
1578 akorotkov 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));
1237 432 6 : root.leftBuffer = InvalidBuffer;
1578 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 : }
2208 teodor 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
6031 bruce 456 866 : ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
457 : {
2545 kgrittn 458 866 : Page origpage = BufferGetPage(buffer),
459 : tmppage;
460 : OffsetNumber i,
6031 bruce 461 866 : maxoff = PageGetMaxOffsetNumber(origpage);
462 :
6186 teodor 463 866 : tmppage = origpage;
464 :
6031 bruce 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 : */
4475 tgl 477 9 : roots[*nroot] = GinGetDownlink(itup);
6186 teodor 478 9 : (*nroot)++;
479 : }
6031 bruce 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. */
3364 heikki.linnakangas 488 120024 : if (GinItupIsCompressed(itup))
489 : {
2950 490 120024 : items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(itup), &nitems);
491 120024 : free_items_orig = true;
492 : }
493 : else
494 : {
2950 heikki.linnakangas 495 UBC 0 : items_orig = (ItemPointer) GinGetPosting(itup);
3364 496 0 : nitems = GinGetNPosting(itup);
2950 497 0 : free_items_orig = false;
498 : }
499 :
500 : /* Remove any items from the list that need to be vacuumed. */
2950 heikki.linnakangas 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 :
3364 515 120012 : if (nitems > 0)
516 : {
2950 517 21 : plist = ginCompressPostingList(items, nitems, GinMaxItemSize, NULL);
3364 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 : */
6031 bruce 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 : */
5270 tgl 536 865 : tmppage = PageGetTempPageCopy(origpage);
537 :
538 : /* set itup pointer to new page */
6186 teodor 539 865 : itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
540 : }
541 :
5385 tgl 542 120012 : attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
4475 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);
3364 heikki.linnakangas 547 120012 : if (plist)
548 21 : pfree(plist);
6186 teodor 549 120012 : PageIndexTupleDelete(tmppage, i);
550 :
5680 tgl 551 120012 : if (PageAddItem(tmppage, (Item) itup, IndexTupleSize(itup), i, false, false) != i)
6031 bruce 552 UBC 0 : elog(ERROR, "failed to add item to index page in \"%s\"",
553 : RelationGetRelationName(gvs->index));
554 :
6031 bruce 555 CBC 120012 : pfree(itup);
2950 heikki.linnakangas 556 120012 : pfree(items);
557 : }
558 : }
559 : }
560 :
6031 bruce 561 866 : return (tmppage == origpage) ? NULL : tmppage;
562 : }
563 :
564 : IndexBulkDeleteResult *
2639 tgl 565 8 : ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
566 : IndexBulkDeleteCallback callback, void *callback_state)
567 : {
6186 568 8 : Relation index = info->index;
6031 bruce 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 :
3364 heikki.linnakangas 575 8 : gvs.tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
576 : "Gin vacuum temporary context",
577 : ALLOCSET_DEFAULT_SIZES);
5129 tgl 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? */
6186 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 : */
2537 teodor 593 8 : ginInsertCleanup(&gvs.ginstate, !IsAutoVacuumWorkerProcess(),
1970 rhaas 594 8 : false, true, stats);
595 : }
596 :
597 : /* we'll re-count the tuples each time */
6186 tgl 598 8 : stats->num_index_tuples = 0;
599 8 : gvs.result = stats;
600 :
5273 heikki.linnakangas 601 8 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
602 : RBM_NORMAL, info->strategy);
603 :
604 : /* find leaf page */
605 : for (;;)
6031 bruce 606 3 : {
2545 kgrittn 607 11 : Page page = BufferGetPage(buffer);
608 : IndexTuple itup;
609 :
6031 bruce 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 : {
6031 bruce 621 UBC 0 : LockBuffer(buffer, GIN_UNLOCK);
622 0 : continue; /* check it one more */
623 : }
6031 bruce 624 CBC 8 : break;
625 : }
626 :
627 3 : Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
628 :
6186 teodor 629 3 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
4475 tgl 630 3 : blkno = GinGetDownlink(itup);
6031 bruce 631 3 : Assert(blkno != InvalidBlockNumber);
632 :
5792 teodor 633 3 : UnlockReleaseBuffer(buffer);
5273 heikki.linnakangas 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 (;;)
6031 bruce 641 858 : {
2545 kgrittn 642 866 : Page page = BufferGetPage(buffer);
643 : Page resPage;
644 : uint32 i;
645 :
6031 bruce 646 866 : Assert(!GinPageIsData(page));
647 :
6186 teodor 648 866 : resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
649 :
6031 bruce 650 866 : blkno = GinPageGetOpaque(page)->rightlink;
651 :
652 866 : if (resPage)
653 : {
6186 teodor 654 865 : START_CRIT_SECTION();
6031 bruce 655 865 : PageRestoreTempPage(resPage, page);
656 865 : MarkBufferDirty(buffer);
5787 teodor 657 865 : xlogVacuumPage(gvs.index, buffer);
6186 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 :
6031 bruce 668 875 : for (i = 0; i < nRoot; i++)
669 : {
670 9 : ginVacuumPostingTree(&gvs, rootOfPostingTree[i]);
6186 teodor 671 9 : vacuum_delay_point();
672 : }
673 :
2118 tgl 674 866 : if (blkno == InvalidBlockNumber) /* rightmost page */
6186 teodor 675 8 : break;
676 :
5273 heikki.linnakangas 677 858 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
678 : RBM_NORMAL, info->strategy);
6031 bruce 679 858 : LockBuffer(buffer, GIN_EXCLUSIVE);
680 : }
681 :
3364 heikki.linnakangas 682 8 : MemoryContextDelete(gvs.tmpCxt);
683 :
2639 tgl 684 8 : return gvs.result;
685 : }
686 :
687 : IndexBulkDeleteResult *
688 34 : ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
689 : {
6031 bruce 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 : */
5129 tgl 702 34 : if (info->analyze_only)
703 : {
704 7 : if (IsAutoVacuumWorkerProcess())
705 : {
706 4 : initGinState(&ginstate, index);
1970 rhaas 707 4 : ginInsertCleanup(&ginstate, false, true, true, stats);
708 : }
2639 tgl 709 7 : return stats;
710 : }
711 :
712 : /*
713 : * Set up all-zero stats and cleanup pending inserts if ginbulkdelete
714 : * wasn't called
715 : */
6186 716 27 : if (stats == NULL)
717 : {
718 19 : stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
5129 719 19 : initGinState(&ginstate, index);
2537 teodor 720 19 : ginInsertCleanup(&ginstate, !IsAutoVacuumWorkerProcess(),
1970 rhaas 721 19 : false, true, stats);
722 : }
723 :
4557 tgl 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 : */
952 731 27 : stats->num_index_tuples = Max(info->num_heap_tuples, 0);
5055 732 27 : stats->estimated_count = info->estimated_count;
733 :
734 : /*
735 : * Need lock unless it's local to this backend.
736 : */
4808 737 27 : needLock = !RELATION_IS_LOCAL(index);
738 :
6186 teodor 739 27 : if (needLock)
740 24 : LockRelationForExtension(index, ExclusiveLock);
741 27 : npages = RelationGetNumberOfBlocks(index);
742 27 : if (needLock)
743 24 : UnlockRelationForExtension(index, ExclusiveLock);
744 :
5050 bruce 745 27 : totFreePages = 0;
746 :
4557 tgl 747 4593 : for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
748 : {
749 : Buffer buffer;
750 : Page page;
751 :
6186 teodor 752 4566 : vacuum_delay_point();
753 :
5273 heikki.linnakangas 754 4566 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
755 : RBM_NORMAL, info->strategy);
6186 teodor 756 4566 : LockBuffer(buffer, GIN_SHARE);
2545 kgrittn 757 4566 : page = (Page) BufferGetPage(buffer);
758 :
1578 akorotkov 759 4566 : if (GinPageIsRecyclable(page))
760 : {
4557 tgl 761 2322 : Assert(blkno != GIN_ROOT_BLKNO);
5304 heikki.linnakangas 762 2322 : RecordFreeIndexPage(index, blkno);
6044 tgl 763 2322 : totFreePages++;
764 : }
4557 765 2244 : else if (GinPageIsData(page))
766 : {
767 105 : idxStat.nDataPages++;
768 : }
769 2139 : else if (!GinPageIsList(page))
770 : {
771 2139 : idxStat.nEntryPages++;
772 :
4382 bruce 773 2139 : if (GinPageIsLeaf(page))
4557 tgl 774 2124 : idxStat.nEntries += PageGetMaxOffsetNumber(page);
775 : }
776 :
6186 teodor 777 4566 : UnlockReleaseBuffer(buffer);
778 : }
779 :
780 : /* Update the metapage with accurate page and entry counts */
4557 tgl 781 27 : idxStat.nTotalPages = npages;
1467 heikki.linnakangas 782 27 : ginUpdateStats(info->index, &idxStat, false);
783 :
784 : /* Finally, vacuum the FSM */
5298 785 27 : IndexFreeSpaceMapVacuum(info->index);
786 :
6044 tgl 787 27 : stats->pages_free = totFreePages;
788 :
6186 teodor 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 :
2639 tgl 795 27 : return stats;
796 : }
797 :
798 : /*
799 : * Return whether Page can safely be recycled.
800 : */
801 : bool
970 andres 802 4618 : GinPageIsRecyclable(Page page)
803 : {
804 : TransactionId delete_xid;
805 :
806 4618 : if (PageIsNew(page))
970 andres 807 UBC 0 : return true;
808 :
970 andres 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 : }
|