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