Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * blvacuum.c
4 : * Bloom VACUUM functions.
5 : *
6 : * Copyright (c) 2016-2026, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * contrib/bloom/blvacuum.c
10 : *
11 : *-------------------------------------------------------------------------
12 : */
13 : #include "postgres.h"
14 :
15 : #include "access/genam.h"
16 : #include "bloom.h"
17 : #include "commands/vacuum.h"
18 : #include "storage/bufmgr.h"
19 : #include "storage/indexfsm.h"
20 : #include "storage/read_stream.h"
21 :
22 :
23 : /*
24 : * Bulk deletion of all index entries pointing to a set of heap tuples.
25 : * The set of target tuples is specified via a callback routine that tells
26 : * whether any given heap tuple (identified by ItemPointer) is being deleted.
27 : *
28 : * Result: a palloc'd struct containing statistical info for VACUUM displays.
29 : */
30 : IndexBulkDeleteResult *
31 11 : blbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
32 : IndexBulkDeleteCallback callback, void *callback_state)
33 : {
34 11 : Relation index = info->index;
35 : BlockNumber blkno,
36 : npages;
37 : FreeBlockNumberArray notFullPage;
38 11 : int countPage = 0;
39 : BloomState state;
40 : Buffer buffer;
41 : Page page;
42 : BloomMetaPageData *metaData;
43 : GenericXLogState *gxlogState;
44 : BlockRangeReadStreamPrivate p;
45 : ReadStream *stream;
46 :
47 11 : if (stats == NULL)
48 11 : stats = palloc0_object(IndexBulkDeleteResult);
49 :
50 11 : initBloomState(&state, index);
51 :
52 : /*
53 : * Iterate over the pages. We don't care about concurrently added pages,
54 : * they can't contain tuples to delete.
55 : */
56 11 : npages = RelationGetNumberOfBlocks(index);
57 :
58 : /* Scan all blocks except the metapage using streaming reads */
59 11 : p.current_blocknum = BLOOM_HEAD_BLKNO;
60 11 : p.last_exclusive = npages;
61 :
62 : /*
63 : * It is safe to use batchmode as block_range_read_stream_cb takes no
64 : * locks.
65 : */
66 11 : stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
67 : READ_STREAM_FULL |
68 : READ_STREAM_USE_BATCHING,
69 : info->strategy,
70 : index,
71 : MAIN_FORKNUM,
72 : block_range_read_stream_cb,
73 : &p,
74 : 0);
75 :
76 678 : for (blkno = BLOOM_HEAD_BLKNO; blkno < npages; blkno++)
77 : {
78 : BloomTuple *itup,
79 : *itupPtr,
80 : *itupEnd;
81 :
82 667 : vacuum_delay_point(false);
83 :
84 667 : buffer = read_stream_next_buffer(stream, NULL);
85 :
86 667 : LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
87 667 : gxlogState = GenericXLogStart(index);
88 667 : page = GenericXLogRegisterBuffer(gxlogState, buffer, 0);
89 :
90 : /* Ignore empty/deleted pages until blvacuumcleanup() */
91 667 : if (PageIsNew(page) || BloomPageIsDeleted(page))
92 : {
93 4 : UnlockReleaseBuffer(buffer);
94 4 : GenericXLogAbort(gxlogState);
95 4 : continue;
96 : }
97 :
98 : /*
99 : * Iterate over the tuples. itup points to current tuple being
100 : * scanned, itupPtr points to where to save next non-deleted tuple.
101 : */
102 663 : itup = itupPtr = BloomPageGetTuple(&state, page, FirstOffsetNumber);
103 663 : itupEnd = BloomPageGetTuple(&state, page,
104 : OffsetNumberNext(BloomPageGetMaxOffset(page)));
105 336663 : while (itup < itupEnd)
106 : {
107 : /* Do we have to delete this tuple? */
108 336000 : if (callback(&itup->heapPtr, callback_state))
109 : {
110 : /* Yes; adjust count of tuples that will be left on page */
111 48625 : BloomPageGetOpaque(page)->maxoff--;
112 48625 : stats->tuples_removed += 1;
113 : }
114 : else
115 : {
116 : /* No; copy it to itupPtr++, but skip copy if not needed */
117 287375 : if (itupPtr != itup)
118 284079 : memmove(itupPtr, itup, state.sizeOfBloomTuple);
119 287375 : itupPtr = BloomPageGetNextTuple(&state, itupPtr);
120 : }
121 :
122 336000 : itup = BloomPageGetNextTuple(&state, itup);
123 : }
124 :
125 : /* Assert that we counted correctly */
126 : Assert(itupPtr == BloomPageGetTuple(&state, page,
127 : OffsetNumberNext(BloomPageGetMaxOffset(page))));
128 :
129 : /*
130 : * Add page to new notFullPage list if we will not mark page as
131 : * deleted and there is free space on it
132 : */
133 663 : if (BloomPageGetMaxOffset(page) != 0 &&
134 659 : BloomPageGetFreeSpace(&state, page) >= state.sizeOfBloomTuple &&
135 656 : countPage < BloomMetaBlockN)
136 656 : notFullPage[countPage++] = blkno;
137 :
138 : /* Did we delete something? */
139 663 : if (itupPtr != itup)
140 : {
141 : /* Is it empty page now? */
142 659 : if (BloomPageGetMaxOffset(page) == 0)
143 4 : BloomPageSetDeleted(page);
144 : /* Adjust pd_lower */
145 659 : ((PageHeader) page)->pd_lower = (char *) itupPtr - page;
146 : /* Finish WAL-logging */
147 659 : GenericXLogFinish(gxlogState);
148 : }
149 : else
150 : {
151 : /* Didn't change anything: abort WAL-logging */
152 4 : GenericXLogAbort(gxlogState);
153 : }
154 663 : UnlockReleaseBuffer(buffer);
155 : }
156 :
157 : Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
158 11 : read_stream_end(stream);
159 :
160 : /*
161 : * Update the metapage's notFullPage list with whatever we found. Our
162 : * info could already be out of date at this point, but blinsert() will
163 : * cope if so.
164 : */
165 11 : buffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO);
166 11 : LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
167 :
168 11 : gxlogState = GenericXLogStart(index);
169 11 : page = GenericXLogRegisterBuffer(gxlogState, buffer, 0);
170 :
171 11 : metaData = BloomPageGetMeta(page);
172 11 : memcpy(metaData->notFullPage, notFullPage, sizeof(BlockNumber) * countPage);
173 11 : metaData->nStart = 0;
174 11 : metaData->nEnd = countPage;
175 :
176 11 : GenericXLogFinish(gxlogState);
177 11 : UnlockReleaseBuffer(buffer);
178 :
179 11 : return stats;
180 : }
181 :
182 : /*
183 : * Post-VACUUM cleanup.
184 : *
185 : * Result: a palloc'd struct containing statistical info for VACUUM displays.
186 : */
187 : IndexBulkDeleteResult *
188 12 : blvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
189 : {
190 12 : Relation index = info->index;
191 : BlockNumber npages,
192 : blkno;
193 : BlockRangeReadStreamPrivate p;
194 : ReadStream *stream;
195 :
196 12 : if (info->analyze_only)
197 0 : return stats;
198 :
199 12 : if (stats == NULL)
200 1 : stats = palloc0_object(IndexBulkDeleteResult);
201 :
202 : /*
203 : * Iterate over the pages: insert deleted pages into FSM and collect
204 : * statistics.
205 : */
206 12 : npages = RelationGetNumberOfBlocks(index);
207 12 : stats->num_pages = npages;
208 12 : stats->pages_free = 0;
209 12 : stats->num_index_tuples = 0;
210 :
211 : /* Scan all blocks except the metapage using streaming reads */
212 12 : p.current_blocknum = BLOOM_HEAD_BLKNO;
213 12 : p.last_exclusive = npages;
214 :
215 : /*
216 : * It is safe to use batchmode as block_range_read_stream_cb takes no
217 : * locks.
218 : */
219 12 : stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
220 : READ_STREAM_FULL |
221 : READ_STREAM_USE_BATCHING,
222 : info->strategy,
223 : index,
224 : MAIN_FORKNUM,
225 : block_range_read_stream_cb,
226 : &p,
227 : 0);
228 :
229 787 : for (blkno = BLOOM_HEAD_BLKNO; blkno < npages; blkno++)
230 : {
231 : Buffer buffer;
232 : Page page;
233 :
234 775 : vacuum_delay_point(false);
235 :
236 775 : buffer = read_stream_next_buffer(stream, NULL);
237 775 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
238 775 : page = BufferGetPage(buffer);
239 :
240 775 : if (PageIsNew(page) || BloomPageIsDeleted(page))
241 : {
242 8 : RecordFreeIndexPage(index, blkno);
243 8 : stats->pages_free++;
244 : }
245 : else
246 : {
247 767 : stats->num_index_tuples += BloomPageGetMaxOffset(page);
248 : }
249 :
250 775 : UnlockReleaseBuffer(buffer);
251 : }
252 :
253 : Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
254 12 : read_stream_end(stream);
255 :
256 12 : IndexFreeSpaceMapVacuum(info->index);
257 :
258 12 : return stats;
259 : }
|