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