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