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-2024, 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
3735 heikki.linnakangas@i 48 :CBC 120492 : ginVacuumItemPointers(GinVacuumState *gvs, ItemPointerData *items,
49 : : int nitem, int *nremaining)
50 : : {
51 : : int i,
52 : 120492 : remaining = 0;
3631 bruce@momjian.us 53 : 120492 : ItemPointer tmpitems = NULL;
54 : :
55 : : /*
56 : : * Iterate over TIDs array
57 : : */
6402 58 [ + + ]: 504318 : for (i = 0; i < nitem; i++)
59 : : {
60 [ + + ]: 383826 : if (gvs->callback(items + i, gvs->callback_state))
61 : : {
6557 teodor@sigaev.ru 62 : 368925 : gvs->result->tuples_removed += 1;
3735 heikki.linnakangas@i 63 [ + + ]: 368925 : if (!tmpitems)
64 : : {
65 : : /*
66 : : * First TID to be deleted: allocate memory to hold the
67 : : * remaining items.
68 : : */
69 : 120444 : tmpitems = palloc(sizeof(ItemPointerData) * nitem);
70 : 120444 : memcpy(tmpitems, items, sizeof(ItemPointerData) * i);
71 : : }
72 : : }
73 : : else
74 : : {
6557 teodor@sigaev.ru 75 : 14901 : gvs->result->num_index_tuples += 1;
3735 heikki.linnakangas@i 76 [ + + ]: 14901 : if (tmpitems)
77 : 3726 : tmpitems[remaining] = items[i];
78 : 14901 : remaining++;
79 : : }
80 : : }
81 : :
82 : 120492 : *nremaining = remaining;
83 : 120492 : return tmpitems;
84 : : }
85 : :
86 : : /*
87 : : * Create a WAL record for vacuuming entry tree leaf page.
88 : : */
89 : : static void
6402 bruce@momjian.us 90 : 862 : xlogVacuumPage(Relation index, Buffer buffer)
91 : : {
2916 kgrittn@postgresql.o 92 : 862 : Page page = BufferGetPage(buffer);
93 : : XLogRecPtr recptr;
94 : :
95 : : /* This is only used for entry tree leaf pages. */
3735 heikki.linnakangas@i 96 [ - + ]: 862 : Assert(!GinPageIsData(page));
6402 bruce@momjian.us 97 [ - + ]: 862 : Assert(GinPageIsLeaf(page));
98 : :
4871 rhaas@postgresql.org 99 [ + + + - : 862 : if (!RelationNeedsWAL(index))
+ - - + ]
6402 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 : : */
3433 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);
6557 teodor@sigaev.ru 110 : 1 : 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
6402 bruce@momjian.us 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 : : */
3810 heikki.linnakangas@i 146 : 6 : lBuffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, leftBlkno,
147 : : RBM_NORMAL, gvs->strategy);
5644 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 : :
2207 teodor@sigaev.ru 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 : :
6557 162 : 6 : START_CRIT_SECTION();
163 : :
164 : : /* Unlink the page by changing left sibling's rightlink */
2916 kgrittn@postgresql.o 165 : 6 : page = BufferGetPage(lBuffer);
3810 heikki.linnakangas@i 166 : 6 : GinPageGetOpaque(page)->rightlink = rightlink;
167 : :
168 : : /* Delete downlink from parent */
2916 kgrittn@postgresql.o 169 : 6 : parentPage = BufferGetPage(pBuffer);
170 : : #ifdef USE_ASSERT_CHECKING
171 : : do
172 : : {
3846 heikki.linnakangas@i 173 : 6 : PostingItem *tod = GinDataPageGetPostingItem(parentPage, myoff);
174 : :
5995 bruce@momjian.us 175 [ - + ]: 6 : Assert(PostingItemGetBlockNumber(tod) == deleteBlkno);
176 : : } while (0);
177 : : #endif
4928 tgl@sss.pgh.pa.us 178 : 6 : GinPageDeletePostingItem(parentPage, myoff);
179 : :
2916 kgrittn@postgresql.o 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 : : */
1608 akorotkov@postgresql 191 : 6 : GinPageSetDeleted(page);
1154 tmunro@postgresql.or 192 : 6 : GinPageSetDeleteXid(page, ReadNextTransactionId());
193 : :
6158 teodor@sigaev.ru 194 : 6 : MarkBufferDirty(pBuffer);
3653 heikki.linnakangas@i 195 : 6 : MarkBufferDirty(lBuffer);
6158 teodor@sigaev.ru 196 : 6 : MarkBufferDirty(dBuffer);
197 : :
4871 rhaas@postgresql.org 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 : : */
3433 heikki.linnakangas@i 211 :UBC 0 : XLogBeginInsert();
212 : 0 : XLogRegisterBuffer(0, dBuffer, 0);
213 : 0 : XLogRegisterBuffer(1, pBuffer, REGBUF_STANDARD);
214 : 0 : XLogRegisterBuffer(2, lBuffer, 0);
215 : :
6557 teodor@sigaev.ru 216 : 0 : data.parentOffset = myoff;
6402 bruce@momjian.us 217 : 0 : data.rightLink = GinPageGetOpaque(page)->rightlink;
1949 akorotkov@postgresql 218 : 0 : data.deleteXid = GinPageGetDeleteXid(page);
219 : :
3433 heikki.linnakangas@i 220 : 0 : XLogRegisterData((char *) &data, sizeof(ginxlogDeletePage));
221 : :
222 : 0 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_DELETE_PAGE);
6557 teodor@sigaev.ru 223 : 0 : PageSetLSN(page, recptr);
224 : 0 : PageSetLSN(parentPage, recptr);
2916 kgrittn@postgresql.o 225 : 0 : PageSetLSN(BufferGetPage(lBuffer), recptr);
226 : : }
227 : :
6402 bruce@momjian.us 228 :CBC 6 : ReleaseBuffer(pBuffer);
1608 akorotkov@postgresql 229 : 6 : ReleaseBuffer(lBuffer);
2579 teodor@sigaev.ru 230 : 6 : ReleaseBuffer(dBuffer);
231 : :
6557 232 [ - + ]: 6 : END_CRIT_SECTION();
233 : :
1144 pg@bowt.ie 234 : 6 : gvs->result->pages_newly_deleted++;
6557 teodor@sigaev.ru 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
3812 heikki.linnakangas@i 247 : 24 : ginScanToDelete(GinVacuumState *gvs, BlockNumber blkno, bool isRoot,
248 : : DataPageDeleteStack *parent, OffsetNumber myoff)
249 : : {
250 : : DataPageDeleteStack *me;
251 : : Buffer buffer;
252 : : Page page;
2433 peter_e@gmx.net 253 : 24 : bool meDelete = false;
254 : : bool isempty;
255 : :
6402 bruce@momjian.us 256 [ + + ]: 24 : if (isRoot)
257 : : {
6557 teodor@sigaev.ru 258 : 6 : me = parent;
259 : : }
260 : : else
261 : : {
6402 bruce@momjian.us 262 [ + + ]: 18 : if (!parent->child)
263 : : {
264 : 6 : me = (DataPageDeleteStack *) palloc0(sizeof(DataPageDeleteStack));
265 : 6 : me->parent = parent;
6557 teodor@sigaev.ru 266 : 6 : parent->child = me;
1608 akorotkov@postgresql 267 : 6 : me->leftBuffer = InvalidBuffer;
268 : : }
269 : : else
6557 teodor@sigaev.ru 270 : 12 : me = parent->child;
271 : : }
272 : :
5644 heikki.linnakangas@i 273 : 24 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
274 : : RBM_NORMAL, gvs->strategy);
275 : :
2524 bruce@momjian.us 276 [ + + ]: 24 : if (!isRoot)
2579 teodor@sigaev.ru 277 : 18 : LockBuffer(buffer, GIN_EXCLUSIVE);
278 : :
2916 kgrittn@postgresql.o 279 : 24 : page = BufferGetPage(buffer);
280 : :
6402 bruce@momjian.us 281 [ - + ]: 24 : Assert(GinPageIsData(page));
282 : :
283 [ + + ]: 24 : if (!GinPageIsLeaf(page))
284 : : {
285 : : OffsetNumber i;
286 : :
6345 teodor@sigaev.ru 287 : 6 : me->blkno = blkno;
6402 bruce@momjian.us 288 [ + + ]: 24 : for (i = FirstOffsetNumber; i <= GinPageGetOpaque(page)->maxoff; i++)
289 : : {
3846 heikki.linnakangas@i 290 : 18 : PostingItem *pitem = GinDataPageGetPostingItem(page, i);
291 : :
2433 peter_e@gmx.net 292 [ + + ]: 18 : if (ginScanToDelete(gvs, PostingItemGetBlockNumber(pitem), false, me, i))
6557 teodor@sigaev.ru 293 : 6 : i--;
294 : : }
295 : :
1608 akorotkov@postgresql 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 : :
3735 heikki.linnakangas@i 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 */
1608 akorotkov@postgresql 311 [ + + + + ]: 15 : if (BufferIsValid(me->leftBuffer) && !GinPageRightMost(page))
312 : : {
6402 bruce@momjian.us 313 [ - + ]: 6 : Assert(!isRoot);
1608 akorotkov@postgresql 314 : 6 : ginDeletePage(gvs, blkno, BufferGetBlockNumber(me->leftBuffer),
315 : 6 : me->parent->blkno, myoff, me->parent->isRoot);
2433 peter_e@gmx.net 316 : 6 : meDelete = true;
317 : : }
318 : : }
319 : :
1608 akorotkov@postgresql 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 : :
6557 teodor@sigaev.ru 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
1949 akorotkov@postgresql 346 : 12 : ginVacuumPostingTreeLeaves(GinVacuumState *gvs, BlockNumber blkno)
347 : : {
348 : : Buffer buffer;
349 : : Page page;
2433 peter_e@gmx.net 350 : 12 : bool hasVoidPage = false;
351 : : MemoryContext oldCxt;
352 : :
353 : : /* Find leftmost leaf page of posting tree and lock it in exclusive mode */
354 : : while (true)
1949 akorotkov@postgresql 355 : 6 : {
356 : : PostingItem *pitem;
357 : :
358 : 18 : buffer = ReadBufferExtended(gvs->index, MAIN_FORKNUM, blkno,
359 : : RBM_NORMAL, gvs->strategy);
360 : 18 : LockBuffer(buffer, GIN_SHARE);
361 : 18 : page = BufferGetPage(buffer);
362 : :
363 [ - + ]: 18 : Assert(GinPageIsData(page));
364 : :
365 [ + + ]: 18 : if (GinPageIsLeaf(page))
366 : : {
367 : 12 : LockBuffer(buffer, GIN_UNLOCK);
368 : 12 : LockBuffer(buffer, GIN_EXCLUSIVE);
369 : 12 : 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 : : {
2579 teodor@sigaev.ru 384 : 24 : oldCxt = MemoryContextSwitchTo(gvs->tmpCxt);
385 : 24 : ginVacuumPostingTreeLeaf(gvs->index, buffer, gvs);
386 : 24 : MemoryContextSwitchTo(oldCxt);
387 : 24 : MemoryContextReset(gvs->tmpCxt);
388 : :
389 [ + - + + ]: 24 : if (GinDataLeafPageIsEmpty(page))
2433 peter_e@gmx.net 390 : 15 : hasVoidPage = true;
391 : :
1949 akorotkov@postgresql 392 : 24 : blkno = GinPageGetOpaque(page)->rightlink;
393 : :
2579 teodor@sigaev.ru 394 : 24 : UnlockReleaseBuffer(buffer);
395 : :
1949 akorotkov@postgresql 396 [ + + ]: 24 : if (blkno == InvalidBlockNumber)
397 : 12 : 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 : 12 : return hasVoidPage;
406 : : }
407 : :
408 : : static void
409 : 12 : ginVacuumPostingTree(GinVacuumState *gvs, BlockNumber rootBlkno)
410 : : {
411 [ + + ]: 12 : 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));
1608 432 : 6 : root.leftBuffer = InvalidBuffer;
1949 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 : : }
2579 teodor@sigaev.ru 448 : 12 : }
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
6402 bruce@momjian.us 456 : 864 : ginVacuumEntryPage(GinVacuumState *gvs, Buffer buffer, BlockNumber *roots, uint32 *nroot)
457 : : {
2916 kgrittn@postgresql.o 458 : 864 : Page origpage = BufferGetPage(buffer),
459 : : tmppage;
460 : : OffsetNumber i,
6402 bruce@momjian.us 461 : 864 : maxoff = PageGetMaxOffsetNumber(origpage);
462 : :
6557 teodor@sigaev.ru 463 : 864 : tmppage = origpage;
464 : :
6402 bruce@momjian.us 465 : 864 : *nroot = 0;
466 : :
467 [ + + ]: 120891 : for (i = FirstOffsetNumber; i <= maxoff; i++)
468 : : {
469 : 120027 : IndexTuple itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
470 : :
471 [ + + ]: 120027 : 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 : : */
4846 tgl@sss.pgh.pa.us 477 : 12 : roots[*nroot] = GinGetDownlink(itup);
6557 teodor@sigaev.ru 478 : 12 : (*nroot)++;
479 : : }
6402 bruce@momjian.us 480 [ + - ]: 120015 : 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. */
3735 heikki.linnakangas@i 488 [ + - ]: 120015 : if (GinItupIsCompressed(itup))
489 : : {
3321 490 : 120015 : items_orig = ginPostingListDecode((GinPostingList *) GinGetPosting(itup), &nitems);
491 : 120015 : free_items_orig = true;
492 : : }
493 : : else
494 : : {
3321 heikki.linnakangas@i 495 :UBC 0 : items_orig = (ItemPointer) GinGetPosting(itup);
3735 496 : 0 : nitems = GinGetNPosting(itup);
3321 497 : 0 : free_items_orig = false;
498 : : }
499 : :
500 : : /* Remove any items from the list that need to be vacuumed. */
3321 heikki.linnakangas@i 501 :CBC 120015 : items = ginVacuumItemPointers(gvs, items_orig, nitems, &nitems);
502 : :
503 [ + - ]: 120015 : if (free_items_orig)
504 : 120015 : pfree(items_orig);
505 : :
506 : : /* If any item pointers were removed, recreate the tuple. */
507 [ + + ]: 120015 : if (items)
508 : : {
509 : : OffsetNumber attnum;
510 : : Datum key;
511 : : GinNullCategory category;
512 : : GinPostingList *plist;
513 : : int plistsize;
514 : :
3735 515 [ + + ]: 120003 : if (nitems > 0)
516 : : {
3321 517 : 12 : plist = ginCompressPostingList(items, nitems, GinMaxItemSize, NULL);
3735 518 : 12 : 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 : : */
6402 bruce@momjian.us 530 [ + + ]: 120003 : 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 : : */
5641 tgl@sss.pgh.pa.us 536 : 862 : tmppage = PageGetTempPageCopy(origpage);
537 : :
538 : : /* set itup pointer to new page */
6557 teodor@sigaev.ru 539 : 862 : itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i));
540 : : }
541 : :
5756 tgl@sss.pgh.pa.us 542 : 120003 : attnum = gintuple_get_attrnum(&gvs->ginstate, itup);
4846 543 : 120003 : key = gintuple_get_key(&gvs->ginstate, itup, &category);
544 : 120003 : itup = GinFormTuple(&gvs->ginstate, attnum, key, category,
545 : : (char *) plist, plistsize,
546 : : nitems, true);
3735 heikki.linnakangas@i 547 [ + + ]: 120003 : if (plist)
548 : 12 : pfree(plist);
6557 teodor@sigaev.ru 549 : 120003 : PageIndexTupleDelete(tmppage, i);
550 : :
6051 tgl@sss.pgh.pa.us 551 [ - + ]: 120003 : if (PageAddItem(tmppage, (Item) itup, IndexTupleSize(itup), i, false, false) != i)
6402 bruce@momjian.us 552 [ # # ]:UBC 0 : elog(ERROR, "failed to add item to index page in \"%s\"",
553 : : RelationGetRelationName(gvs->index));
554 : :
6402 bruce@momjian.us 555 :CBC 120003 : pfree(itup);
3321 heikki.linnakangas@i 556 : 120003 : pfree(items);
557 : : }
558 : : }
559 : : }
560 : :
6402 bruce@momjian.us 561 [ + + ]: 864 : return (tmppage == origpage) ? NULL : tmppage;
562 : : }
563 : :
564 : : IndexBulkDeleteResult *
3010 tgl@sss.pgh.pa.us 565 : 6 : ginbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
566 : : IndexBulkDeleteCallback callback, void *callback_state)
567 : : {
6557 568 : 6 : Relation index = info->index;
6402 bruce@momjian.us 569 : 6 : BlockNumber blkno = GIN_ROOT_BLKNO;
570 : : GinVacuumState gvs;
571 : : Buffer buffer;
572 : : BlockNumber rootOfPostingTree[BLCKSZ / (sizeof(IndexTupleData) + sizeof(ItemId))];
573 : : uint32 nRoot;
574 : :
3735 heikki.linnakangas@i 575 : 6 : gvs.tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
576 : : "Gin vacuum temporary context",
577 : : ALLOCSET_DEFAULT_SIZES);
5500 tgl@sss.pgh.pa.us 578 : 6 : gvs.index = index;
579 : 6 : gvs.callback = callback;
580 : 6 : gvs.callback_state = callback_state;
581 : 6 : gvs.strategy = info->strategy;
582 : 6 : initGinState(&gvs.ginstate, index);
583 : :
584 : : /* first time through? */
6557 585 [ + - ]: 6 : if (stats == NULL)
586 : : {
587 : : /* Yes, so initialize stats to zeroes */
588 : 6 : stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
589 : :
590 : : /*
591 : : * and cleanup any pending inserts
592 : : */
41 heikki.linnakangas@i 593 :GNC 6 : ginInsertCleanup(&gvs.ginstate, !AmAutoVacuumWorkerProcess(),
2341 rhaas@postgresql.org 594 :ECB (7) : false, true, stats);
595 : : }
596 : :
597 : : /* we'll re-count the tuples each time */
6557 tgl@sss.pgh.pa.us 598 :CBC 6 : stats->num_index_tuples = 0;
599 : 6 : gvs.result = stats;
600 : :
5644 heikki.linnakangas@i 601 : 6 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
602 : : RBM_NORMAL, info->strategy);
603 : :
604 : : /* find leaf page */
605 : : for (;;)
6402 bruce@momjian.us 606 : 3 : {
2916 kgrittn@postgresql.o 607 : 9 : Page page = BufferGetPage(buffer);
608 : : IndexTuple itup;
609 : :
6402 bruce@momjian.us 610 : 9 : LockBuffer(buffer, GIN_SHARE);
611 : :
612 [ - + ]: 9 : Assert(!GinPageIsData(page));
613 : :
614 [ + + ]: 9 : if (GinPageIsLeaf(page))
615 : : {
616 : 6 : LockBuffer(buffer, GIN_UNLOCK);
617 : 6 : LockBuffer(buffer, GIN_EXCLUSIVE);
618 : :
619 [ + + - + ]: 6 : if (blkno == GIN_ROOT_BLKNO && !GinPageIsLeaf(page))
620 : : {
6402 bruce@momjian.us 621 :UBC 0 : LockBuffer(buffer, GIN_UNLOCK);
622 : 0 : continue; /* check it one more */
623 : : }
6402 bruce@momjian.us 624 :CBC 6 : break;
625 : : }
626 : :
627 [ - + ]: 3 : Assert(PageGetMaxOffsetNumber(page) >= FirstOffsetNumber);
628 : :
6557 teodor@sigaev.ru 629 : 3 : itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber));
4846 tgl@sss.pgh.pa.us 630 : 3 : blkno = GinGetDownlink(itup);
6402 bruce@momjian.us 631 [ - + ]: 3 : Assert(blkno != InvalidBlockNumber);
632 : :
6163 teodor@sigaev.ru 633 : 3 : UnlockReleaseBuffer(buffer);
5644 heikki.linnakangas@i 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 (;;)
6402 bruce@momjian.us 641 : 858 : {
2916 kgrittn@postgresql.o 642 : 864 : Page page = BufferGetPage(buffer);
643 : : Page resPage;
644 : : uint32 i;
645 : :
6402 bruce@momjian.us 646 [ - + ]: 864 : Assert(!GinPageIsData(page));
647 : :
6557 teodor@sigaev.ru 648 : 864 : resPage = ginVacuumEntryPage(&gvs, buffer, rootOfPostingTree, &nRoot);
649 : :
6402 bruce@momjian.us 650 : 864 : blkno = GinPageGetOpaque(page)->rightlink;
651 : :
652 [ + + ]: 864 : if (resPage)
653 : : {
6557 teodor@sigaev.ru 654 : 862 : START_CRIT_SECTION();
6402 bruce@momjian.us 655 : 862 : PageRestoreTempPage(resPage, page);
656 : 862 : MarkBufferDirty(buffer);
6158 teodor@sigaev.ru 657 : 862 : xlogVacuumPage(gvs.index, buffer);
6557 658 : 862 : UnlockReleaseBuffer(buffer);
659 [ - + ]: 862 : END_CRIT_SECTION();
660 : : }
661 : : else
662 : : {
663 : 2 : UnlockReleaseBuffer(buffer);
664 : : }
665 : :
666 : 864 : vacuum_delay_point();
667 : :
6402 bruce@momjian.us 668 [ + + ]: 876 : for (i = 0; i < nRoot; i++)
669 : : {
670 : 12 : ginVacuumPostingTree(&gvs, rootOfPostingTree[i]);
6557 teodor@sigaev.ru 671 : 12 : vacuum_delay_point();
672 : : }
673 : :
2489 tgl@sss.pgh.pa.us 674 [ + + ]: 864 : if (blkno == InvalidBlockNumber) /* rightmost page */
6557 teodor@sigaev.ru 675 : 6 : break;
676 : :
5644 heikki.linnakangas@i 677 : 858 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
678 : : RBM_NORMAL, info->strategy);
6402 bruce@momjian.us 679 : 858 : LockBuffer(buffer, GIN_EXCLUSIVE);
680 : : }
681 : :
3735 heikki.linnakangas@i 682 : 6 : MemoryContextDelete(gvs.tmpCxt);
683 : :
3010 tgl@sss.pgh.pa.us 684 : 6 : return gvs.result;
685 : : }
686 : :
687 : : IndexBulkDeleteResult *
688 : 39 : ginvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
689 : : {
6402 bruce@momjian.us 690 : 39 : 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 : : */
5500 tgl@sss.pgh.pa.us 702 [ + + ]: 39 : if (info->analyze_only)
703 : : {
41 heikki.linnakangas@i 704 [ + + ]:GNC 11 : if (AmAutoVacuumWorkerProcess())
705 : : {
5500 tgl@sss.pgh.pa.us 706 :CBC 8 : initGinState(&ginstate, index);
2341 rhaas@postgresql.org 707 : 8 : ginInsertCleanup(&ginstate, false, true, true, stats);
708 : : }
3010 tgl@sss.pgh.pa.us 709 : 11 : return stats;
710 : : }
711 : :
712 : : /*
713 : : * Set up all-zero stats and cleanup pending inserts if ginbulkdelete
714 : : * wasn't called
715 : : */
6557 716 [ + + ]: 28 : if (stats == NULL)
717 : : {
718 : 22 : stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
5500 719 : 22 : initGinState(&ginstate, index);
41 heikki.linnakangas@i 720 :GNC 22 : ginInsertCleanup(&ginstate, !AmAutoVacuumWorkerProcess(),
2341 rhaas@postgresql.org 721 :ECB (20) : false, true, stats);
722 : : }
723 : :
4928 tgl@sss.pgh.pa.us 724 :CBC 28 : 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 : : */
1323 731 [ + - ]: 28 : stats->num_index_tuples = Max(info->num_heap_tuples, 0);
5426 732 : 28 : stats->estimated_count = info->estimated_count;
733 : :
734 : : /*
735 : : * Need lock unless it's local to this backend.
736 : : */
5179 737 [ + + + - ]: 28 : needLock = !RELATION_IS_LOCAL(index);
738 : :
6557 teodor@sigaev.ru 739 [ + + ]: 28 : if (needLock)
740 : 25 : LockRelationForExtension(index, ExclusiveLock);
741 : 28 : npages = RelationGetNumberOfBlocks(index);
742 [ + + ]: 28 : if (needLock)
743 : 25 : UnlockRelationForExtension(index, ExclusiveLock);
744 : :
5421 bruce@momjian.us 745 : 28 : totFreePages = 0;
746 : :
4928 tgl@sss.pgh.pa.us 747 [ + + ]: 4606 : for (blkno = GIN_ROOT_BLKNO; blkno < npages; blkno++)
748 : : {
749 : : Buffer buffer;
750 : : Page page;
751 : :
6557 teodor@sigaev.ru 752 : 4578 : vacuum_delay_point();
753 : :
5644 heikki.linnakangas@i 754 : 4578 : buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
755 : : RBM_NORMAL, info->strategy);
6557 teodor@sigaev.ru 756 : 4578 : LockBuffer(buffer, GIN_SHARE);
2916 kgrittn@postgresql.o 757 : 4578 : page = (Page) BufferGetPage(buffer);
758 : :
1949 akorotkov@postgresql 759 [ + + ]: 4578 : if (GinPageIsRecyclable(page))
760 : : {
4928 tgl@sss.pgh.pa.us 761 [ - + ]: 2330 : Assert(blkno != GIN_ROOT_BLKNO);
5675 heikki.linnakangas@i 762 : 2330 : RecordFreeIndexPage(index, blkno);
6415 tgl@sss.pgh.pa.us 763 : 2330 : totFreePages++;
764 : : }
4928 765 [ + + ]: 2248 : else if (GinPageIsData(page))
766 : : {
767 : 108 : idxStat.nDataPages++;
768 : : }
769 [ + - ]: 2140 : else if (!GinPageIsList(page))
770 : : {
771 : 2140 : idxStat.nEntryPages++;
772 : :
4753 bruce@momjian.us 773 [ + + ]: 2140 : if (GinPageIsLeaf(page))
4928 tgl@sss.pgh.pa.us 774 : 2125 : idxStat.nEntries += PageGetMaxOffsetNumber(page);
775 : : }
776 : :
6557 teodor@sigaev.ru 777 : 4578 : UnlockReleaseBuffer(buffer);
778 : : }
779 : :
780 : : /* Update the metapage with accurate page and entry counts */
4928 tgl@sss.pgh.pa.us 781 : 28 : idxStat.nTotalPages = npages;
1838 heikki.linnakangas@i 782 : 28 : ginUpdateStats(info->index, &idxStat, false);
783 : :
784 : : /* Finally, vacuum the FSM */
5669 785 : 28 : IndexFreeSpaceMapVacuum(info->index);
786 : :
6415 tgl@sss.pgh.pa.us 787 : 28 : stats->pages_free = totFreePages;
788 : :
6557 teodor@sigaev.ru 789 [ + + ]: 28 : if (needLock)
790 : 25 : LockRelationForExtension(index, ExclusiveLock);
791 : 28 : stats->num_pages = RelationGetNumberOfBlocks(index);
792 [ + + ]: 28 : if (needLock)
793 : 25 : UnlockRelationForExtension(index, ExclusiveLock);
794 : :
3010 tgl@sss.pgh.pa.us 795 : 28 : return stats;
796 : : }
797 : :
798 : : /*
799 : : * Return whether Page can safely be recycled.
800 : : */
801 : : bool
1341 andres@anarazel.de 802 : 4631 : GinPageIsRecyclable(Page page)
803 : : {
804 : : TransactionId delete_xid;
805 : :
806 [ - + ]: 4631 : if (PageIsNew(page))
1341 andres@anarazel.de 807 :UBC 0 : return true;
808 : :
1341 andres@anarazel.de 809 [ + + ]:CBC 4631 : if (!GinPageIsDeleted(page))
810 : 2242 : return false;
811 : :
812 : 2389 : delete_xid = GinPageGetDeleteXid(page);
813 : :
814 [ + + ]: 2389 : if (!TransactionIdIsValid(delete_xid))
815 : 2383 : 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 : : }
|