Age Owner Branch data 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-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 "storage/read_stream.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
4435 heikki.linnakangas@i 48 :CBC 125937 : ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
49 : : int nitem, int *nremaining)
50 : : {
51 : : int i,
52 : 125937 : remaining = 0;
4331 bruce@momjian.us 53 : 125937 : ItemPointer tmpitems = NULL;
54 : :
55 : : /*
56 : : * Iterate over TIDs array
57 : : */
7102 58 [ + + ]: 547910 : for (i = 0; i < nitem; i++)
59 : : {
60 [ + + ]: 421973 : if (gvs->callback(items + i, gvs->callback_state))
61 : : {
7257 teodor@sigaev.ru 62 : 368925 : gvs->result->tuples_removed += 1;
4435 heikki.linnakangas@i 63 [ + + ]: 368925 : if (!tmpitems)
64 : : {
65 : : /*
66 : : * First TID to be deleted: allocate memory to hold the
67 : : * remaining items.
68 : : */
95 michael@paquier.xyz 69 :GNC 120444 : tmpitems = palloc_array(ItemPointerData, nitem);
4435 heikki.linnakangas@i 70 :CBC 120444 : memcpy(tmpitems, items, sizeof(ItemPointerData) * i);
71 : : }
72 : : }
73 : : else
74 : : {
7257 teodor@sigaev.ru 75 : 53048 : gvs->result->num_index_tuples += 1;
4435 heikki.linnakangas@i 76 [ + + ]: 53048 : if (tmpitems)
77 : 3726 : tmpitems[remaining] = items[i];
78 : 53048 : remaining++;
79 : : }
80 : : }
81 : :
82 : 125937 : *nremaining = remaining;
83 : 125937 : return tmpitems;
84 : : }
85 : :
86 : : /*
87 : : * Create a WAL record for vacuuming entry tree leaf page.
88 : : */
89 : : static void
7102 bruce@momjian.us 90 : 862 : xlogVacuumPage(Relation index, Buffer buffer)
91 : : {
3616 kgrittn@postgresql.o 92 : 862 : Page page = BufferGetPage(buffer);
93 : : XLogRecPtr recptr;
94 : :
95 : : /* This is only used for entry tree leaf pages. */
4435 heikki.linnakangas@i 96 [ - + ]: 862 : Assert(!GinPageIsData(page));
7102 bruce@momjian.us 97 [ - + ]: 862 : Assert(GinPageIsLeaf(page));
98 : :
5571 rhaas@postgresql.org 99 [ + + - + : 862 : if (!RelationNeedsWAL(index))
- - - - ]
7102 bruce@momjian.us 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 : : */
4133 heikki.linnakangas@i 106 : 1 : XLogBeginInsert();
107 : 1 : XLogRegisterBuffer(0, buffer, REGBUF_FORCE_IMAGE | REGBUF_STANDARD);
108 : :
109 : 1 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_VACUUM_PAGE);
7257 teodor@sigaev.ru 110 : 1 : PageSetLSN(page, recptr);
111 : : }
112 : :
113 : :
114 : : /*
115 : : * Stack entry used during posting tree empty-page deletion scan.
116 : : *
117 : : * One DataPageDeleteStack entry is allocated per tree level. As
118 : : * ginScanPostingTreeToDelete() recurses down the tree, each entry tracks
119 : : * the buffer of the page currently being visited at that level ('buffer'),
120 : : * and the buffer of its left sibling ('leftBuffer'). The left page is kept
121 : : * pinned and exclusively locked because ginDeletePostingPage() needs it to
122 : : * update the sibling chain; acquiring it later could deadlock with
123 : : * ginStepRight(), which locks pages left-to-right.
124 : : */
125 : : typedef struct DataPageDeleteStack
126 : : {
127 : : struct DataPageDeleteStack *child;
128 : : struct DataPageDeleteStack *parent;
129 : :
130 : : Buffer buffer; /* buffer for the page being visited at this
131 : : * tree level */
132 : : Buffer leftBuffer; /* pinned and locked rightmost non-deleted
133 : : * sibling to the left of the current page */
134 : : OffsetNumber myoff; /* offset of this page's downlink in the
135 : : * parent */
136 : : bool isRoot;
137 : : } DataPageDeleteStack;
138 : :
139 : :
140 : : /*
141 : : * Delete a posting tree page.
142 : : *
143 : : * Removes the page identified by dBuffer from the posting tree by updating
144 : : * the left sibling's rightlink (in lBuffer) to skip over the deleted page,
145 : : * and removing the downlink from the parent page (in pBuffer). All three
146 : : * buffers must already have been pinned and exclusively locked by the caller.
147 : : *
148 : : * The buffers are NOT released nor unlocked here; the caller is responsible
149 : : * for this.
150 : : */
151 : : static void
22 akorotkov@postgresql 152 :GNC 6 : ginDeletePostingPage(GinVacuumState *gvs, Buffer dBuffer, Buffer lBuffer,
153 : : Buffer pBuffer, OffsetNumber myoff, bool isParentRoot)
154 : : {
155 : : Page page,
156 : : parentPage;
157 : : BlockNumber rightlink;
158 : 6 : BlockNumber deleteBlkno = BufferGetBlockNumber(dBuffer);
159 : :
160 : : /*
161 : : * This function MUST be called only if someone of parent pages hold
162 : : * exclusive cleanup lock. This guarantees that no insertions currently
163 : : * happen in this subtree. Caller also acquires Exclusive locks on
164 : : * deletable, parent and left pages.
165 : : */
166 : :
2907 teodor@sigaev.ru 167 :CBC 6 : page = BufferGetPage(dBuffer);
168 : 6 : rightlink = GinPageGetOpaque(page)->rightlink;
169 : :
170 : : /*
171 : : * Any insert which would have gone on the leaf block will now go to its
172 : : * right sibling.
173 : : */
174 : 6 : PredicateLockPageCombine(gvs->index, deleteBlkno, rightlink);
175 : :
7257 176 : 6 : START_CRIT_SECTION();
177 : :
178 : : /* Unlink the page by changing left sibling's rightlink */
3616 kgrittn@postgresql.o 179 : 6 : page = BufferGetPage(lBuffer);
4510 heikki.linnakangas@i 180 : 6 : GinPageGetOpaque(page)->rightlink = rightlink;
181 : :
182 : : /* Delete downlink from parent */
3616 kgrittn@postgresql.o 183 : 6 : parentPage = BufferGetPage(pBuffer);
184 : : #ifdef USE_ASSERT_CHECKING
185 : : do
186 : : {
4546 heikki.linnakangas@i 187 : 6 : PostingItem *tod = GinDataPageGetPostingItem(parentPage, myoff);
188 : :
6695 bruce@momjian.us 189 [ - + ]: 6 : Assert(PostingItemGetBlockNumber(tod) == deleteBlkno);
190 : : } while (0);
191 : : #endif
5628 tgl@sss.pgh.pa.us 192 : 6 : GinPageDeletePostingItem(parentPage, myoff);
193 : :
3616 kgrittn@postgresql.o 194 : 6 : page = BufferGetPage(dBuffer);
195 : :
196 : : /*
197 : : * we shouldn't change rightlink field to save workability of running
198 : : * search scan
199 : : */
200 : :
201 : : /*
202 : : * Mark page as deleted, and remember last xid which could know its
203 : : * address.
204 : : */
2308 akorotkov@postgresql 205 : 6 : GinPageSetDeleted(page);
1854 tmunro@postgresql.or 206 : 6 : GinPageSetDeleteXid(page, ReadNextTransactionId());
207 : :
6858 teodor@sigaev.ru 208 : 6 : MarkBufferDirty(pBuffer);
4353 heikki.linnakangas@i 209 : 6 : MarkBufferDirty(lBuffer);
6858 teodor@sigaev.ru 210 : 6 : MarkBufferDirty(dBuffer);
211 : :
5571 rhaas@postgresql.org 212 [ - + - - : 6 : if (RelationNeedsWAL(gvs->index))
- - - - ]
213 : : {
214 : : XLogRecPtr recptr;
215 : : ginxlogDeletePage data;
216 : :
217 : : /*
218 : : * We can't pass REGBUF_STANDARD for the deleted page, because we
219 : : * didn't set pd_lower on pre-9.4 versions. The page might've been
220 : : * binary-upgraded from an older version, and hence not have pd_lower
221 : : * set correctly. Ditto for the left page, but removing the item from
222 : : * the parent updated its pd_lower, so we know that's OK at this
223 : : * point.
224 : : */
4133 heikki.linnakangas@i 225 :UBC 0 : XLogBeginInsert();
226 : 0 : XLogRegisterBuffer(0, dBuffer, 0);
227 : 0 : XLogRegisterBuffer(1, pBuffer, REGBUF_STANDARD);
228 : 0 : XLogRegisterBuffer(2, lBuffer, 0);
229 : :
7257 teodor@sigaev.ru 230 : 0 : data.parentOffset = myoff;
7102 bruce@momjian.us 231 : 0 : data.rightLink = GinPageGetOpaque(page)->rightlink;
2649 akorotkov@postgresql 232 : 0 : data.deleteXid = GinPageGetDeleteXid(page);
233 : :
397 peter@eisentraut.org 234 : 0 : XLogRegisterData(&data, sizeof(ginxlogDeletePage));
235 : :
4133 heikki.linnakangas@i 236 : 0 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_DELETE_PAGE);
7257 teodor@sigaev.ru 237 : 0 : PageSetLSN(page, recptr);
238 : 0 : PageSetLSN(parentPage, recptr);
3616 kgrittn@postgresql.o 239 : 0 : PageSetLSN(BufferGetPage(lBuffer), recptr);
240 : : }
241 : :
24 michael@paquier.xyz 242 [ - + ]:CBC 6 : END_CRIT_SECTION();
243 : :
1844 pg@bowt.ie 244 : 6 : gvs->result->pages_newly_deleted++;
7257 teodor@sigaev.ru 245 : 6 : gvs->result->pages_deleted++;
246 : 6 : }
247 : :
248 : :
249 : : /*
250 : : * Scans a posting tree and deletes empty pages.
251 : : *
252 : : * The caller must hold a cleanup lock on the root page to prevent concurrent
253 : : * inserts. The entire path from the root down to the current page is kept
254 : : * exclusively locked throughout the scan. The left sibling at each level is
255 : : * also kept locked, because ginDeletePostingPage() needs it to update the
256 : : * rightlink of the left sibling; re-acquiring the left sibling lock later
257 : : * could deadlock with ginStepRight(), which acquires page locks
258 : : * left-to-right.
259 : : *
260 : : * All per-level state is carried in 'myStackItem': the buffer to process
261 : : * (must already be pinned and exclusively locked), the left sibling buffer,
262 : : * and this page's offset in the parent's downlink array. The root entry is
263 : : * set up by ginVacuumPostingTree(); child entries are populated here before
264 : : * recursing.
265 : : *
266 : : * Returns true if the page was deleted, false otherwise.
267 : : */
268 : : static bool
22 akorotkov@postgresql 269 :GNC 24 : ginScanPostingTreeToDelete(GinVacuumState *gvs, DataPageDeleteStack *myStackItem)
270 : : {
271 : 24 : Buffer buffer = myStackItem->buffer;
272 : : Page page;
273 : 24 : bool pageWasDeleted = false;
274 : : bool isempty;
275 : :
3616 kgrittn@postgresql.o 276 :CBC 24 : page = BufferGetPage(buffer);
277 : :
7102 bruce@momjian.us 278 [ - + ]: 24 : Assert(GinPageIsData(page));
279 : :
280 [ + + ]: 24 : if (!GinPageIsLeaf(page))
281 : : {
282 : : OffsetNumber i;
283 : :
22 akorotkov@postgresql 284 [ + + ]:GNC 24 : for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff;)
285 : : {
4546 heikki.linnakangas@i 286 :CBC 18 : PostingItem *pitem = GinDataPageGetPostingItem(page, i);
287 : : Buffer childBuffer;
288 : :
22 akorotkov@postgresql 289 :GNC 18 : childBuffer = ReadBufferExtended(gvs->index,
290 : : MAIN_FORKNUM,
291 : 18 : PostingItemGetBlockNumber(pitem),
292 : : RBM_NORMAL, gvs->strategy);
293 : 18 : LockBuffer(childBuffer, GIN_EXCLUSIVE);
294 : :
295 : : /* Allocate a child stack entry on first use; reuse thereafter */
296 [ + + ]: 18 : if (!myStackItem->child)
297 : : {
298 : 6 : myStackItem->child = palloc0_object(DataPageDeleteStack);
299 : 6 : myStackItem->child->parent = myStackItem;
300 : 6 : myStackItem->child->leftBuffer = InvalidBuffer;
301 : : }
302 : :
303 : 18 : myStackItem->child->buffer = childBuffer;
304 : 18 : myStackItem->child->isRoot = false;
305 : 18 : myStackItem->child->myoff = i;
306 : :
307 : : /*
308 : : * Recurse into child. If the child page was deleted, its
309 : : * downlink was removed from our page, so re-examine the same
310 : : * offset; otherwise advance to the next downlink.
311 : : */
312 [ + + ]: 18 : if (!ginScanPostingTreeToDelete(gvs, myStackItem->child))
313 : 12 : i++;
314 : : }
315 : 6 : myStackItem->buffer = InvalidBuffer;
316 : :
317 : : /*
318 : : * After processing all children at this level, release the child
319 : : * level's leftBuffer if we're at the rightmost page. There is no
320 : : * right sibling that could need it for deletion.
321 : : */
322 [ + - + - ]: 6 : if (GinPageRightMost(page) && BufferIsValid(myStackItem->child->leftBuffer))
323 : : {
324 : 6 : UnlockReleaseBuffer(myStackItem->child->leftBuffer);
325 : 6 : myStackItem->child->leftBuffer = InvalidBuffer;
326 : : }
327 : : }
328 : :
4435 heikki.linnakangas@i 329 [ + + ]:CBC 24 : if (GinPageIsLeaf(page))
330 [ + - ]: 18 : isempty = GinDataLeafPageIsEmpty(page);
331 : : else
332 : 6 : isempty = GinPageGetOpaque(page)->maxoff < FirstOffsetNumber;
333 : :
334 [ + + ]: 24 : if (isempty)
335 : : {
336 : : /*
337 : : * Proceed to the ginDeletePostingPage() if that's not the leftmost or
338 : : * the rightmost page.
339 : : */
22 akorotkov@postgresql 340 [ + + + + ]:GNC 15 : if (BufferIsValid(myStackItem->leftBuffer) && !GinPageRightMost(page))
341 : : {
342 [ - + ]: 6 : Assert(!myStackItem->isRoot);
343 : 6 : ginDeletePostingPage(gvs, buffer, myStackItem->leftBuffer,
344 : 6 : myStackItem->parent->buffer,
345 : 6 : myStackItem->myoff,
346 : 6 : myStackItem->parent->isRoot);
347 : 6 : pageWasDeleted = true;
348 : : }
349 : : }
350 : :
351 [ + + ]: 24 : if (!pageWasDeleted)
352 : : {
353 : : /*
354 : : * Keep this page as the new leftBuffer for this level: the next
355 : : * sibling to the right might need it for deletion. Release any
356 : : * previously held left page first.
357 : : */
358 [ + + ]: 18 : if (BufferIsValid(myStackItem->leftBuffer))
359 : 6 : UnlockReleaseBuffer(myStackItem->leftBuffer);
360 : 18 : myStackItem->leftBuffer = buffer;
361 : : }
362 : : else
363 : : {
364 : : /*
365 : : * Page was deleted; release the buffer. leftBuffer remains the same.
366 : : */
367 : 6 : UnlockReleaseBuffer(buffer);
368 : : }
369 : :
370 : 24 : return pageWasDeleted;
371 : : }
372 : :
373 : :
374 : : /*
375 : : * Scan through posting tree leafs, delete empty tuples. Returns true if there
376 : : * is at least one empty page.
377 : : */
378 : : static bool
2649 akorotkov@postgresql 379 :CBC 12 : ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
380 : : {
381 : : Buffer buffer;
382 : : Page page;
3133 peter_e@gmx.net 383 : 12 : bool hasVoidPage = false;
384 : : MemoryContext oldCxt;
385 : :
386 : : /* Find leftmost leaf page of posting tree and lock it in exclusive mode */
387 : : while (true)
2649 akorotkov@postgresql 388 : 6 : {
389 : : PostingItem *pitem;
390 : :
391 : 18 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
392 : : RBM_NORMAL, gvs->strategy);
393 : 18 : LockBuffer(buffer, GIN_SHARE);
394 : 18 : page = BufferGetPage(buffer);
395 : :
396 [ - + ]: 18 : Assert(GinPageIsData(page));
397 : :
398 [ + + ]: 18 : if (GinPageIsLeaf(page))
399 : : {
400 : 12 : LockBuffer(buffer, GIN_UNLOCK);
401 : 12 : LockBuffer(buffer, GIN_EXCLUSIVE);
402 : 12 : break;
403 : : }
404 : :
405 [ - + ]: 6 : Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
406 : :
407 : 6 : pitem = GinDataPageGetPostingItem(page, FirstOffsetNumber);
408 : 6 : blkno = PostingItemGetBlockNumber(pitem);
409 [ - + ]: 6 : Assert(blkno != InvalidBlockNumber);
410 : :
411 : 6 : UnlockReleaseBuffer(buffer);
412 : : }
413 : :
414 : : /* Iterate all posting tree leaves using rightlinks and vacuum them */
415 : : while (true)
416 : : {
3279 teodor@sigaev.ru 417 : 24 : oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
418 : 24 : ginVacuumPostingTreeLeaf(gvs->index, buffer, gvs);
419 : 24 : MemoryContextSwitchTo(oldCxt);
420 : 24 : MemoryContextReset(gvs->tmpCxt);
421 : :
422 [ + - + + ]: 24 : if (GinDataLeafPageIsEmpty(page))
3133 peter_e@gmx.net 423 : 15 : hasVoidPage = true;
424 : :
2649 akorotkov@postgresql 425 : 24 : blkno = GinPageGetOpaque(page)->rightlink;
426 : :
3279 teodor@sigaev.ru 427 : 24 : UnlockReleaseBuffer(buffer);
428 : :
2649 akorotkov@postgresql 429 [ + + ]: 24 : if (blkno == InvalidBlockNumber)
430 : 12 : break;
431 : :
432 : 12 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
433 : : RBM_NORMAL, gvs->strategy);
434 : 12 : LockBuffer(buffer, GIN_EXCLUSIVE);
435 : 12 : page = BufferGetPage(buffer);
436 : : }
437 : :
438 : 12 : return hasVoidPage;
439 : : }
440 : :
441 : : static void
442 : 12 : ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
443 : : {
444 [ + + ]: 12 : if (ginVacuumPostingTreeLeaves(gvs, rootBlkno))
445 : : {
446 : : /*
447 : : * There is at least one empty page. So we have to rescan the tree
448 : : * deleting empty pages.
449 : : */
450 : : Buffer buffer;
451 : : DataPageDeleteStack root,
452 : : *ptr,
453 : : *tmp;
454 : : bool deleted PG_USED_FOR_ASSERTS_ONLY;
455 : :
456 : 6 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, rootBlkno,
457 : : RBM_NORMAL, gvs->strategy);
458 : :
459 : : /*
460 : : * Lock posting tree root for cleanup to ensure there are no
461 : : * concurrent inserts.
462 : : */
463 : 6 : LockBufferForCleanup(buffer);
464 : :
465 : 6 : memset(&root, 0, sizeof(DataPageDeleteStack));
22 akorotkov@postgresql 466 :GNC 6 : root.buffer = buffer;
2308 akorotkov@postgresql 467 :CBC 6 : root.leftBuffer = InvalidBuffer;
22 akorotkov@postgresql 468 :GNC 6 : root.myoff = InvalidOffsetNumber;
2649 akorotkov@postgresql 469 :CBC 6 : root.isRoot = true;
470 : :
22 akorotkov@postgresql 471 :GNC 6 : deleted = ginScanPostingTreeToDelete(gvs, &root);
472 [ - + ]: 6 : Assert(!deleted);
473 : :
2649 akorotkov@postgresql 474 :CBC 6 : ptr = root.child;
475 : :
476 [ + + ]: 12 : while (ptr)
477 : : {
478 : 6 : tmp = ptr->child;
479 : 6 : pfree(ptr);
480 : 6 : ptr = tmp;
481 : : }
482 : :
483 : 6 : UnlockReleaseBuffer(buffer);
484 : : }
3279 teodor@sigaev.ru 485 : 12 : }
486 : :
487 : : /*
488 : : * returns modified page or NULL if page isn't modified.
489 : : * Function works with original page until first change is occurred,
490 : : * then page is copied into temporary one.
491 : : */
492 : : static Page
7102 bruce@momjian.us 493 : 937 : ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
494 : : {
3616 kgrittn@postgresql.o 495 : 937 : Page origpage = BufferGetPage(buffer),
496 : : tmppage;
497 : : OffsetNumber i,
7102 bruce@momjian.us 498 : 937 : maxoff = PageGetMaxOffsetNumber(origpage);
499 : :
7257 teodor@sigaev.ru 500 : 937 : tmppage = origpage;
501 : :
7102 bruce@momjian.us 502 : 937 : *nroot = 0;
503 : :
504 [ + + ]: 126409 : for (i = FirstOffsetNumber; i <= maxoff; i++)
505 : : {
506 : 125472 : IndexTuple itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
507 : :
508 [ + + ]: 125472 : if (GinIsPostingTree(itup))
509 : : {
510 : : /*
511 : : * store posting tree's roots for further processing, we can't
512 : : * vacuum it just now due to risk of deadlocks with scans/inserts
513 : : */
5546 tgl@sss.pgh.pa.us 514 : 12 : roots[*nroot] = GinGetDownlink(itup);
7257 teodor@sigaev.ru 515 : 12 : (*nroot)++;
516 : : }
7102 bruce@momjian.us 517 [ + - ]: 125460 : else if (GinGetNPosting(itup) > 0)
518 : : {
519 : : int nitems;
520 : : ItemPointer items_orig;
521 : : bool free_items_orig;
522 : : ItemPointer items;
523 : :
524 : : /* Get list of item pointers from the tuple. */
4435 heikki.linnakangas@i 525 [ + - ]: 125460 : if (GinItupIsCompressed(itup))
526 : : {
4021 527 : 125460 : items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(itup), &nitems);
528 : 125460 : free_items_orig = true;
529 : : }
530 : : else
531 : : {
4021 heikki.linnakangas@i 532 :UBC 0 : items_orig = (ItemPointer) GinGetPosting(itup);
4435 533 : 0 : nitems = GinGetNPosting(itup);
4021 534 : 0 : free_items_orig = false;
535 : : }
536 : :
537 : : /* Remove any items from the list that need to be vacuumed. */
4021 heikki.linnakangas@i 538 :CBC 125460 : items = ginVacuumItemPointers(gvs, items_orig, nitems, &nitems);
539 : :
540 [ + - ]: 125460 : if (free_items_orig)
541 : 125460 : pfree(items_orig);
542 : :
543 : : /* If any item pointers were removed, recreate the tuple. */
544 [ + + ]: 125460 : if (items)
545 : : {
546 : : OffsetNumber attnum;
547 : : Datum key;
548 : : GinNullCategory category;
549 : : GinPostingList *plist;
550 : : int plistsize;
551 : :
4435 552 [ + + ]: 120003 : if (nitems > 0)
553 : : {
4021 554 : 12 : plist = ginCompressPostingList(items, nitems, GinMaxItemSize, NULL);
4435 555 : 12 : plistsize = SizeOfGinPostingList(plist);
556 : : }
557 : : else
558 : : {
559 : 119991 : plist = NULL;
560 : 119991 : plistsize = 0;
561 : : }
562 : :
563 : : /*
564 : : * if we already created a temporary page, make changes in
565 : : * place
566 : : */
7102 bruce@momjian.us 567 [ + + ]: 120003 : if (tmppage == origpage)
568 : : {
569 : : /*
570 : : * On first difference, create a temporary copy of the
571 : : * page and copy the tuple's posting list to it.
572 : : */
6341 tgl@sss.pgh.pa.us 573 : 862 : tmppage = PageGetTempPageCopy(origpage);
574 : :
575 : : /* set itup pointer to new page */
7257 teodor@sigaev.ru 576 : 862 : itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
577 : : }
578 : :
6456 tgl@sss.pgh.pa.us 579 : 120003 : attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
5546 580 : 120003 : key = gintuple_get_key(&gvs->ginstate, itup, &category);
581 : 120003 : itup = GinFormTuple(&gvs->ginstate, attnum, key, category,
582 : : (char *) plist, plistsize,
583 : : nitems, true);
4435 heikki.linnakangas@i 584 [ + + ]: 120003 : if (plist)
585 : 12 : pfree(plist);
7257 teodor@sigaev.ru 586 : 120003 : PageIndexTupleDelete(tmppage, i);
587 : :
139 peter@eisentraut.org 588 [ - + ]:GNC 120003 : if (PageAddItem(tmppage, itup, IndexTupleSize(itup), i, false, false) != i)
7102 bruce@momjian.us 589 [ # # ]:UBC 0 : elog(ERROR, "failed to add item to index page in \"%s\"",
590 : : RelationGetRelationName(gvs->index));
591 : :
7102 bruce@momjian.us 592 :CBC 120003 : pfree(itup);
4021 heikki.linnakangas@i 593 : 120003 : pfree(items);
594 : : }
595 : : }
596 : : }
597 : :
7102 bruce@momjian.us 598 [ + + ]: 937 : return (tmppage == origpage) ? NULL : tmppage;
599 : : }
600 : :
601 : : IndexBulkDeleteResult *
3710 tgl@sss.pgh.pa.us 602 : 43 : ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
603 : : IndexBulkDeleteCallback callback, void *callback_state)
604 : : {
7257 605 : 43 : Relation index = info->index;
7102 bruce@momjian.us 606 : 43 : BlockNumber blkno = GIN_ROOT_BLKNO;
607 : : GinVacuumState gvs;
608 : : Buffer buffer;
609 : : BlockNumber rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))];
610 : : uint32 nRoot;
611 : :
4435 heikki.linnakangas@i 612 : 43 : gvs.tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
613 : : "Gin vacuum temporary context",
614 : : ALLOCSET_DEFAULT_SIZES);
6200 tgl@sss.pgh.pa.us 615 : 43 : gvs.index = index;
616 : 43 : gvs.callback = callback;
617 : 43 : gvs.callback_state = callback_state;
618 : 43 : gvs.strategy = info->strategy;
619 : 43 : initGinState(&gvs.ginstate, index);
620 : :
621 : : /* first time through? */
7257 622 [ + - ]: 43 : if (stats == NULL)
623 : : {
624 : : /* Yes, so initialize stats to zeroes */
95 michael@paquier.xyz 625 :GNC 43 : stats = palloc0_object(IndexBulkDeleteResult);
626 : :
627 : : /*
628 : : * and cleanup any pending inserts
629 : : */
741 heikki.linnakangas@i 630 :CBC 43 : ginInsertCleanup(&gvs.ginstate, !AmAutoVacuumWorkerProcess(),
631 : : false, true, stats);
632 : : }
633 : :
634 : : /* we'll re-count the tuples each time */
7257 tgl@sss.pgh.pa.us 635 : 43 : stats->num_index_tuples = 0;
636 : 43 : gvs.result = stats;
637 : :
6344 heikki.linnakangas@i 638 : 43 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
639 : : RBM_NORMAL, info->strategy);
640 : :
641 : : /* find leaf page */
642 : : for (;;)
7102 bruce@momjian.us 643 : 6 : {
3616 kgrittn@postgresql.o 644 : 49 : Page page = BufferGetPage(buffer);
645 : : IndexTuple itup;
646 : :
7102 bruce@momjian.us 647 : 49 : LockBuffer(buffer, GIN_SHARE);
648 : :
649 [ - + ]: 49 : Assert(!GinPageIsData(page));
650 : :
651 [ + + ]: 49 : if (GinPageIsLeaf(page))
652 : : {
653 : 43 : LockBuffer(buffer, GIN_UNLOCK);
654 : 43 : LockBuffer(buffer, GIN_EXCLUSIVE);
655 : :
656 [ + + - + ]: 43 : if (blkno == GIN_ROOT_BLKNO && !GinPageIsLeaf(page))
657 : : {
7102 bruce@momjian.us 658 :UBC 0 : LockBuffer(buffer, GIN_UNLOCK);
659 : 0 : continue; /* check it one more */
660 : : }
7102 bruce@momjian.us 661 :CBC 43 : break;
662 : : }
663 : :
664 [ - + ]: 6 : Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
665 : :
7257 teodor@sigaev.ru 666 : 6 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
5546 tgl@sss.pgh.pa.us 667 : 6 : blkno = GinGetDownlink(itup);
7102 bruce@momjian.us 668 [ - + ]: 6 : Assert(blkno != InvalidBlockNumber);
669 : :
6863 teodor@sigaev.ru 670 : 6 : UnlockReleaseBuffer(buffer);
6344 heikki.linnakangas@i 671 : 6 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
672 : : RBM_NORMAL, info->strategy);
673 : : }
674 : :
675 : : /* right now we found leftmost page in entry's BTree */
676 : :
677 : : for (;;)
7102 bruce@momjian.us 678 : 894 : {
3616 kgrittn@postgresql.o 679 : 937 : Page page = BufferGetPage(buffer);
680 : : Page resPage;
681 : : uint32 i;
682 : :
7102 bruce@momjian.us 683 [ - + ]: 937 : Assert(!GinPageIsData(page));
684 : :
7257 teodor@sigaev.ru 685 : 937 : resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
686 : :
7102 bruce@momjian.us 687 : 937 : blkno = GinPageGetOpaque(page)->rightlink;
688 : :
689 [ + + ]: 937 : if (resPage)
690 : : {
7257 teodor@sigaev.ru 691 : 862 : START_CRIT_SECTION();
7102 bruce@momjian.us 692 : 862 : PageRestoreTempPage(resPage, page);
693 : 862 : MarkBufferDirty(buffer);
6858 teodor@sigaev.ru 694 : 862 : xlogVacuumPage(gvs.index, buffer);
7257 695 [ - + ]: 862 : END_CRIT_SECTION();
24 michael@paquier.xyz 696 :GNC 862 : UnlockReleaseBuffer(buffer);
697 : : }
698 : : else
699 : : {
7257 teodor@sigaev.ru 700 :CBC 75 : UnlockReleaseBuffer(buffer);
701 : : }
702 : :
397 nathan@postgresql.or 703 : 937 : vacuum_delay_point(false);
704 : :
7102 bruce@momjian.us 705 [ + + ]: 949 : for (i = 0; i < nRoot; i++)
706 : : {
707 : 12 : ginVacuumPostingTree(&gvs, rootOfPostingTree[i]);
397 nathan@postgresql.or 708 : 12 : vacuum_delay_point(false);
709 : : }
710 : :
3189 tgl@sss.pgh.pa.us 711 [ + + ]: 937 : if (blkno == InvalidBlockNumber) /* rightmost page */
7257 teodor@sigaev.ru 712 : 43 : break;
713 : :
6344 heikki.linnakangas@i 714 : 894 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
715 : : RBM_NORMAL, info->strategy);
7102 bruce@momjian.us 716 : 894 : LockBuffer(buffer, GIN_EXCLUSIVE);
717 : : }
718 : :
4435 heikki.linnakangas@i 719 : 43 : MemoryContextDelete(gvs.tmpCxt);
720 : :
3710 tgl@sss.pgh.pa.us 721 : 43 : return gvs.result;
722 : : }
723 : :
724 : : IndexBulkDeleteResult *
725 : 38 : ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
726 : : {
7102 bruce@momjian.us 727 : 38 : Relation index = info->index;
728 : : bool needLock;
729 : : BlockNumber npages,
730 : : blkno;
731 : : BlockNumber totFreePages;
732 : : GinState ginstate;
733 : : GinStatsData idxStat;
734 : : BlockRangeReadStreamPrivate p;
735 : : ReadStream *stream;
736 : :
737 : : /*
738 : : * In an autovacuum analyze, we want to clean up pending insertions.
739 : : * Otherwise, an ANALYZE-only call is a no-op.
740 : : */
6200 tgl@sss.pgh.pa.us 741 [ + + ]: 38 : if (info->analyze_only)
742 : : {
741 heikki.linnakangas@i 743 [ + + ]: 10 : if (AmAutoVacuumWorkerProcess())
744 : : {
6200 tgl@sss.pgh.pa.us 745 : 7 : initGinState(&ginstate, index);
3041 rhaas@postgresql.org 746 : 7 : ginInsertCleanup(&ginstate, false, true, true, stats);
747 : : }
3710 tgl@sss.pgh.pa.us 748 : 10 : return stats;
749 : : }
750 : :
751 : : /*
752 : : * Set up all-zero stats and cleanup pending inserts if ginbulkdelete
753 : : * wasn't called
754 : : */
7257 755 [ + + ]: 28 : if (stats == NULL)
756 : : {
95 michael@paquier.xyz 757 :GNC 22 : stats = palloc0_object(IndexBulkDeleteResult);
6200 tgl@sss.pgh.pa.us 758 :CBC 22 : initGinState(&ginstate, index);
741 heikki.linnakangas@i 759 : 22 : ginInsertCleanup(&ginstate, !AmAutoVacuumWorkerProcess(),
760 : : false, true, stats);
761 : : }
762 : :
5628 tgl@sss.pgh.pa.us 763 : 28 : memset(&idxStat, 0, sizeof(idxStat));
764 : :
765 : : /*
766 : : * XXX we always report the heap tuple count as the number of index
767 : : * entries. This is bogus if the index is partial, but it's real hard to
768 : : * tell how many distinct heap entries are referenced by a GIN index.
769 : : */
2023 770 [ + - ]: 28 : stats->num_index_tuples = Max(info->num_heap_tuples, 0);
6126 771 : 28 : stats->estimated_count = info->estimated_count;
772 : :
773 : : /*
774 : : * Need lock unless it's local to this backend.
775 : : */
5879 776 [ + + + - ]: 28 : needLock = !RELATION_IS_LOCAL(index);
777 : :
7257 teodor@sigaev.ru 778 [ + + ]: 28 : if (needLock)
779 : 25 : LockRelationForExtension(index, ExclusiveLock);
780 : 28 : npages = RelationGetNumberOfBlocks(index);
781 [ + + ]: 28 : if (needLock)
782 : 25 : UnlockRelationForExtension(index, ExclusiveLock);
783 : :
6121 bruce@momjian.us 784 : 28 : totFreePages = 0;
785 : :
786 : : /* Scan all blocks starting from the root using streaming reads */
3 michael@paquier.xyz 787 :GNC 28 : p.current_blocknum = GIN_ROOT_BLKNO;
788 : 28 : p.last_exclusive = npages;
789 : :
790 : : /*
791 : : * It is safe to use batchmode as block_range_read_stream_cb takes no
792 : : * locks.
793 : : */
794 : 28 : stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
795 : : READ_STREAM_FULL |
796 : : READ_STREAM_USE_BATCHING,
797 : : info->strategy,
798 : : index,
799 : : MAIN_FORKNUM,
800 : : block_range_read_stream_cb,
801 : : &p,
802 : : 0);
803 : :
5628 tgl@sss.pgh.pa.us 804 [ + + ]:CBC 4606 : for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
805 : : {
806 : : Buffer buffer;
807 : : Page page;
808 : :
397 nathan@postgresql.or 809 : 4578 : vacuum_delay_point(false);
810 : :
3 michael@paquier.xyz 811 :GNC 4578 : buffer = read_stream_next_buffer(stream, NULL);
812 : :
7257 teodor@sigaev.ru 813 :CBC 4578 : LockBuffer(buffer, GIN_SHARE);
198 peter@eisentraut.org 814 :GNC 4578 : page = BufferGetPage(buffer);
815 : :
2649 akorotkov@postgresql 816 [ + + ]:CBC 4578 : if (GinPageIsRecyclable(page))
817 : : {
5628 tgl@sss.pgh.pa.us 818 [ - + ]: 2330 : Assert(blkno != GIN_ROOT_BLKNO);
6375 heikki.linnakangas@i 819 : 2330 : RecordFreeIndexPage(index, blkno);
7115 tgl@sss.pgh.pa.us 820 : 2330 : totFreePages++;
821 : : }
5628 822 [ + + ]: 2248 : else if (GinPageIsData(page))
823 : : {
824 : 108 : idxStat.nDataPages++;
825 : : }
826 [ + - ]: 2140 : else if (!GinPageIsList(page))
827 : : {
828 : 2140 : idxStat.nEntryPages++;
829 : :
5453 bruce@momjian.us 830 [ + + ]: 2140 : if (GinPageIsLeaf(page))
5628 tgl@sss.pgh.pa.us 831 : 2125 : idxStat.nEntries += PageGetMaxOffsetNumber(page);
832 : : }
833 : :
7257 teodor@sigaev.ru 834 : 4578 : UnlockReleaseBuffer(buffer);
835 : : }
836 : :
3 michael@paquier.xyz 837 [ - + ]:GNC 28 : Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
838 : 28 : read_stream_end(stream);
839 : :
840 : : /* Update the metapage with accurate page and entry counts */
5628 tgl@sss.pgh.pa.us 841 :CBC 28 : idxStat.nTotalPages = npages;
2538 heikki.linnakangas@i 842 : 28 : ginUpdateStats(info->index, &idxStat, false);
843 : :
844 : : /* Finally, vacuum the FSM */
6369 845 : 28 : IndexFreeSpaceMapVacuum(info->index);
846 : :
7115 tgl@sss.pgh.pa.us 847 : 28 : stats->pages_free = totFreePages;
848 : :
7257 teodor@sigaev.ru 849 [ + + ]: 28 : if (needLock)
850 : 25 : LockRelationForExtension(index, ExclusiveLock);
851 : 28 : stats->num_pages = RelationGetNumberOfBlocks(index);
852 [ + + ]: 28 : if (needLock)
853 : 25 : UnlockRelationForExtension(index, ExclusiveLock);
854 : :
3710 tgl@sss.pgh.pa.us 855 : 28 : return stats;
856 : : }
857 : :
858 : : /*
859 : : * Return whether Page can safely be recycled.
860 : : */
861 : : bool
2041 andres@anarazel.de 862 : 4631 : GinPageIsRecyclable(Page page)
863 : : {
864 : : TransactionId delete_xid;
865 : :
866 [ - + ]: 4631 : if (PageIsNew(page))
2041 andres@anarazel.de 867 :UBC 0 : return true;
868 : :
2041 andres@anarazel.de 869 [ + + ]:CBC 4631 : if (!GinPageIsDeleted(page))
870 : 2242 : return false;
871 : :
872 : 2389 : delete_xid = GinPageGetDeleteXid(page);
873 : :
874 [ + + ]: 2389 : if (!TransactionIdIsValid(delete_xid))
875 : 2383 : return true;
876 : :
877 : : /*
878 : : * If no backend still could view delete_xid as in running, all scans
879 : : * concurrent with ginDeletePostingPage() must have finished.
880 : : */
881 : 6 : return GlobalVisCheckRemovableXid(NULL, delete_xid);
882 : : }
|