Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * ginfast.c
4 : : * Fast insert routines for the Postgres inverted index access method.
5 : : * Pending entries are stored in linear list of pages. Later on
6 : : * (typically during VACUUM), ginInsertCleanup() will be invoked to
7 : : * transfer pending entries into the regular index structure. This
8 : : * wins because bulk insertion is much more efficient than retail.
9 : : *
10 : : * Portions Copyright (c) 1996-2024, PostgreSQL Global Development Group
11 : : * Portions Copyright (c) 1994, Regents of the University of California
12 : : *
13 : : * IDENTIFICATION
14 : : * src/backend/access/gin/ginfast.c
15 : : *
16 : : *-------------------------------------------------------------------------
17 : : */
18 : :
19 : : #include "postgres.h"
20 : :
21 : : #include "access/gin_private.h"
22 : : #include "access/ginxlog.h"
23 : : #include "access/xlog.h"
24 : : #include "access/xloginsert.h"
25 : : #include "catalog/pg_am.h"
26 : : #include "commands/vacuum.h"
27 : : #include "miscadmin.h"
28 : : #include "port/pg_bitutils.h"
29 : : #include "postmaster/autovacuum.h"
30 : : #include "storage/indexfsm.h"
31 : : #include "storage/lmgr.h"
32 : : #include "storage/predicate.h"
33 : : #include "utils/acl.h"
34 : : #include "utils/fmgrprotos.h"
35 : : #include "utils/memutils.h"
36 : : #include "utils/rel.h"
37 : :
38 : : /* GUC parameter */
39 : : int gin_pending_list_limit = 0;
40 : :
41 : : #define GIN_PAGE_FREESIZE \
42 : : ( BLCKSZ - MAXALIGN(SizeOfPageHeaderData) - MAXALIGN(sizeof(GinPageOpaqueData)) )
43 : :
44 : : typedef struct KeyArray
45 : : {
46 : : Datum *keys; /* expansible array */
47 : : GinNullCategory *categories; /* another expansible array */
48 : : int32 nvalues; /* current number of valid entries */
49 : : int32 maxvalues; /* allocated size of arrays */
50 : : } KeyArray;
51 : :
52 : :
53 : : /*
54 : : * Build a pending-list page from the given array of tuples, and write it out.
55 : : *
56 : : * Returns amount of free space left on the page.
57 : : */
58 : : static int32
5500 tgl@sss.pgh.pa.us 59 :CBC 1442 : writeListPage(Relation index, Buffer buffer,
60 : : IndexTuple *tuples, int32 ntuples, BlockNumber rightlink)
61 : : {
2916 kgrittn@postgresql.o 62 : 1442 : Page page = BufferGetPage(buffer);
63 : : int32 i,
64 : : freesize,
5421 bruce@momjian.us 65 : 1442 : size = 0;
66 : : OffsetNumber l,
67 : : off;
68 : : PGAlignedBlock workspace;
69 : : char *ptr;
70 : :
5500 tgl@sss.pgh.pa.us 71 : 1442 : START_CRIT_SECTION();
72 : :
73 : 1442 : GinInitBuffer(buffer, GIN_LIST);
74 : :
75 : 1442 : off = FirstOffsetNumber;
2052 76 : 1442 : ptr = workspace.data;
77 : :
5421 bruce@momjian.us 78 [ + + ]: 8420 : for (i = 0; i < ntuples; i++)
79 : : {
80 : 6978 : int this_size = IndexTupleSize(tuples[i]);
81 : :
5500 tgl@sss.pgh.pa.us 82 : 6978 : memcpy(ptr, tuples[i], this_size);
83 : 6978 : ptr += this_size;
84 : 6978 : size += this_size;
85 : :
5421 bruce@momjian.us 86 : 6978 : l = PageAddItem(page, (Item) tuples[i], this_size, off, false, false);
87 : :
5500 tgl@sss.pgh.pa.us 88 [ - + ]: 6978 : if (l == InvalidOffsetNumber)
5500 tgl@sss.pgh.pa.us 89 [ # # ]:UBC 0 : elog(ERROR, "failed to add item to index page in \"%s\"",
90 : : RelationGetRelationName(index));
91 : :
5500 tgl@sss.pgh.pa.us 92 :CBC 6978 : off++;
93 : : }
94 : :
95 [ - + ]: 1442 : Assert(size <= BLCKSZ); /* else we overran workspace */
96 : :
97 : 1442 : GinPageGetOpaque(page)->rightlink = rightlink;
98 : :
99 : : /*
100 : : * tail page may contain only whole row(s) or final part of row placed on
101 : : * previous pages (a "row" here meaning all the index tuples generated for
102 : : * one heap tuple)
103 : : */
5421 bruce@momjian.us 104 [ + - ]: 1442 : if (rightlink == InvalidBlockNumber)
105 : : {
5500 tgl@sss.pgh.pa.us 106 : 1442 : GinPageSetFullRow(page);
107 : 1442 : GinPageGetOpaque(page)->maxoff = 1;
108 : : }
109 : : else
110 : : {
5500 tgl@sss.pgh.pa.us 111 :UBC 0 : GinPageGetOpaque(page)->maxoff = 0;
112 : : }
113 : :
5500 tgl@sss.pgh.pa.us 114 :CBC 1442 : MarkBufferDirty(buffer);
115 : :
4871 rhaas@postgresql.org 116 [ + + + + : 1442 : if (RelationNeedsWAL(index))
+ - + - ]
117 : : {
118 : : ginxlogInsertListPage data;
119 : : XLogRecPtr recptr;
120 : :
5325 tgl@sss.pgh.pa.us 121 : 550 : data.rightlink = rightlink;
122 : 550 : data.ntuples = ntuples;
123 : :
3433 heikki.linnakangas@i 124 : 550 : XLogBeginInsert();
125 : 550 : XLogRegisterData((char *) &data, sizeof(ginxlogInsertListPage));
126 : :
127 : 550 : XLogRegisterBuffer(0, buffer, REGBUF_WILL_INIT);
2052 tgl@sss.pgh.pa.us 128 : 550 : XLogRegisterBufData(0, workspace.data, size);
129 : :
3433 heikki.linnakangas@i 130 : 550 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_INSERT_LISTPAGE);
5500 tgl@sss.pgh.pa.us 131 : 550 : PageSetLSN(page, recptr);
132 : : }
133 : :
134 : : /* get free space before releasing buffer */
5325 135 : 1442 : freesize = PageGetExactFreeSpace(page);
136 : :
5500 137 : 1442 : UnlockReleaseBuffer(buffer);
138 : :
139 [ - + ]: 1442 : END_CRIT_SECTION();
140 : :
141 : 1442 : return freesize;
142 : : }
143 : :
144 : : static void
145 : 1442 : makeSublist(Relation index, IndexTuple *tuples, int32 ntuples,
146 : : GinMetaPageData *res)
147 : : {
5421 bruce@momjian.us 148 : 1442 : Buffer curBuffer = InvalidBuffer;
149 : 1442 : Buffer prevBuffer = InvalidBuffer;
150 : : int i,
151 : 1442 : size = 0,
152 : : tupsize;
153 : 1442 : int startTuple = 0;
154 : :
5500 tgl@sss.pgh.pa.us 155 [ - + ]: 1442 : Assert(ntuples > 0);
156 : :
157 : : /*
158 : : * Split tuples into pages
159 : : */
5421 bruce@momjian.us 160 [ + + ]: 8420 : for (i = 0; i < ntuples; i++)
161 : : {
162 [ + + ]: 6978 : if (curBuffer == InvalidBuffer)
163 : : {
5500 tgl@sss.pgh.pa.us 164 : 1442 : curBuffer = GinNewBuffer(index);
165 : :
5421 bruce@momjian.us 166 [ - + ]: 1442 : if (prevBuffer != InvalidBuffer)
167 : : {
5500 tgl@sss.pgh.pa.us 168 :UBC 0 : res->nPendingPages++;
169 : 0 : writeListPage(index, prevBuffer,
5325 170 : 0 : tuples + startTuple,
171 : : i - startTuple,
172 : : BufferGetBlockNumber(curBuffer));
173 : : }
174 : : else
175 : : {
5500 tgl@sss.pgh.pa.us 176 :CBC 1442 : res->head = BufferGetBlockNumber(curBuffer);
177 : : }
178 : :
179 : 1442 : prevBuffer = curBuffer;
180 : 1442 : startTuple = i;
181 : 1442 : size = 0;
182 : : }
183 : :
184 : 6978 : tupsize = MAXALIGN(IndexTupleSize(tuples[i])) + sizeof(ItemIdData);
185 : :
5325 186 [ - + ]: 6978 : if (size + tupsize > GinListPageSize)
187 : : {
188 : : /* won't fit, force a new page and reprocess */
5500 tgl@sss.pgh.pa.us 189 :UBC 0 : i--;
190 : 0 : curBuffer = InvalidBuffer;
191 : : }
192 : : else
193 : : {
5500 tgl@sss.pgh.pa.us 194 :CBC 6978 : size += tupsize;
195 : : }
196 : : }
197 : :
198 : : /*
199 : : * Write last page
200 : : */
201 : 1442 : res->tail = BufferGetBlockNumber(curBuffer);
202 : 2884 : res->tailFreeSize = writeListPage(index, curBuffer,
5325 203 : 1442 : tuples + startTuple,
204 : : ntuples - startTuple,
205 : : InvalidBlockNumber);
5500 206 : 1442 : res->nPendingPages++;
207 : : /* that was only one heap tuple */
208 : 1442 : res->nPendingHeapTuples = 1;
209 : 1442 : }
210 : :
211 : : /*
212 : : * Write the index tuples contained in *collector into the index's
213 : : * pending list.
214 : : *
215 : : * Function guarantees that all these tuples will be inserted consecutively,
216 : : * preserving order
217 : : */
218 : : void
4846 219 : 132435 : ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
220 : : {
221 : 132435 : Relation index = ginstate->index;
222 : : Buffer metabuffer;
223 : : Page metapage;
5421 bruce@momjian.us 224 : 132435 : GinMetaPageData *metadata = NULL;
225 : 132435 : Buffer buffer = InvalidBuffer;
226 : 132435 : Page page = NULL;
227 : : ginxlogUpdateMeta data;
228 : 132435 : bool separateList = false;
229 : 132435 : bool needCleanup = false;
230 : : int cleanupSize;
231 : : bool needWal;
232 : :
233 [ - + ]: 132435 : if (collector->ntuples == 0)
5500 tgl@sss.pgh.pa.us 234 :UBC 0 : return;
235 : :
3433 heikki.linnakangas@i 236 [ + + + + :CBC 132435 : needWal = RelationNeedsWAL(index);
+ - + - ]
237 : :
648 rhaas@postgresql.org 238 : 132435 : data.locator = index->rd_locator;
5500 tgl@sss.pgh.pa.us 239 : 132435 : data.ntuples = 0;
240 : 132435 : data.newRightlink = data.prevTail = InvalidBlockNumber;
241 : :
242 : 132435 : metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
2916 kgrittn@postgresql.o 243 : 132435 : metapage = BufferGetPage(metabuffer);
244 : :
245 : : /*
246 : : * An insertion to the pending list could logically belong anywhere in the
247 : : * tree, so it conflicts with all serializable scans. All scans acquire a
248 : : * predicate lock on the metabuffer to represent that. Therefore we'll
249 : : * check for conflicts in, but not until we have the page locked and are
250 : : * ready to modify the page.
251 : : */
252 : :
5325 tgl@sss.pgh.pa.us 253 [ - + ]: 132435 : if (collector->sumsize + collector->ntuples * sizeof(ItemIdData) > GinListPageSize)
254 : : {
255 : : /*
256 : : * Total size is greater than one page => make sublist
257 : : */
5500 tgl@sss.pgh.pa.us 258 :UBC 0 : separateList = true;
259 : : }
260 : : else
261 : : {
5500 tgl@sss.pgh.pa.us 262 :CBC 132435 : LockBuffer(metabuffer, GIN_EXCLUSIVE);
263 : 132435 : metadata = GinPageGetMeta(metapage);
264 : :
5421 bruce@momjian.us 265 [ + + ]: 132435 : if (metadata->head == InvalidBlockNumber ||
266 [ + + ]: 132406 : collector->sumsize + collector->ntuples * sizeof(ItemIdData) > metadata->tailFreeSize)
267 : : {
268 : : /*
269 : : * Pending list is empty or total size is greater than freespace
270 : : * on tail page => make sublist
271 : : *
272 : : * We unlock metabuffer to keep high concurrency
273 : : */
5500 tgl@sss.pgh.pa.us 274 : 1442 : separateList = true;
275 : 1442 : LockBuffer(metabuffer, GIN_UNLOCK);
276 : : }
277 : : }
278 : :
5421 bruce@momjian.us 279 [ + + ]: 132435 : if (separateList)
280 : : {
281 : : /*
282 : : * We should make sublist separately and append it to the tail
283 : : */
284 : : GinMetaPageData sublist;
285 : :
5325 tgl@sss.pgh.pa.us 286 : 1442 : memset(&sublist, 0, sizeof(GinMetaPageData));
5500 287 : 1442 : makeSublist(index, collector->tuples, collector->ntuples, &sublist);
288 : :
289 : : /*
290 : : * metapage was unlocked, see above
291 : : */
292 : 1442 : LockBuffer(metabuffer, GIN_EXCLUSIVE);
293 : 1442 : metadata = GinPageGetMeta(metapage);
294 : :
286 tmunro@postgresql.or 295 : 1442 : CheckForSerializableConflictIn(index, NULL, GIN_METAPAGE_BLKNO);
296 : :
5421 bruce@momjian.us 297 [ + + ]: 1439 : if (metadata->head == InvalidBlockNumber)
298 : : {
299 : : /*
300 : : * Main list is empty, so just insert sublist as main list
301 : : */
5500 tgl@sss.pgh.pa.us 302 : 26 : START_CRIT_SECTION();
303 : :
4846 304 : 26 : metadata->head = sublist.head;
305 : 26 : metadata->tail = sublist.tail;
306 : 26 : metadata->tailFreeSize = sublist.tailFreeSize;
307 : :
308 : 26 : metadata->nPendingPages = sublist.nPendingPages;
309 : 26 : metadata->nPendingHeapTuples = sublist.nPendingHeapTuples;
310 : :
549 michael@paquier.xyz 311 [ + + ]: 26 : if (needWal)
312 : 16 : XLogBeginInsert();
313 : : }
314 : : else
315 : : {
316 : : /*
317 : : * Merge lists
318 : : */
5500 tgl@sss.pgh.pa.us 319 : 1413 : data.prevTail = metadata->tail;
5325 320 : 1413 : data.newRightlink = sublist.head;
321 : :
5500 322 : 1413 : buffer = ReadBuffer(index, metadata->tail);
323 : 1413 : LockBuffer(buffer, GIN_EXCLUSIVE);
2916 kgrittn@postgresql.o 324 : 1413 : page = BufferGetPage(buffer);
325 : :
5500 tgl@sss.pgh.pa.us 326 [ - + ]: 1413 : Assert(GinPageGetOpaque(page)->rightlink == InvalidBlockNumber);
327 : :
328 : 1413 : START_CRIT_SECTION();
329 : :
330 : 1413 : GinPageGetOpaque(page)->rightlink = sublist.head;
331 : :
5325 332 : 1413 : MarkBufferDirty(buffer);
333 : :
5500 334 : 1413 : metadata->tail = sublist.tail;
335 : 1413 : metadata->tailFreeSize = sublist.tailFreeSize;
336 : :
337 : 1413 : metadata->nPendingPages += sublist.nPendingPages;
338 : 1413 : metadata->nPendingHeapTuples += sublist.nPendingHeapTuples;
339 : :
3433 heikki.linnakangas@i 340 [ + + ]: 1413 : if (needWal)
341 : : {
549 michael@paquier.xyz 342 : 531 : XLogBeginInsert();
3433 heikki.linnakangas@i 343 : 531 : XLogRegisterBuffer(1, buffer, REGBUF_STANDARD);
344 : : }
345 : : }
346 : : }
347 : : else
348 : : {
349 : : /*
350 : : * Insert into tail page. Metapage is already locked
351 : : */
352 : : OffsetNumber l,
353 : : off;
354 : : int i,
355 : : tupsize;
356 : : char *ptr;
357 : : char *collectordata;
358 : :
286 tmunro@postgresql.or 359 : 130993 : CheckForSerializableConflictIn(index, NULL, GIN_METAPAGE_BLKNO);
360 : :
5500 tgl@sss.pgh.pa.us 361 : 130993 : buffer = ReadBuffer(index, metadata->tail);
362 : 130993 : LockBuffer(buffer, GIN_EXCLUSIVE);
2916 kgrittn@postgresql.o 363 : 130993 : page = BufferGetPage(buffer);
364 : :
5500 tgl@sss.pgh.pa.us 365 [ + - ]: 130993 : off = (PageIsEmpty(page)) ? FirstOffsetNumber :
5421 bruce@momjian.us 366 : 130993 : OffsetNumberNext(PageGetMaxOffsetNumber(page));
367 : :
3433 heikki.linnakangas@i 368 : 130993 : collectordata = ptr = (char *) palloc(collector->sumsize);
369 : :
5500 tgl@sss.pgh.pa.us 370 : 130993 : data.ntuples = collector->ntuples;
371 : :
549 michael@paquier.xyz 372 : 130993 : START_CRIT_SECTION();
373 : :
3433 heikki.linnakangas@i 374 [ + + ]: 130993 : if (needWal)
375 : 71838 : XLogBeginInsert();
376 : :
377 : : /*
378 : : * Increase counter of heap tuples
379 : : */
5421 bruce@momjian.us 380 [ - + ]: 130993 : Assert(GinPageGetOpaque(page)->maxoff <= metadata->nPendingHeapTuples);
5500 tgl@sss.pgh.pa.us 381 : 130993 : GinPageGetOpaque(page)->maxoff++;
382 : 130993 : metadata->nPendingHeapTuples++;
383 : :
5421 bruce@momjian.us 384 [ + + ]: 702694 : for (i = 0; i < collector->ntuples; i++)
385 : : {
5500 tgl@sss.pgh.pa.us 386 : 571701 : tupsize = IndexTupleSize(collector->tuples[i]);
5421 bruce@momjian.us 387 : 571701 : l = PageAddItem(page, (Item) collector->tuples[i], tupsize, off, false, false);
388 : :
5500 tgl@sss.pgh.pa.us 389 [ - + ]: 571701 : if (l == InvalidOffsetNumber)
5500 tgl@sss.pgh.pa.us 390 [ # # ]:UBC 0 : elog(ERROR, "failed to add item to index page in \"%s\"",
391 : : RelationGetRelationName(index));
392 : :
5500 tgl@sss.pgh.pa.us 393 :CBC 571701 : memcpy(ptr, collector->tuples[i], tupsize);
5421 bruce@momjian.us 394 : 571701 : ptr += tupsize;
395 : :
5500 tgl@sss.pgh.pa.us 396 : 571701 : off++;
397 : : }
398 : :
3433 heikki.linnakangas@i 399 [ - + ]: 130993 : Assert((ptr - collectordata) <= collector->sumsize);
400 : :
174 jdavis@postgresql.or 401 :GNC 130993 : MarkBufferDirty(buffer);
402 : :
3433 heikki.linnakangas@i 403 [ + + ]:CBC 130993 : if (needWal)
404 : : {
405 : 71838 : XLogRegisterBuffer(1, buffer, REGBUF_STANDARD);
406 : 71838 : XLogRegisterBufData(1, collectordata, collector->sumsize);
407 : : }
408 : :
5325 tgl@sss.pgh.pa.us 409 : 130993 : metadata->tailFreeSize = PageGetExactFreeSpace(page);
410 : : }
411 : :
412 : : /*
413 : : * Set pd_lower just past the end of the metadata. This is essential,
414 : : * because without doing so, metadata will be lost if xlog.c compresses
415 : : * the page. (We must do this here because pre-v11 versions of PG did not
416 : : * set the metapage's pd_lower correctly, so a pg_upgraded index might
417 : : * contain the wrong value.)
418 : : */
2355 419 : 132432 : ((PageHeader) metapage)->pd_lower =
420 : 132432 : ((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage;
421 : :
422 : : /*
423 : : * Write metabuffer, make xlog entry
424 : : */
5500 425 : 132432 : MarkBufferDirty(metabuffer);
426 : :
3433 heikki.linnakangas@i 427 [ + + ]: 132432 : if (needWal)
428 : : {
429 : : XLogRecPtr recptr;
430 : :
5325 tgl@sss.pgh.pa.us 431 : 72385 : memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
432 : :
2355 433 : 72385 : XLogRegisterBuffer(0, metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);
3433 heikki.linnakangas@i 434 : 72385 : XLogRegisterData((char *) &data, sizeof(ginxlogUpdateMeta));
435 : :
436 : 72385 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE);
5500 tgl@sss.pgh.pa.us 437 : 72385 : PageSetLSN(metapage, recptr);
438 : :
5421 bruce@momjian.us 439 [ + + ]: 72385 : if (buffer != InvalidBuffer)
440 : : {
5500 tgl@sss.pgh.pa.us 441 : 72369 : PageSetLSN(page, recptr);
442 : : }
443 : : }
444 : :
445 [ + + ]: 132432 : if (buffer != InvalidBuffer)
446 : 132406 : UnlockReleaseBuffer(buffer);
447 : :
448 : : /*
449 : : * Force pending list cleanup when it becomes too long. And,
450 : : * ginInsertCleanup could take significant amount of time, so we prefer to
451 : : * call it when it can do all the work in a single collection cycle. In
452 : : * non-vacuum mode, it shouldn't require maintenance_work_mem, so fire it
453 : : * while pending list is still small enough to fit into
454 : : * gin_pending_list_limit.
455 : : *
456 : : * ginInsertCleanup() should not be called inside our CRIT_SECTION.
457 : : */
3442 fujii@postgresql.org 458 [ + - - + : 132432 : cleanupSize = GinGetPendingListCleanupSize(index);
+ + + - ]
459 [ - + ]: 132432 : if (metadata->nPendingPages * GIN_PAGE_FREESIZE > cleanupSize * 1024L)
5500 tgl@sss.pgh.pa.us 460 :UBC 0 : needCleanup = true;
461 : :
5500 tgl@sss.pgh.pa.us 462 :CBC 132432 : UnlockReleaseBuffer(metabuffer);
463 : :
464 [ - + ]: 132432 : END_CRIT_SECTION();
465 : :
466 : : /*
467 : : * Since it could contend with concurrent cleanup process we cleanup
468 : : * pending list not forcibly.
469 : : */
5421 bruce@momjian.us 470 [ - + ]: 132432 : if (needCleanup)
2341 rhaas@postgresql.org 471 :UBC 0 : ginInsertCleanup(ginstate, false, true, false, NULL);
472 : : }
473 : :
474 : : /*
475 : : * Create temporary index tuples for a single indexable item (one index column
476 : : * for the heap tuple specified by ht_ctid), and append them to the array
477 : : * in *collector. They will subsequently be written out using
478 : : * ginHeapTupleFastInsert. Note that to guarantee consistent state, all
479 : : * temp tuples for a given heap tuple must be written in one call to
480 : : * ginHeapTupleFastInsert.
481 : : */
482 : : void
4846 tgl@sss.pgh.pa.us 483 :CBC 192474 : ginHeapTupleFastCollect(GinState *ginstate,
484 : : GinTupleCollector *collector,
485 : : OffsetNumber attnum, Datum value, bool isNull,
486 : : ItemPointer ht_ctid)
487 : : {
488 : : Datum *entries;
489 : : GinNullCategory *categories;
490 : : int32 i,
491 : : nentries;
492 : :
493 : : /*
494 : : * Extract the key values that need to be inserted in the index
495 : : */
496 : 192474 : entries = ginExtractEntries(ginstate, attnum, value, isNull,
497 : : &nentries, &categories);
498 : :
499 : : /*
500 : : * Protect against integer overflow in allocation calculations
501 : : */
1943 502 [ + - ]: 192474 : if (nentries < 0 ||
503 [ - + ]: 192474 : collector->ntuples + nentries > MaxAllocSize / sizeof(IndexTuple))
1943 tgl@sss.pgh.pa.us 504 [ # # ]:UBC 0 : elog(ERROR, "too many entries for GIN index");
505 : :
506 : : /*
507 : : * Allocate/reallocate memory for storing collected tuples
508 : : */
5421 bruce@momjian.us 509 [ + + ]:CBC 192474 : if (collector->tuples == NULL)
510 : : {
511 : : /*
512 : : * Determine the number of elements to allocate in the tuples array
513 : : * initially. Make it a power of 2 to avoid wasting memory when
514 : : * resizing (since palloc likes powers of 2).
515 : : */
1467 drowley@postgresql.o 516 : 132435 : collector->lentuples = pg_nextpower2_32(Max(16, nentries));
580 peter@eisentraut.org 517 : 132435 : collector->tuples = palloc_array(IndexTuple, collector->lentuples);
518 : : }
1943 tgl@sss.pgh.pa.us 519 [ - + ]: 60039 : else if (collector->lentuples < collector->ntuples + nentries)
520 : : {
521 : : /*
522 : : * Advance lentuples to the next suitable power of 2. This won't
523 : : * overflow, though we could get to a value that exceeds
524 : : * MaxAllocSize/sizeof(IndexTuple), causing an error in repalloc.
525 : : */
1467 drowley@postgresql.o 526 :UBC 0 : collector->lentuples = pg_nextpower2_32(collector->ntuples + nentries);
580 peter@eisentraut.org 527 : 0 : collector->tuples = repalloc_array(collector->tuples,
528 : : IndexTuple, collector->lentuples);
529 : : }
530 : :
531 : : /*
532 : : * Build an index tuple for each key value, and add to array. In pending
533 : : * tuples we just stick the heap TID into t_tid.
534 : : */
5500 tgl@sss.pgh.pa.us 535 [ + + ]:CBC 771153 : for (i = 0; i < nentries; i++)
536 : : {
537 : : IndexTuple itup;
538 : :
4846 539 : 578679 : itup = GinFormTuple(ginstate, attnum, entries[i], categories[i],
540 : : NULL, 0, 0, true);
541 : 578679 : itup->t_tid = *ht_ctid;
542 : 578679 : collector->tuples[collector->ntuples++] = itup;
543 : 578679 : collector->sumsize += IndexTupleSize(itup);
544 : : }
5500 545 : 192474 : }
546 : :
547 : : /*
548 : : * Deletes pending list pages up to (not including) newHead page.
549 : : * If newHead == InvalidBlockNumber then function drops the whole list.
550 : : *
551 : : * metapage is pinned and exclusive-locked throughout this function.
552 : : */
553 : : static void
554 : 18 : shiftList(Relation index, Buffer metabuffer, BlockNumber newHead,
555 : : bool fill_fsm, IndexBulkDeleteResult *stats)
556 : : {
557 : : Page metapage;
558 : : GinMetaPageData *metadata;
559 : : BlockNumber blknoToDelete;
560 : :
2916 kgrittn@postgresql.o 561 : 18 : metapage = BufferGetPage(metabuffer);
5500 tgl@sss.pgh.pa.us 562 : 18 : metadata = GinPageGetMeta(metapage);
563 : 18 : blknoToDelete = metadata->head;
564 : :
565 : : do
566 : : {
567 : : Page page;
568 : : int i;
5421 bruce@momjian.us 569 : 99 : int64 nDeletedHeapTuples = 0;
570 : : ginxlogDeleteListPages data;
571 : : Buffer buffers[GIN_NDELETE_AT_ONCE];
572 : : BlockNumber freespace[GIN_NDELETE_AT_ONCE];
573 : :
5500 tgl@sss.pgh.pa.us 574 : 99 : data.ndeleted = 0;
575 [ + + + + ]: 1530 : while (data.ndeleted < GIN_NDELETE_AT_ONCE && blknoToDelete != newHead)
576 : : {
3142 teodor@sigaev.ru 577 : 1431 : freespace[data.ndeleted] = blknoToDelete;
5421 bruce@momjian.us 578 : 1431 : buffers[data.ndeleted] = ReadBuffer(index, blknoToDelete);
579 : 1431 : LockBuffer(buffers[data.ndeleted], GIN_EXCLUSIVE);
2916 kgrittn@postgresql.o 580 : 1431 : page = BufferGetPage(buffers[data.ndeleted]);
581 : :
5500 tgl@sss.pgh.pa.us 582 : 1431 : data.ndeleted++;
583 : :
2908 teodor@sigaev.ru 584 [ - + ]: 1431 : Assert(!GinPageIsDeleted(page));
585 : :
5500 tgl@sss.pgh.pa.us 586 : 1431 : nDeletedHeapTuples += GinPageGetOpaque(page)->maxoff;
5421 bruce@momjian.us 587 : 1431 : blknoToDelete = GinPageGetOpaque(page)->rightlink;
588 : : }
589 : :
5500 tgl@sss.pgh.pa.us 590 [ + + ]: 99 : if (stats)
591 : 97 : stats->pages_deleted += data.ndeleted;
592 : :
593 : : /*
594 : : * This operation touches an unusually large number of pages, so
595 : : * prepare the XLogInsert machinery for that before entering the
596 : : * critical section.
597 : : */
3432 heikki.linnakangas@i 598 [ + + + + : 99 : if (RelationNeedsWAL(index))
+ - + - ]
599 : 42 : XLogEnsureRecordSpace(data.ndeleted, 0);
600 : :
5500 tgl@sss.pgh.pa.us 601 : 99 : START_CRIT_SECTION();
602 : :
603 : 99 : metadata->head = blknoToDelete;
604 : :
5421 bruce@momjian.us 605 [ - + ]: 99 : Assert(metadata->nPendingPages >= data.ndeleted);
5500 tgl@sss.pgh.pa.us 606 : 99 : metadata->nPendingPages -= data.ndeleted;
5421 bruce@momjian.us 607 [ - + ]: 99 : Assert(metadata->nPendingHeapTuples >= nDeletedHeapTuples);
5500 tgl@sss.pgh.pa.us 608 : 99 : metadata->nPendingHeapTuples -= nDeletedHeapTuples;
609 : :
5421 bruce@momjian.us 610 [ + + ]: 99 : if (blknoToDelete == InvalidBlockNumber)
611 : : {
5500 tgl@sss.pgh.pa.us 612 : 18 : metadata->tail = InvalidBlockNumber;
613 : 18 : metadata->tailFreeSize = 0;
614 : 18 : metadata->nPendingPages = 0;
615 : 18 : metadata->nPendingHeapTuples = 0;
616 : : }
617 : :
618 : : /*
619 : : * Set pd_lower just past the end of the metadata. This is essential,
620 : : * because without doing so, metadata will be lost if xlog.c
621 : : * compresses the page. (We must do this here because pre-v11
622 : : * versions of PG did not set the metapage's pd_lower correctly, so a
623 : : * pg_upgraded index might contain the wrong value.)
624 : : */
2355 625 : 99 : ((PageHeader) metapage)->pd_lower =
626 : 99 : ((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage;
627 : :
5421 bruce@momjian.us 628 : 99 : MarkBufferDirty(metabuffer);
629 : :
630 [ + + ]: 1530 : for (i = 0; i < data.ndeleted; i++)
631 : : {
2916 kgrittn@postgresql.o 632 : 1431 : page = BufferGetPage(buffers[i]);
5421 bruce@momjian.us 633 : 1431 : GinPageGetOpaque(page)->flags = GIN_DELETED;
634 : 1431 : MarkBufferDirty(buffers[i]);
635 : : }
636 : :
4871 rhaas@postgresql.org 637 [ + + + + : 99 : if (RelationNeedsWAL(index))
+ - + - ]
638 : : {
639 : : XLogRecPtr recptr;
640 : :
3433 heikki.linnakangas@i 641 : 42 : XLogBeginInsert();
2355 tgl@sss.pgh.pa.us 642 : 42 : XLogRegisterBuffer(0, metabuffer,
643 : : REGBUF_WILL_INIT | REGBUF_STANDARD);
3433 heikki.linnakangas@i 644 [ + + ]: 588 : for (i = 0; i < data.ndeleted; i++)
645 : 546 : XLogRegisterBuffer(i + 1, buffers[i], REGBUF_WILL_INIT);
646 : :
5325 tgl@sss.pgh.pa.us 647 : 42 : memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
648 : :
3433 heikki.linnakangas@i 649 : 42 : XLogRegisterData((char *) &data,
650 : : sizeof(ginxlogDeleteListPages));
651 : :
652 : 42 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_DELETE_LISTPAGE);
5500 tgl@sss.pgh.pa.us 653 : 42 : PageSetLSN(metapage, recptr);
654 : :
5421 bruce@momjian.us 655 [ + + ]: 588 : for (i = 0; i < data.ndeleted; i++)
656 : : {
2916 kgrittn@postgresql.o 657 : 546 : page = BufferGetPage(buffers[i]);
5500 tgl@sss.pgh.pa.us 658 : 546 : PageSetLSN(page, recptr);
659 : : }
660 : : }
661 : :
5421 bruce@momjian.us 662 [ + + ]: 1530 : for (i = 0; i < data.ndeleted; i++)
663 : 1431 : UnlockReleaseBuffer(buffers[i]);
664 : :
5500 tgl@sss.pgh.pa.us 665 [ - + ]: 99 : END_CRIT_SECTION();
666 : :
3126 teodor@sigaev.ru 667 [ + + + + ]: 1451 : for (i = 0; fill_fsm && i < data.ndeleted; i++)
3142 668 : 1352 : RecordFreeIndexPage(index, freespace[i]);
669 : :
5421 bruce@momjian.us 670 [ + + ]: 99 : } while (blknoToDelete != newHead);
5500 tgl@sss.pgh.pa.us 671 : 18 : }
672 : :
673 : : /* Initialize empty KeyArray */
674 : : static void
4846 675 : 18 : initKeyArray(KeyArray *keys, int32 maxvalues)
676 : : {
580 peter@eisentraut.org 677 : 18 : keys->keys = palloc_array(Datum, maxvalues);
678 : 18 : keys->categories = palloc_array(GinNullCategory, maxvalues);
4846 tgl@sss.pgh.pa.us 679 : 18 : keys->nvalues = 0;
680 : 18 : keys->maxvalues = maxvalues;
681 : 18 : }
682 : :
683 : : /* Add datum to KeyArray, resizing if needed */
684 : : static void
685 : 578618 : addDatum(KeyArray *keys, Datum datum, GinNullCategory category)
686 : : {
687 [ - + ]: 578618 : if (keys->nvalues >= keys->maxvalues)
688 : : {
4846 tgl@sss.pgh.pa.us 689 :UBC 0 : keys->maxvalues *= 2;
580 peter@eisentraut.org 690 : 0 : keys->keys = repalloc_array(keys->keys, Datum, keys->maxvalues);
691 : 0 : keys->categories = repalloc_array(keys->categories, GinNullCategory, keys->maxvalues);
692 : : }
693 : :
4846 tgl@sss.pgh.pa.us 694 :CBC 578618 : keys->keys[keys->nvalues] = datum;
695 : 578618 : keys->categories[keys->nvalues] = category;
696 : 578618 : keys->nvalues++;
5500 697 : 578618 : }
698 : :
699 : : /*
700 : : * Collect data from a pending-list page in preparation for insertion into
701 : : * the main index.
702 : : *
703 : : * Go through all tuples >= startoff on page and collect values in accum
704 : : *
705 : : * Note that ka is just workspace --- it does not carry any state across
706 : : * calls.
707 : : */
708 : : static void
4846 709 : 1431 : processPendingPage(BuildAccumulator *accum, KeyArray *ka,
710 : : Page page, OffsetNumber startoff)
711 : : {
712 : : ItemPointerData heapptr;
713 : : OffsetNumber i,
714 : : maxoff;
715 : : OffsetNumber attrnum;
716 : :
717 : : /* reset *ka to empty */
718 : 1431 : ka->nvalues = 0;
719 : :
5500 720 : 1431 : maxoff = PageGetMaxOffsetNumber(page);
5421 bruce@momjian.us 721 [ - + ]: 1431 : Assert(maxoff >= FirstOffsetNumber);
5500 tgl@sss.pgh.pa.us 722 : 1431 : ItemPointerSetInvalid(&heapptr);
723 : 1431 : attrnum = 0;
724 : :
725 [ + + ]: 580049 : for (i = startoff; i <= maxoff; i = OffsetNumberNext(i))
726 : : {
5421 bruce@momjian.us 727 : 578618 : IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i));
728 : : OffsetNumber curattnum;
729 : : Datum curkey;
730 : : GinNullCategory curcategory;
731 : :
732 : : /* Check for change of heap TID or attnum */
5500 tgl@sss.pgh.pa.us 733 : 578618 : curattnum = gintuple_get_attrnum(accum->ginstate, itup);
734 : :
5421 bruce@momjian.us 735 [ + + ]: 578618 : if (!ItemPointerIsValid(&heapptr))
736 : : {
5500 tgl@sss.pgh.pa.us 737 : 1431 : heapptr = itup->t_tid;
738 : 1431 : attrnum = curattnum;
739 : : }
5421 bruce@momjian.us 740 [ + + + + ]: 577187 : else if (!(ItemPointerEquals(&heapptr, &itup->t_tid) &&
741 : : curattnum == attrnum))
742 : : {
743 : : /*
744 : : * ginInsertBAEntries can insert several datums per call, but only
745 : : * for one heap tuple and one column. So call it at a boundary,
746 : : * and reset ka.
747 : : */
4846 tgl@sss.pgh.pa.us 748 : 191010 : ginInsertBAEntries(accum, &heapptr, attrnum,
749 : : ka->keys, ka->categories, ka->nvalues);
750 : 191010 : ka->nvalues = 0;
5500 751 : 191010 : heapptr = itup->t_tid;
752 : 191010 : attrnum = curattnum;
753 : : }
754 : :
755 : : /* Add key to KeyArray */
4846 756 : 578618 : curkey = gintuple_get_key(accum->ginstate, itup, &curcategory);
757 : 578618 : addDatum(ka, curkey, curcategory);
758 : : }
759 : :
760 : : /* Dump out all remaining keys */
761 : 1431 : ginInsertBAEntries(accum, &heapptr, attrnum,
762 : : ka->keys, ka->categories, ka->nvalues);
5500 763 : 1431 : }
764 : :
765 : : /*
766 : : * Move tuples from pending pages into regular GIN structure.
767 : : *
768 : : * On first glance it looks completely not crash-safe. But if we crash
769 : : * after posting entries to the main index and before removing them from the
770 : : * pending list, it's okay because when we redo the posting later on, nothing
771 : : * bad will happen.
772 : : *
773 : : * fill_fsm indicates that ginInsertCleanup should add deleted pages
774 : : * to FSM otherwise caller is responsible to put deleted pages into
775 : : * FSM.
776 : : *
777 : : * If stats isn't null, we count deleted pending pages into the counts.
778 : : */
779 : : void
2908 teodor@sigaev.ru 780 : 45 : ginInsertCleanup(GinState *ginstate, bool full_clean,
781 : : bool fill_fsm, bool forceCleanup,
782 : : IndexBulkDeleteResult *stats)
783 : : {
4846 tgl@sss.pgh.pa.us 784 : 45 : Relation index = ginstate->index;
785 : : Buffer metabuffer,
786 : : buffer;
787 : : Page metapage,
788 : : page;
789 : : GinMetaPageData *metadata;
790 : : MemoryContext opCtx,
791 : : oldCtx;
792 : : BuildAccumulator accum;
793 : : KeyArray datums;
794 : : BlockNumber blkno,
795 : : blknoFinish;
2908 teodor@sigaev.ru 796 : 45 : bool cleanupFinish = false;
3142 797 : 45 : bool fsm_vac = false;
798 : : Size workMemory;
799 : :
800 : : /*
801 : : * We would like to prevent concurrent cleanup process. For that we will
802 : : * lock metapage in exclusive mode using LockPage() call. Nobody other
803 : : * will use that lock for metapage, so we keep possibility of concurrent
804 : : * insertion into pending list
805 : : */
806 : :
2341 rhaas@postgresql.org 807 [ + - ]: 45 : if (forceCleanup)
808 : : {
809 : : /*
810 : : * We are called from [auto]vacuum/analyze or gin_clean_pending_list()
811 : : * and we would like to wait concurrent cleanup to finish.
812 : : */
2908 teodor@sigaev.ru 813 : 45 : LockPage(index, GIN_METAPAGE_BLKNO, ExclusiveLock);
814 : 45 : workMemory =
41 heikki.linnakangas@i 815 [ - + ]:GNC 8 : (AmAutoVacuumWorkerProcess() && autovacuum_work_mem != -1) ?
2866 rhaas@postgresql.org 816 [ + + ]:CBC 53 : autovacuum_work_mem : maintenance_work_mem;
817 : : }
818 : : else
819 : : {
820 : : /*
821 : : * We are called from regular insert and if we see concurrent cleanup
822 : : * just exit in hope that concurrent process will clean up pending
823 : : * list.
824 : : */
2908 teodor@sigaev.ru 825 [ # # ]:UBC 0 : if (!ConditionalLockPage(index, GIN_METAPAGE_BLKNO, ExclusiveLock))
826 : 0 : return;
827 : 0 : workMemory = work_mem;
828 : : }
829 : :
5500 tgl@sss.pgh.pa.us 830 :CBC 45 : metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
831 : 45 : LockBuffer(metabuffer, GIN_SHARE);
2916 kgrittn@postgresql.o 832 : 45 : metapage = BufferGetPage(metabuffer);
5500 tgl@sss.pgh.pa.us 833 : 45 : metadata = GinPageGetMeta(metapage);
834 : :
5421 bruce@momjian.us 835 [ + + ]: 45 : if (metadata->head == InvalidBlockNumber)
836 : : {
837 : : /* Nothing to do */
5500 tgl@sss.pgh.pa.us 838 : 27 : UnlockReleaseBuffer(metabuffer);
2908 teodor@sigaev.ru 839 : 27 : UnlockPage(index, GIN_METAPAGE_BLKNO, ExclusiveLock);
5500 tgl@sss.pgh.pa.us 840 : 27 : return;
841 : : }
842 : :
843 : : /*
844 : : * Remember a tail page to prevent infinite cleanup if other backends add
845 : : * new tuples faster than we can cleanup.
846 : : */
2908 teodor@sigaev.ru 847 : 18 : blknoFinish = metadata->tail;
848 : :
849 : : /*
850 : : * Read and lock head of pending list
851 : : */
5500 tgl@sss.pgh.pa.us 852 : 18 : blkno = metadata->head;
853 : 18 : buffer = ReadBuffer(index, blkno);
854 : 18 : LockBuffer(buffer, GIN_SHARE);
2916 kgrittn@postgresql.o 855 : 18 : page = BufferGetPage(buffer);
856 : :
5500 tgl@sss.pgh.pa.us 857 : 18 : LockBuffer(metabuffer, GIN_UNLOCK);
858 : :
859 : : /*
860 : : * Initialize. All temporary space will be in opCtx
861 : : */
862 : 18 : opCtx = AllocSetContextCreate(CurrentMemoryContext,
863 : : "GIN insert cleanup temporary context",
864 : : ALLOCSET_DEFAULT_SIZES);
865 : :
866 : 18 : oldCtx = MemoryContextSwitchTo(opCtx);
867 : :
4846 868 : 18 : initKeyArray(&datums, 128);
5500 869 : 18 : ginInitBA(&accum);
870 : 18 : accum.ginstate = ginstate;
871 : :
872 : : /*
873 : : * At the top of this loop, we have pin and lock on the current page of
874 : : * the pending list. However, we'll release that before exiting the loop.
875 : : * Note we also have pin but not lock on the metapage.
876 : : */
877 : : for (;;)
878 : : {
2908 teodor@sigaev.ru 879 [ - + ]: 1431 : Assert(!GinPageIsDeleted(page));
880 : :
881 : : /*
882 : : * Are we walk through the page which as we remember was a tail when
883 : : * we start our cleanup? But if caller asks us to clean up whole
884 : : * pending list then ignore old tail, we will work until list becomes
885 : : * empty.
886 : : */
887 [ + + + + ]: 1431 : if (blkno == blknoFinish && full_clean == false)
2908 teodor@sigaev.ru 888 :GBC 2 : cleanupFinish = true;
889 : :
890 : : /*
891 : : * read page's datums into accum
892 : : */
5500 tgl@sss.pgh.pa.us 893 :CBC 1431 : processPendingPage(&accum, &datums, page, FirstOffsetNumber);
894 : :
3142 teodor@sigaev.ru 895 : 1431 : vacuum_delay_point();
896 : :
897 : : /*
898 : : * Is it time to flush memory to disk? Flush if we are at the end of
899 : : * the pending list, or if we have a full row and memory is getting
900 : : * full.
901 : : */
5500 tgl@sss.pgh.pa.us 902 [ + + ]: 1431 : if (GinPageGetOpaque(page)->rightlink == InvalidBlockNumber ||
903 [ + - ]: 1413 : (GinPageHasFullRow(page) &&
2908 teodor@sigaev.ru 904 [ - + ]: 1413 : (accum.allocatedMemory >= workMemory * 1024L)))
5500 tgl@sss.pgh.pa.us 905 :UBC 0 : {
906 : : ItemPointerData *list;
907 : : uint32 nlist;
908 : : Datum key;
909 : : GinNullCategory category;
910 : : OffsetNumber maxoff,
911 : : attnum;
912 : :
913 : : /*
914 : : * Unlock current page to increase performance. Changes of page
915 : : * will be checked later by comparing maxoff after completion of
916 : : * memory flush.
917 : : */
5500 tgl@sss.pgh.pa.us 918 :CBC 18 : maxoff = PageGetMaxOffsetNumber(page);
919 : 18 : LockBuffer(buffer, GIN_UNLOCK);
920 : :
921 : : /*
922 : : * Moving collected data into regular structure can take
923 : : * significant amount of time - so, run it without locking pending
924 : : * list.
925 : : */
5005 926 : 18 : ginBeginBAScan(&accum);
4846 927 : 183070 : while ((list = ginGetBAEntry(&accum,
2489 928 [ + + ]: 183070 : &attnum, &key, &category, &nlist)) != NULL)
929 : : {
4846 930 : 183052 : ginEntryInsert(ginstate, attnum, key, category,
931 : : list, nlist, NULL);
3142 teodor@sigaev.ru 932 : 183052 : vacuum_delay_point();
933 : : }
934 : :
935 : : /*
936 : : * Lock the whole list to remove pages
937 : : */
5500 tgl@sss.pgh.pa.us 938 : 18 : LockBuffer(metabuffer, GIN_EXCLUSIVE);
939 : 18 : LockBuffer(buffer, GIN_SHARE);
940 : :
2908 teodor@sigaev.ru 941 [ - + ]: 18 : Assert(!GinPageIsDeleted(page));
942 : :
943 : : /*
944 : : * While we left the page unlocked, more stuff might have gotten
945 : : * added to it. If so, process those entries immediately. There
946 : : * shouldn't be very many, so we don't worry about the fact that
947 : : * we're doing this with exclusive lock. Insertion algorithm
948 : : * guarantees that inserted row(s) will not continue on next page.
949 : : * NOTE: intentionally no vacuum_delay_point in this loop.
950 : : */
5421 bruce@momjian.us 951 [ - + ]: 18 : if (PageGetMaxOffsetNumber(page) != maxoff)
952 : : {
5500 tgl@sss.pgh.pa.us 953 :UBC 0 : ginInitBA(&accum);
5421 bruce@momjian.us 954 : 0 : processPendingPage(&accum, &datums, page, maxoff + 1);
955 : :
5005 tgl@sss.pgh.pa.us 956 : 0 : ginBeginBAScan(&accum);
4846 957 : 0 : while ((list = ginGetBAEntry(&accum,
2489 958 [ # # ]: 0 : &attnum, &key, &category, &nlist)) != NULL)
4846 959 : 0 : ginEntryInsert(ginstate, attnum, key, category,
960 : : list, nlist, NULL);
961 : : }
962 : :
963 : : /*
964 : : * Remember next page - it will become the new list head
965 : : */
5500 tgl@sss.pgh.pa.us 966 :CBC 18 : blkno = GinPageGetOpaque(page)->rightlink;
2489 967 : 18 : UnlockReleaseBuffer(buffer); /* shiftList will do exclusive
968 : : * locking */
969 : :
970 : : /*
971 : : * remove read pages from pending list, at this point all content
972 : : * of read pages is in regular structure
973 : : */
2908 teodor@sigaev.ru 974 : 18 : shiftList(index, metabuffer, blkno, fill_fsm, stats);
975 : :
976 : : /* At this point, some pending pages have been freed up */
3142 977 : 18 : fsm_vac = true;
978 : :
5421 bruce@momjian.us 979 [ - + ]: 18 : Assert(blkno == metadata->head);
5500 tgl@sss.pgh.pa.us 980 : 18 : LockBuffer(metabuffer, GIN_UNLOCK);
981 : :
982 : : /*
983 : : * if we removed the whole pending list or we cleanup tail (which
984 : : * we remembered on start our cleanup process) then just exit
985 : : */
2908 teodor@sigaev.ru 986 [ - + - - ]: 18 : if (blkno == InvalidBlockNumber || cleanupFinish)
987 : : break;
988 : :
989 : : /*
990 : : * release memory used so far and reinit state
991 : : */
5500 tgl@sss.pgh.pa.us 992 :UBC 0 : MemoryContextReset(opCtx);
4846 993 : 0 : initKeyArray(&datums, datums.maxvalues);
5500 994 : 0 : ginInitBA(&accum);
995 : : }
996 : : else
997 : : {
5500 tgl@sss.pgh.pa.us 998 :CBC 1413 : blkno = GinPageGetOpaque(page)->rightlink;
999 : 1413 : UnlockReleaseBuffer(buffer);
1000 : : }
1001 : :
1002 : : /*
1003 : : * Read next page in pending list
1004 : : */
3142 teodor@sigaev.ru 1005 : 1413 : vacuum_delay_point();
5500 tgl@sss.pgh.pa.us 1006 : 1413 : buffer = ReadBuffer(index, blkno);
1007 : 1413 : LockBuffer(buffer, GIN_SHARE);
2916 kgrittn@postgresql.o 1008 : 1413 : page = BufferGetPage(buffer);
1009 : : }
1010 : :
2908 teodor@sigaev.ru 1011 : 18 : UnlockPage(index, GIN_METAPAGE_BLKNO, ExclusiveLock);
5500 tgl@sss.pgh.pa.us 1012 : 18 : ReleaseBuffer(metabuffer);
1013 : :
1014 : : /*
1015 : : * As pending list pages can have a high churn rate, it is desirable to
1016 : : * recycle them immediately to the FreeSpaceMap when ordinary backends
1017 : : * clean the list.
1018 : : */
3126 teodor@sigaev.ru 1019 [ + - + + ]: 18 : if (fsm_vac && fill_fsm)
3142 1020 : 8 : IndexFreeSpaceMapVacuum(index);
1021 : :
1022 : : /* Clean up temporary space */
5500 tgl@sss.pgh.pa.us 1023 : 18 : MemoryContextSwitchTo(oldCtx);
1024 : 18 : MemoryContextDelete(opCtx);
1025 : : }
1026 : :
1027 : : /*
1028 : : * SQL-callable function to clean the insert pending list
1029 : : */
1030 : : Datum
2999 fujii@postgresql.org 1031 : 9 : gin_clean_pending_list(PG_FUNCTION_ARGS)
1032 : : {
1033 : 9 : Oid indexoid = PG_GETARG_OID(0);
1837 tgl@sss.pgh.pa.us 1034 : 9 : Relation indexRel = index_open(indexoid, RowExclusiveLock);
1035 : : IndexBulkDeleteResult stats;
1036 : :
2999 fujii@postgresql.org 1037 [ - + ]: 9 : if (RecoveryInProgress())
2997 peter_e@gmx.net 1038 [ # # ]:UBC 0 : ereport(ERROR,
1039 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1040 : : errmsg("recovery is in progress"),
1041 : : errhint("GIN pending list cannot be cleaned up during recovery.")));
1042 : :
1043 : : /* Must be a GIN index */
2999 fujii@postgresql.org 1044 [ + - ]:CBC 9 : if (indexRel->rd_rel->relkind != RELKIND_INDEX ||
1045 [ - + ]: 9 : indexRel->rd_rel->relam != GIN_AM_OID)
2999 fujii@postgresql.org 1046 [ # # ]:UBC 0 : ereport(ERROR,
1047 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1048 : : errmsg("\"%s\" is not a GIN index",
1049 : : RelationGetRelationName(indexRel))));
1050 : :
1051 : : /*
1052 : : * Reject attempts to read non-local temporary relations; we would be
1053 : : * likely to get wrong data since we have no visibility into the owning
1054 : : * session's local buffers.
1055 : : */
2999 fujii@postgresql.org 1056 [ + + - + ]:CBC 9 : if (RELATION_IS_OTHER_TEMP(indexRel))
2999 fujii@postgresql.org 1057 [ # # ]:UBC 0 : ereport(ERROR,
1058 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1059 : : errmsg("cannot access temporary indexes of other sessions")));
1060 : :
1061 : : /* User must own the index (comparable to privileges needed for VACUUM) */
518 peter@eisentraut.org 1062 [ - + ]:CBC 9 : if (!object_ownercheck(RelationRelationId, indexoid, GetUserId()))
2325 peter_e@gmx.net 1063 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
2999 fujii@postgresql.org 1064 : 0 : RelationGetRelationName(indexRel));
1065 : :
2999 fujii@postgresql.org 1066 :CBC 9 : memset(&stats, 0, sizeof(stats));
1067 : :
1068 : : /*
1069 : : * Can't assume anything about the content of an !indisready index. Make
1070 : : * those a no-op, not an error, so users can just run this function on all
1071 : : * indexes of the access method. Since an indisready&&!indisvalid index
1072 : : * is merely awaiting missed aminsert calls, we're capable of processing
1073 : : * it. Decline to do so, out of an abundance of caution.
1074 : : */
167 noah@leadboat.com 1075 [ + - ]: 9 : if (indexRel->rd_index->indisvalid)
1076 : : {
1077 : : GinState ginstate;
1078 : :
1079 : 9 : initGinState(&ginstate, indexRel);
1080 : 9 : ginInsertCleanup(&ginstate, true, true, true, &stats);
1081 : : }
1082 : : else
167 noah@leadboat.com 1083 [ # # ]:UBC 0 : ereport(DEBUG1,
1084 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1085 : : errmsg("index \"%s\" is not valid",
1086 : : RelationGetRelationName(indexRel))));
1087 : :
1837 tgl@sss.pgh.pa.us 1088 :CBC 9 : index_close(indexRel, RowExclusiveLock);
1089 : :
2999 fujii@postgresql.org 1090 : 9 : PG_RETURN_INT64((int64) stats.pages_deleted);
1091 : : }
|