LCOV - code coverage report
Current view: top level - src/backend/access/gin - ginvacuum.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 91.5 % 295 270
Test Date: 2026-09-04 06:15:53 Functions: 100.0 % 10 10
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 77.5 % 142 110

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

Generated by: LCOV version 2.0-1