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