TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * blinsert.c
4 : * Bloom index build and insert functions.
5 : *
6 : * Copyright (c) 2016-2023, PostgreSQL Global Development Group
7 : *
8 : * IDENTIFICATION
9 : * contrib/bloom/blinsert.c
10 : *
11 : *-------------------------------------------------------------------------
12 : */
13 : #include "postgres.h"
14 :
15 : #include "access/genam.h"
16 : #include "access/generic_xlog.h"
17 : #include "access/tableam.h"
18 : #include "bloom.h"
19 : #include "catalog/index.h"
20 : #include "miscadmin.h"
21 : #include "storage/bufmgr.h"
22 : #include "storage/indexfsm.h"
23 : #include "storage/smgr.h"
24 : #include "utils/memutils.h"
25 : #include "utils/rel.h"
26 :
27 CBC 95 : PG_MODULE_MAGIC;
28 :
29 : /*
30 : * State of bloom index build. We accumulate one page data here before
31 : * flushing it to buffer manager.
32 : */
33 : typedef struct
34 : {
35 : BloomState blstate; /* bloom index state */
36 : int64 indtuples; /* total number of tuples indexed */
37 : MemoryContext tmpCtx; /* temporary memory context reset after each
38 : * tuple */
39 : PGAlignedBlock data; /* cached page */
40 : int count; /* number of tuples in cached page */
41 : } BloomBuildState;
42 :
43 : /*
44 : * Flush page cached in BloomBuildState.
45 : */
46 : static void
47 213 : flushCachedPage(Relation index, BloomBuildState *buildstate)
48 : {
49 : Page page;
50 213 : Buffer buffer = BloomNewBuffer(index);
51 : GenericXLogState *state;
52 :
53 213 : state = GenericXLogStart(index);
54 213 : page = GenericXLogRegisterBuffer(state, buffer, GENERIC_XLOG_FULL_IMAGE);
55 213 : memcpy(page, buildstate->data.data, BLCKSZ);
56 213 : GenericXLogFinish(state);
57 213 : UnlockReleaseBuffer(buffer);
58 213 : }
59 :
60 : /*
61 : * (Re)initialize cached page in BloomBuildState.
62 : */
63 : static void
64 213 : initCachedPage(BloomBuildState *buildstate)
65 : {
66 213 : BloomInitPage(buildstate->data.data, 0);
67 213 : buildstate->count = 0;
68 213 : }
69 :
70 : /*
71 : * Per-tuple callback for table_index_build_scan.
72 : */
73 : static void
74 108758 : bloomBuildCallback(Relation index, ItemPointer tid, Datum *values,
75 : bool *isnull, bool tupleIsAlive, void *state)
76 : {
77 108758 : BloomBuildState *buildstate = (BloomBuildState *) state;
78 : MemoryContext oldCtx;
79 : BloomTuple *itup;
80 :
81 108758 : oldCtx = MemoryContextSwitchTo(buildstate->tmpCtx);
82 :
83 108758 : itup = BloomFormTuple(&buildstate->blstate, tid, values, isnull);
84 :
85 : /* Try to add next item to cached page */
86 108758 : if (BloomPageAddItem(&buildstate->blstate, buildstate->data.data, itup))
87 : {
88 : /* Next item was added successfully */
89 108550 : buildstate->count++;
90 : }
91 : else
92 : {
93 : /* Cached page is full, flush it out and make a new one */
94 208 : flushCachedPage(index, buildstate);
95 :
96 208 : CHECK_FOR_INTERRUPTS();
97 :
98 208 : initCachedPage(buildstate);
99 :
100 208 : if (!BloomPageAddItem(&buildstate->blstate, buildstate->data.data, itup))
101 : {
102 : /* We shouldn't be here since we're inserting to the empty page */
103 UBC 0 : elog(ERROR, "could not add new bloom tuple to empty page");
104 : }
105 :
106 : /* Next item was added successfully */
107 CBC 208 : buildstate->count++;
108 : }
109 :
110 : /* Update total tuple count */
111 108758 : buildstate->indtuples += 1;
112 :
113 108758 : MemoryContextSwitchTo(oldCtx);
114 108758 : MemoryContextReset(buildstate->tmpCtx);
115 108758 : }
116 :
117 : /*
118 : * Build a new bloom index.
119 : */
120 : IndexBuildResult *
121 5 : blbuild(Relation heap, Relation index, IndexInfo *indexInfo)
122 : {
123 : IndexBuildResult *result;
124 : double reltuples;
125 : BloomBuildState buildstate;
126 :
127 5 : if (RelationGetNumberOfBlocks(index) != 0)
128 UBC 0 : elog(ERROR, "index \"%s\" already contains data",
129 : RelationGetRelationName(index));
130 :
131 : /* Initialize the meta page */
132 CBC 5 : BloomInitMetapage(index);
133 :
134 : /* Initialize the bloom build state */
135 5 : memset(&buildstate, 0, sizeof(buildstate));
136 5 : initBloomState(&buildstate.blstate, index);
137 5 : buildstate.tmpCtx = AllocSetContextCreate(CurrentMemoryContext,
138 : "Bloom build temporary context",
139 : ALLOCSET_DEFAULT_SIZES);
140 5 : initCachedPage(&buildstate);
141 :
142 : /* Do the heap scan */
143 5 : reltuples = table_index_build_scan(heap, index, indexInfo, true, true,
144 : bloomBuildCallback, (void *) &buildstate,
145 : NULL);
146 :
147 : /* Flush last page if needed (it will be, unless heap was empty) */
148 5 : if (buildstate.count > 0)
149 5 : flushCachedPage(index, &buildstate);
150 :
151 5 : MemoryContextDelete(buildstate.tmpCtx);
152 :
153 5 : result = (IndexBuildResult *) palloc(sizeof(IndexBuildResult));
154 5 : result->heap_tuples = reltuples;
155 5 : result->index_tuples = buildstate.indtuples;
156 :
157 5 : return result;
158 : }
159 :
160 : /*
161 : * Build an empty bloom index in the initialization fork.
162 : */
163 : void
164 1 : blbuildempty(Relation index)
165 : {
166 : Page metapage;
167 :
168 : /* Construct metapage. */
169 GNC 1 : metapage = (Page) palloc_aligned(BLCKSZ, PG_IO_ALIGN_SIZE, 0);
170 CBC 1 : BloomFillMetapage(index, metapage);
171 :
172 : /*
173 : * Write the page and log it. It might seem that an immediate sync would
174 : * be sufficient to guarantee that the file exists on disk, but recovery
175 : * itself might remove it while replaying, for example, an
176 : * XLOG_DBASE_CREATE* or XLOG_TBLSPC_CREATE record. Therefore, we need
177 : * this even when wal_level=minimal.
178 : */
179 1 : PageSetChecksumInplace(metapage, BLOOM_METAPAGE_BLKNO);
180 1 : smgrwrite(RelationGetSmgr(index), INIT_FORKNUM, BLOOM_METAPAGE_BLKNO,
181 : metapage, true);
182 GNC 1 : log_newpage(&(RelationGetSmgr(index))->smgr_rlocator.locator, INIT_FORKNUM,
183 : BLOOM_METAPAGE_BLKNO, metapage, true);
184 :
185 : /*
186 : * An immediate sync is required even if we xlog'd the page, because the
187 : * write did not go through shared_buffers and therefore a concurrent
188 : * checkpoint may have moved the redo pointer past our xlog record.
189 : */
190 CBC 1 : smgrimmedsync(RelationGetSmgr(index), INIT_FORKNUM);
191 1 : }
192 :
193 : /*
194 : * Insert new tuple to the bloom index.
195 : */
196 : bool
197 104000 : blinsert(Relation index, Datum *values, bool *isnull,
198 : ItemPointer ht_ctid, Relation heapRel,
199 : IndexUniqueCheck checkUnique,
200 : bool indexUnchanged,
201 : IndexInfo *indexInfo)
202 : {
203 : BloomState blstate;
204 : BloomTuple *itup;
205 : MemoryContext oldCtx;
206 : MemoryContext insertCtx;
207 : BloomMetaPageData *metaData;
208 : Buffer buffer,
209 : metaBuffer;
210 : Page page,
211 : metaPage;
212 104000 : BlockNumber blkno = InvalidBlockNumber;
213 : OffsetNumber nStart;
214 : GenericXLogState *state;
215 :
216 104000 : insertCtx = AllocSetContextCreate(CurrentMemoryContext,
217 : "Bloom insert temporary context",
218 : ALLOCSET_DEFAULT_SIZES);
219 :
220 104000 : oldCtx = MemoryContextSwitchTo(insertCtx);
221 :
222 104000 : initBloomState(&blstate, index);
223 104000 : itup = BloomFormTuple(&blstate, ht_ctid, values, isnull);
224 :
225 : /*
226 : * At first, try to insert new tuple to the first page in notFullPage
227 : * array. If successful, we don't need to modify the meta page.
228 : */
229 104000 : metaBuffer = ReadBuffer(index, BLOOM_METAPAGE_BLKNO);
230 104000 : LockBuffer(metaBuffer, BUFFER_LOCK_SHARE);
231 104000 : metaData = BloomPageGetMeta(BufferGetPage(metaBuffer));
232 :
233 104000 : if (metaData->nEnd > metaData->nStart)
234 : {
235 GIC 103999 : blkno = metaData->notFullPage[metaData->nStart];
236 103999 : Assert(blkno != InvalidBlockNumber);
237 ECB :
238 : /* Don't hold metabuffer lock while doing insert */
239 CBC 103999 : LockBuffer(metaBuffer, BUFFER_LOCK_UNLOCK);
240 ECB :
241 GIC 103999 : buffer = ReadBuffer(index, blkno);
242 CBC 103999 : LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
243 ECB :
244 GIC 103999 : state = GenericXLogStart(index);
245 103999 : page = GenericXLogRegisterBuffer(state, buffer, 0);
246 :
247 : /*
248 : * We might have found a page that was recently deleted by VACUUM. If
249 ECB : * so, we can reuse it, but we must reinitialize it.
250 EUB : */
251 GIC 103999 : if (PageIsNew(page) || BloomPageIsDeleted(page))
252 LBC 0 : BloomInitPage(page, 0);
253 :
254 GIC 103999 : if (BloomPageAddItem(&blstate, page, itup))
255 ECB : {
256 : /* Success! Apply the change, clean up, and exit */
257 CBC 102712 : GenericXLogFinish(state);
258 102712 : UnlockReleaseBuffer(buffer);
259 102712 : ReleaseBuffer(metaBuffer);
260 102712 : MemoryContextSwitchTo(oldCtx);
261 GIC 102712 : MemoryContextDelete(insertCtx);
262 102712 : return false;
263 : }
264 ECB :
265 : /* Didn't fit, must try other pages */
266 GIC 1287 : GenericXLogAbort(state);
267 1287 : UnlockReleaseBuffer(buffer);
268 : }
269 : else
270 ECB : {
271 : /* No entries in notFullPage */
272 GIC 1 : LockBuffer(metaBuffer, BUFFER_LOCK_UNLOCK);
273 : }
274 :
275 : /*
276 : * Try other pages in notFullPage array. We will have to change nStart in
277 ECB : * metapage. Thus, grab exclusive lock on metapage.
278 : */
279 GIC 1288 : LockBuffer(metaBuffer, BUFFER_LOCK_EXCLUSIVE);
280 ECB :
281 : /* nStart might have changed while we didn't have lock */
282 GIC 1288 : nStart = metaData->nStart;
283 ECB :
284 : /* Skip first page if we already tried it above */
285 CBC 1288 : if (nStart < metaData->nEnd &&
286 GIC 1287 : blkno == metaData->notFullPage[nStart])
287 1287 : nStart++;
288 :
289 : /*
290 : * This loop iterates for each page we try from the notFullPage array, and
291 : * will also initialize a GenericXLogState for the fallback case of having
292 : * to allocate a new page.
293 : */
294 ECB : for (;;)
295 : {
296 GIC 1288 : state = GenericXLogStart(index);
297 ECB :
298 : /* get modifiable copy of metapage */
299 GIC 1288 : metaPage = GenericXLogRegisterBuffer(state, metaBuffer, 0);
300 CBC 1288 : metaData = BloomPageGetMeta(metaPage);
301 ECB :
302 GIC 1288 : if (nStart >= metaData->nEnd)
303 CBC 5 : break; /* no more entries in notFullPage array */
304 ECB :
305 GIC 1283 : blkno = metaData->notFullPage[nStart];
306 CBC 1283 : Assert(blkno != InvalidBlockNumber);
307 ECB :
308 CBC 1283 : buffer = ReadBuffer(index, blkno);
309 GIC 1283 : LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
310 1283 : page = GenericXLogRegisterBuffer(state, buffer, 0);
311 ECB :
312 EUB : /* Basically same logic as above */
313 GIC 1283 : if (PageIsNew(page) || BloomPageIsDeleted(page))
314 LBC 0 : BloomInitPage(page, 0);
315 :
316 GIC 1283 : if (BloomPageAddItem(&blstate, page, itup))
317 ECB : {
318 : /* Success! Apply the changes, clean up, and exit */
319 CBC 1283 : metaData->nStart = nStart;
320 1283 : GenericXLogFinish(state);
321 1283 : UnlockReleaseBuffer(buffer);
322 1283 : UnlockReleaseBuffer(metaBuffer);
323 1283 : MemoryContextSwitchTo(oldCtx);
324 GIC 1283 : MemoryContextDelete(insertCtx);
325 1283 : return false;
326 : }
327 EUB :
328 : /* Didn't fit, must try other pages */
329 UBC 0 : GenericXLogAbort(state);
330 UIC 0 : UnlockReleaseBuffer(buffer);
331 0 : nStart++;
332 : }
333 :
334 : /*
335 : * Didn't find place to insert in notFullPage array. Allocate new page.
336 ECB : * (XXX is it good to do this while holding ex-lock on the metapage??)
337 : */
338 CBC 5 : buffer = BloomNewBuffer(index);
339 ECB :
340 GIC 5 : page = GenericXLogRegisterBuffer(state, buffer, GENERIC_XLOG_FULL_IMAGE);
341 CBC 5 : BloomInitPage(page, 0);
342 :
343 GIC 5 : if (!BloomPageAddItem(&blstate, page, itup))
344 EUB : {
345 : /* We shouldn't be here since we're inserting to an empty page */
346 UIC 0 : elog(ERROR, "could not add new bloom tuple to empty page");
347 : }
348 ECB :
349 : /* Reset notFullPage array to contain just this new page */
350 CBC 5 : metaData->nStart = 0;
351 GIC 5 : metaData->nEnd = 1;
352 5 : metaData->notFullPage[0] = BufferGetBlockNumber(buffer);
353 ECB :
354 : /* Apply the changes, clean up, and exit */
355 CBC 5 : GenericXLogFinish(state);
356 ECB :
357 GIC 5 : UnlockReleaseBuffer(buffer);
358 CBC 5 : UnlockReleaseBuffer(metaBuffer);
359 ECB :
360 GIC 5 : MemoryContextSwitchTo(oldCtx);
361 CBC 5 : MemoryContextDelete(insertCtx);
362 :
363 GIC 5 : return false;
364 : }
|