mirror of
				https://github.com/postgres/postgres.git
				synced 2025-10-31 10:30:33 +03:00 
			
		
		
		
	Reported-by: Michael Paquier Discussion: https://postgr.es/m/ZZKTDPxBBMt3C0J9@paquier.xyz Backpatch-through: 12
		
			
				
	
	
		
			218 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			218 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*-------------------------------------------------------------------------
 | |
|  *
 | |
|  * blvacuum.c
 | |
|  *		Bloom VACUUM functions.
 | |
|  *
 | |
|  * Copyright (c) 2016-2024, PostgreSQL Global Development Group
 | |
|  *
 | |
|  * IDENTIFICATION
 | |
|  *	  contrib/bloom/blvacuum.c
 | |
|  *
 | |
|  *-------------------------------------------------------------------------
 | |
|  */
 | |
| #include "postgres.h"
 | |
| 
 | |
| #include "access/genam.h"
 | |
| #include "bloom.h"
 | |
| #include "catalog/storage.h"
 | |
| #include "commands/vacuum.h"
 | |
| #include "miscadmin.h"
 | |
| #include "postmaster/autovacuum.h"
 | |
| #include "storage/bufmgr.h"
 | |
| #include "storage/indexfsm.h"
 | |
| #include "storage/lmgr.h"
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Bulk deletion of all index entries pointing to a set of heap tuples.
 | |
|  * The set of target tuples is specified via a callback routine that tells
 | |
|  * whether any given heap tuple (identified by ItemPointer) is being deleted.
 | |
|  *
 | |
|  * Result: a palloc'd struct containing statistical info for VACUUM displays.
 | |
|  */
 | |
| IndexBulkDeleteResult *
 | |
| blbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 | |
| 			 IndexBulkDeleteCallback callback, void *callback_state)
 | |
| {
 | |
| 	Relation	index = info->index;
 | |
| 	BlockNumber blkno,
 | |
| 				npages;
 | |
| 	FreeBlockNumberArray notFullPage;
 | |
| 	int			countPage = 0;
 | |
| 	BloomState	state;
 | |
| 	Buffer		buffer;
 | |
| 	Page		page;
 | |
| 	BloomMetaPageData *metaData;
 | |
| 	GenericXLogState *gxlogState;
 | |
| 
 | |
| 	if (stats == NULL)
 | |
| 		stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
 | |
| 
 | |
| 	initBloomState(&state, index);
 | |
| 
 | |
| 	/*
 | |
| 	 * Iterate over the pages. We don't care about concurrently added pages,
 | |
| 	 * they can't contain tuples to delete.
 | |
| 	 */
 | |
| 	npages = RelationGetNumberOfBlocks(index);
 | |
| 	for (blkno = BLOOM_HEAD_BLKNO; blkno < npages; blkno++)
 | |
| 	{
 | |
| 		BloomTuple *itup,
 | |
| 				   *itupPtr,
 | |
| 				   *itupEnd;
 | |
| 
 | |
| 		vacuum_delay_point();
 | |
| 
 | |
| 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
 | |
| 									RBM_NORMAL, info->strategy);
 | |
| 
 | |
| 		LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 | |
| 		gxlogState = GenericXLogStart(index);
 | |
| 		page = GenericXLogRegisterBuffer(gxlogState, buffer, 0);
 | |
| 
 | |
| 		/* Ignore empty/deleted pages until blvacuumcleanup() */
 | |
| 		if (PageIsNew(page) || BloomPageIsDeleted(page))
 | |
| 		{
 | |
| 			UnlockReleaseBuffer(buffer);
 | |
| 			GenericXLogAbort(gxlogState);
 | |
| 			continue;
 | |
| 		}
 | |
| 
 | |
| 		/*
 | |
| 		 * Iterate over the tuples.  itup points to current tuple being
 | |
| 		 * scanned, itupPtr points to where to save next non-deleted tuple.
 | |
| 		 */
 | |
| 		itup = itupPtr = BloomPageGetTuple(&state, page, FirstOffsetNumber);
 | |
| 		itupEnd = BloomPageGetTuple(&state, page,
 | |
| 									OffsetNumberNext(BloomPageGetMaxOffset(page)));
 | |
| 		while (itup < itupEnd)
 | |
| 		{
 | |
| 			/* Do we have to delete this tuple? */
 | |
| 			if (callback(&itup->heapPtr, callback_state))
 | |
| 			{
 | |
| 				/* Yes; adjust count of tuples that will be left on page */
 | |
| 				BloomPageGetOpaque(page)->maxoff--;
 | |
| 				stats->tuples_removed += 1;
 | |
| 			}
 | |
| 			else
 | |
| 			{
 | |
| 				/* No; copy it to itupPtr++, but skip copy if not needed */
 | |
| 				if (itupPtr != itup)
 | |
| 					memmove((Pointer) itupPtr, (Pointer) itup,
 | |
| 							state.sizeOfBloomTuple);
 | |
| 				itupPtr = BloomPageGetNextTuple(&state, itupPtr);
 | |
| 			}
 | |
| 
 | |
| 			itup = BloomPageGetNextTuple(&state, itup);
 | |
| 		}
 | |
| 
 | |
| 		/* Assert that we counted correctly */
 | |
| 		Assert(itupPtr == BloomPageGetTuple(&state, page,
 | |
| 											OffsetNumberNext(BloomPageGetMaxOffset(page))));
 | |
| 
 | |
| 		/*
 | |
| 		 * Add page to new notFullPage list if we will not mark page as
 | |
| 		 * deleted and there is free space on it
 | |
| 		 */
 | |
| 		if (BloomPageGetMaxOffset(page) != 0 &&
 | |
| 			BloomPageGetFreeSpace(&state, page) >= state.sizeOfBloomTuple &&
 | |
| 			countPage < BloomMetaBlockN)
 | |
| 			notFullPage[countPage++] = blkno;
 | |
| 
 | |
| 		/* Did we delete something? */
 | |
| 		if (itupPtr != itup)
 | |
| 		{
 | |
| 			/* Is it empty page now? */
 | |
| 			if (BloomPageGetMaxOffset(page) == 0)
 | |
| 				BloomPageSetDeleted(page);
 | |
| 			/* Adjust pd_lower */
 | |
| 			((PageHeader) page)->pd_lower = (Pointer) itupPtr - page;
 | |
| 			/* Finish WAL-logging */
 | |
| 			GenericXLogFinish(gxlogState);
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			/* Didn't change anything: abort WAL-logging */
 | |
| 			GenericXLogAbort(gxlogState);
 | |
| 		}
 | |
| 		UnlockReleaseBuffer(buffer);
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Update the metapage's notFullPage list with whatever we found.  Our
 | |
| 	 * info could already be out of date at this point, but blinsert() will
 | |
| 	 * cope if so.
 | |
| 	 */
 | |
| 	buffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO);
 | |
| 	LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 | |
| 
 | |
| 	gxlogState = GenericXLogStart(index);
 | |
| 	page = GenericXLogRegisterBuffer(gxlogState, buffer, 0);
 | |
| 
 | |
| 	metaData = BloomPageGetMeta(page);
 | |
| 	memcpy(metaData->notFullPage, notFullPage, sizeof(BlockNumber) * countPage);
 | |
| 	metaData->nStart = 0;
 | |
| 	metaData->nEnd = countPage;
 | |
| 
 | |
| 	GenericXLogFinish(gxlogState);
 | |
| 	UnlockReleaseBuffer(buffer);
 | |
| 
 | |
| 	return stats;
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Post-VACUUM cleanup.
 | |
|  *
 | |
|  * Result: a palloc'd struct containing statistical info for VACUUM displays.
 | |
|  */
 | |
| IndexBulkDeleteResult *
 | |
| blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
 | |
| {
 | |
| 	Relation	index = info->index;
 | |
| 	BlockNumber npages,
 | |
| 				blkno;
 | |
| 
 | |
| 	if (info->analyze_only)
 | |
| 		return stats;
 | |
| 
 | |
| 	if (stats == NULL)
 | |
| 		stats = (IndexBulkDeleteResult *) palloc0(sizeof(IndexBulkDeleteResult));
 | |
| 
 | |
| 	/*
 | |
| 	 * Iterate over the pages: insert deleted pages into FSM and collect
 | |
| 	 * statistics.
 | |
| 	 */
 | |
| 	npages = RelationGetNumberOfBlocks(index);
 | |
| 	stats->num_pages = npages;
 | |
| 	stats->pages_free = 0;
 | |
| 	stats->num_index_tuples = 0;
 | |
| 	for (blkno = BLOOM_HEAD_BLKNO; blkno < npages; blkno++)
 | |
| 	{
 | |
| 		Buffer		buffer;
 | |
| 		Page		page;
 | |
| 
 | |
| 		vacuum_delay_point();
 | |
| 
 | |
| 		buffer = ReadBufferExtended(index, MAIN_FORKNUM, blkno,
 | |
| 									RBM_NORMAL, info->strategy);
 | |
| 		LockBuffer(buffer, BUFFER_LOCK_SHARE);
 | |
| 		page = (Page) BufferGetPage(buffer);
 | |
| 
 | |
| 		if (PageIsNew(page) || BloomPageIsDeleted(page))
 | |
| 		{
 | |
| 			RecordFreeIndexPage(index, blkno);
 | |
| 			stats->pages_free++;
 | |
| 		}
 | |
| 		else
 | |
| 		{
 | |
| 			stats->num_index_tuples += BloomPageGetMaxOffset(page);
 | |
| 		}
 | |
| 
 | |
| 		UnlockReleaseBuffer(buffer);
 | |
| 	}
 | |
| 
 | |
| 	IndexFreeSpaceMapVacuum(info->index);
 | |
| 
 | |
| 	return stats;
 | |
| }
 |