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