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-2026, 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 : : ( (Size) 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
6365 tgl@sss.pgh.pa.us 59 :CBC 1944 : writeListPage(Relation index, Buffer buffer,
60 : : const IndexTuple *tuples, int32 ntuples, BlockNumber rightlink)
61 : : {
3781 kgrittn@postgresql.o 62 : 1944 : Page page = BufferGetPage(buffer);
63 : : int32 i,
64 : : freesize,
6286 bruce@momjian.us 65 : 1944 : size = 0;
66 : : OffsetNumber l,
67 : : off;
68 : : PGAlignedBlock workspace;
69 : : char *ptr;
70 : :
6365 tgl@sss.pgh.pa.us 71 : 1944 : START_CRIT_SECTION();
72 : :
73 : 1944 : GinInitBuffer(buffer, GIN_LIST);
74 : :
75 : 1944 : off = FirstOffsetNumber;
2917 76 : 1944 : ptr = workspace.data;
77 : :
6286 bruce@momjian.us 78 [ + + ]: 11324 : for (i = 0; i < ntuples; i++)
79 : : {
80 : 9380 : int this_size = IndexTupleSize(tuples[i]);
81 : :
6365 tgl@sss.pgh.pa.us 82 : 9380 : memcpy(ptr, tuples[i], this_size);
83 : 9380 : ptr += this_size;
84 : 9380 : size += this_size;
85 : :
304 peter@eisentraut.org 86 : 9380 : l = PageAddItem(page, tuples[i], this_size, off, false, false);
87 : :
6365 tgl@sss.pgh.pa.us 88 [ - + ]: 9380 : if (l == InvalidOffsetNumber)
6365 tgl@sss.pgh.pa.us 89 [ # # ]:UBC 0 : elog(ERROR, "failed to add item to index page in \"%s\"",
90 : : RelationGetRelationName(index));
91 : :
6365 tgl@sss.pgh.pa.us 92 :CBC 9380 : off++;
93 : : }
94 : :
95 [ - + ]: 1944 : Assert(size <= BLCKSZ); /* else we overran workspace */
96 : :
97 : 1944 : 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 : : */
6286 bruce@momjian.us 104 [ + - ]: 1944 : if (rightlink == InvalidBlockNumber)
105 : : {
6365 tgl@sss.pgh.pa.us 106 : 1944 : GinPageSetFullRow(page);
107 : 1944 : GinPageGetOpaque(page)->maxoff = 1;
108 : : }
109 : : else
110 : : {
6365 tgl@sss.pgh.pa.us 111 :UBC 0 : GinPageGetOpaque(page)->maxoff = 0;
112 : : }
113 : :
6365 tgl@sss.pgh.pa.us 114 :CBC 1944 : MarkBufferDirty(buffer);
115 : :
5736 rhaas@postgresql.org 116 [ + + + + : 1944 : if (RelationNeedsWAL(index))
+ - + - ]
117 : : {
118 : : ginxlogInsertListPage data;
119 : : XLogRecPtr recptr;
120 : :
6190 tgl@sss.pgh.pa.us 121 : 755 : data.rightlink = rightlink;
122 : 755 : data.ntuples = ntuples;
123 : :
4298 heikki.linnakangas@i 124 : 755 : XLogBeginInsert();
562 peter@eisentraut.org 125 : 755 : XLogRegisterData(&data, sizeof(ginxlogInsertListPage));
126 : :
4298 heikki.linnakangas@i 127 : 755 : XLogRegisterBuffer(0, buffer, REGBUF_WILL_INIT);
2917 tgl@sss.pgh.pa.us 128 : 755 : XLogRegisterBufData(0, workspace.data, size);
129 : :
4298 heikki.linnakangas@i 130 : 755 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_INSERT_LISTPAGE);
6365 tgl@sss.pgh.pa.us 131 : 755 : PageSetLSN(page, recptr);
132 : : }
133 : :
134 : : /* get free space before releasing buffer */
6190 135 : 1944 : freesize = PageGetExactFreeSpace(page);
136 : :
6365 137 [ - + ]: 1944 : END_CRIT_SECTION();
138 : :
189 michael@paquier.xyz 139 : 1944 : UnlockReleaseBuffer(buffer);
140 : :
6365 tgl@sss.pgh.pa.us 141 : 1944 : return freesize;
142 : : }
143 : :
144 : : static void
145 : 1944 : makeSublist(Relation index, IndexTuple *tuples, int32 ntuples,
146 : : GinMetaPageData *res)
147 : : {
6286 bruce@momjian.us 148 : 1944 : Buffer curBuffer = InvalidBuffer;
149 : 1944 : Buffer prevBuffer = InvalidBuffer;
150 : : int i,
151 : 1944 : size = 0,
152 : : tupsize;
153 : 1944 : int startTuple = 0;
154 : :
6365 tgl@sss.pgh.pa.us 155 [ - + ]: 1944 : Assert(ntuples > 0);
156 : :
157 : : /*
158 : : * Split tuples into pages
159 : : */
6286 bruce@momjian.us 160 [ + + ]: 11324 : for (i = 0; i < ntuples; i++)
161 : : {
162 [ + + ]: 9380 : if (curBuffer == InvalidBuffer)
163 : : {
6365 tgl@sss.pgh.pa.us 164 : 1944 : curBuffer = GinNewBuffer(index);
165 : :
6286 bruce@momjian.us 166 [ - + ]: 1944 : if (prevBuffer != InvalidBuffer)
167 : : {
6365 tgl@sss.pgh.pa.us 168 :UBC 0 : res->nPendingPages++;
169 : 0 : writeListPage(index, prevBuffer,
6190 170 : 0 : tuples + startTuple,
171 : : i - startTuple,
172 : : BufferGetBlockNumber(curBuffer));
173 : : }
174 : : else
175 : : {
6365 tgl@sss.pgh.pa.us 176 :CBC 1944 : res->head = BufferGetBlockNumber(curBuffer);
177 : : }
178 : :
179 : 1944 : prevBuffer = curBuffer;
180 : 1944 : startTuple = i;
181 : 1944 : size = 0;
182 : : }
183 : :
184 : 9380 : tupsize = MAXALIGN(IndexTupleSize(tuples[i])) + sizeof(ItemIdData);
185 : :
6190 186 [ - + ]: 9380 : if (size + tupsize > GinListPageSize)
187 : : {
188 : : /* won't fit, force a new page and reprocess */
6365 tgl@sss.pgh.pa.us 189 :UBC 0 : i--;
190 : 0 : curBuffer = InvalidBuffer;
191 : : }
192 : : else
193 : : {
6365 tgl@sss.pgh.pa.us 194 :CBC 9380 : size += tupsize;
195 : : }
196 : : }
197 : :
198 : : /*
199 : : * Write last page
200 : : */
201 : 1944 : res->tail = BufferGetBlockNumber(curBuffer);
202 : 3888 : res->tailFreeSize = writeListPage(index, curBuffer,
6190 203 : 1944 : tuples + startTuple,
204 : : ntuples - startTuple,
205 : : InvalidBlockNumber);
6365 206 : 1944 : res->nPendingPages++;
207 : : /* that was only one heap tuple */
208 : 1944 : res->nPendingHeapTuples = 1;
209 : 1944 : }
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
5711 219 : 176909 : ginHeapTupleFastInsert(GinState *ginstate, GinTupleCollector *collector)
220 : : {
221 : 176909 : Relation index = ginstate->index;
222 : : Buffer metabuffer;
223 : : Page metapage;
6286 bruce@momjian.us 224 : 176909 : GinMetaPageData *metadata = NULL;
225 : 176909 : Buffer buffer = InvalidBuffer;
226 : 176909 : Page page = NULL;
227 : : ginxlogUpdateMeta data;
228 : 176909 : bool separateList = false;
229 : 176909 : bool needCleanup = false;
230 : : int cleanupSize;
231 : : bool needWal;
232 : :
233 [ - + ]: 176909 : if (collector->ntuples == 0)
6365 tgl@sss.pgh.pa.us 234 :UBC 0 : return;
235 : :
4298 heikki.linnakangas@i 236 [ + + + + :CBC 176909 : needWal = RelationNeedsWAL(index);
+ - + - ]
237 : :
1513 rhaas@postgresql.org 238 : 176909 : data.locator = index->rd_locator;
6365 tgl@sss.pgh.pa.us 239 : 176909 : data.ntuples = 0;
240 : 176909 : data.newRightlink = data.prevTail = InvalidBlockNumber;
241 : :
242 : 176909 : metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
3781 kgrittn@postgresql.o 243 : 176909 : 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 : :
6190 tgl@sss.pgh.pa.us 253 [ - + ]: 176909 : if (collector->sumsize + collector->ntuples * sizeof(ItemIdData) > GinListPageSize)
254 : : {
255 : : /*
256 : : * Total size is greater than one page => make sublist
257 : : */
6365 tgl@sss.pgh.pa.us 258 :UBC 0 : separateList = true;
259 : : }
260 : : else
261 : : {
6365 tgl@sss.pgh.pa.us 262 :CBC 176909 : LockBuffer(metabuffer, GIN_EXCLUSIVE);
263 : 176909 : metadata = GinPageGetMeta(metapage);
264 : :
6286 bruce@momjian.us 265 [ + + ]: 176909 : if (metadata->head == InvalidBlockNumber ||
266 [ + + ]: 176847 : 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 : : */
6365 tgl@sss.pgh.pa.us 274 : 1944 : separateList = true;
275 : 1944 : LockBuffer(metabuffer, GIN_UNLOCK);
276 : : }
277 : : }
278 : :
6286 bruce@momjian.us 279 [ + + ]: 176909 : if (separateList)
280 : : {
281 : : /*
282 : : * We should make sublist separately and append it to the tail
283 : : */
284 : : GinMetaPageData sublist;
285 : :
6190 tgl@sss.pgh.pa.us 286 : 1944 : memset(&sublist, 0, sizeof(GinMetaPageData));
6365 287 : 1944 : makeSublist(index, collector->tuples, collector->ntuples, &sublist);
288 : :
289 : : /*
290 : : * metapage was unlocked, see above
291 : : */
292 : 1944 : LockBuffer(metabuffer, GIN_EXCLUSIVE);
293 : 1944 : metadata = GinPageGetMeta(metapage);
294 : :
1151 tmunro@postgresql.or 295 : 1944 : CheckForSerializableConflictIn(index, NULL, GIN_METAPAGE_BLKNO);
296 : :
6286 bruce@momjian.us 297 [ + + ]: 1941 : if (metadata->head == InvalidBlockNumber)
298 : : {
299 : : /*
300 : : * Main list is empty, so just insert sublist as main list
301 : : */
6365 tgl@sss.pgh.pa.us 302 : 57 : START_CRIT_SECTION();
303 : :
5711 304 : 57 : metadata->head = sublist.head;
305 : 57 : metadata->tail = sublist.tail;
306 : 57 : metadata->tailFreeSize = sublist.tailFreeSize;
307 : :
308 : 57 : metadata->nPendingPages = sublist.nPendingPages;
309 : 57 : metadata->nPendingHeapTuples = sublist.nPendingHeapTuples;
310 : :
1414 michael@paquier.xyz 311 [ + + ]: 57 : if (needWal)
312 : 44 : XLogBeginInsert();
313 : : }
314 : : else
315 : : {
316 : : /*
317 : : * Merge lists
318 : : */
6365 tgl@sss.pgh.pa.us 319 : 1884 : data.prevTail = metadata->tail;
6190 320 : 1884 : data.newRightlink = sublist.head;
321 : :
6365 322 : 1884 : buffer = ReadBuffer(index, metadata->tail);
323 : 1884 : LockBuffer(buffer, GIN_EXCLUSIVE);
3781 kgrittn@postgresql.o 324 : 1884 : page = BufferGetPage(buffer);
325 : :
6365 tgl@sss.pgh.pa.us 326 [ - + ]: 1884 : Assert(GinPageGetOpaque(page)->rightlink == InvalidBlockNumber);
327 : :
328 : 1884 : START_CRIT_SECTION();
329 : :
330 : 1884 : GinPageGetOpaque(page)->rightlink = sublist.head;
331 : :
6190 332 : 1884 : MarkBufferDirty(buffer);
333 : :
6365 334 : 1884 : metadata->tail = sublist.tail;
335 : 1884 : metadata->tailFreeSize = sublist.tailFreeSize;
336 : :
337 : 1884 : metadata->nPendingPages += sublist.nPendingPages;
338 : 1884 : metadata->nPendingHeapTuples += sublist.nPendingHeapTuples;
339 : :
4298 heikki.linnakangas@i 340 [ + + ]: 1884 : if (needWal)
341 : : {
1414 michael@paquier.xyz 342 : 708 : XLogBeginInsert();
4298 heikki.linnakangas@i 343 : 708 : 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 : :
1151 tmunro@postgresql.or 359 : 174965 : CheckForSerializableConflictIn(index, NULL, GIN_METAPAGE_BLKNO);
360 : :
6365 tgl@sss.pgh.pa.us 361 : 174965 : buffer = ReadBuffer(index, metadata->tail);
362 : 174965 : LockBuffer(buffer, GIN_EXCLUSIVE);
3781 kgrittn@postgresql.o 363 : 174965 : page = BufferGetPage(buffer);
364 : :
6365 tgl@sss.pgh.pa.us 365 [ + - ]: 174965 : off = (PageIsEmpty(page)) ? FirstOffsetNumber :
6286 bruce@momjian.us 366 : 174965 : OffsetNumberNext(PageGetMaxOffsetNumber(page));
367 : :
4298 heikki.linnakangas@i 368 : 174965 : collectordata = ptr = (char *) palloc(collector->sumsize);
369 : :
6365 tgl@sss.pgh.pa.us 370 : 174965 : data.ntuples = collector->ntuples;
371 : :
1414 michael@paquier.xyz 372 : 174965 : START_CRIT_SECTION();
373 : :
4298 heikki.linnakangas@i 374 [ + + ]: 174965 : if (needWal)
375 : 96092 : XLogBeginInsert();
376 : :
377 : : /*
378 : : * Increase counter of heap tuples
379 : : */
6286 bruce@momjian.us 380 [ - + ]: 174965 : Assert(GinPageGetOpaque(page)->maxoff <= metadata->nPendingHeapTuples);
6365 tgl@sss.pgh.pa.us 381 : 174965 : GinPageGetOpaque(page)->maxoff++;
382 : 174965 : metadata->nPendingHeapTuples++;
383 : :
6286 bruce@momjian.us 384 [ + + ]: 938452 : for (i = 0; i < collector->ntuples; i++)
385 : : {
6365 tgl@sss.pgh.pa.us 386 : 763487 : tupsize = IndexTupleSize(collector->tuples[i]);
304 peter@eisentraut.org 387 : 763487 : l = PageAddItem(page, collector->tuples[i], tupsize, off, false, false);
388 : :
6365 tgl@sss.pgh.pa.us 389 [ - + ]: 763487 : if (l == InvalidOffsetNumber)
6365 tgl@sss.pgh.pa.us 390 [ # # ]:UBC 0 : elog(ERROR, "failed to add item to index page in \"%s\"",
391 : : RelationGetRelationName(index));
392 : :
6365 tgl@sss.pgh.pa.us 393 :CBC 763487 : memcpy(ptr, collector->tuples[i], tupsize);
6286 bruce@momjian.us 394 : 763487 : ptr += tupsize;
395 : :
6365 tgl@sss.pgh.pa.us 396 : 763487 : off++;
397 : : }
398 : :
4298 heikki.linnakangas@i 399 [ - + ]: 174965 : Assert((ptr - collectordata) <= collector->sumsize);
400 : :
1039 jdavis@postgresql.or 401 : 174965 : MarkBufferDirty(buffer);
402 : :
4298 heikki.linnakangas@i 403 [ + + ]: 174965 : if (needWal)
404 : : {
405 : 96092 : XLogRegisterBuffer(1, buffer, REGBUF_STANDARD);
406 : 96092 : XLogRegisterBufData(1, collectordata, collector->sumsize);
407 : : }
408 : :
6190 tgl@sss.pgh.pa.us 409 : 174965 : 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 : : */
3220 419 : 176906 : ((PageHeader) metapage)->pd_lower =
420 : 176906 : ((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage;
421 : :
422 : : /*
423 : : * Write metabuffer, make xlog entry
424 : : */
6365 425 : 176906 : MarkBufferDirty(metabuffer);
426 : :
4298 heikki.linnakangas@i 427 [ + + ]: 176906 : if (needWal)
428 : : {
429 : : XLogRecPtr recptr;
430 : :
6190 tgl@sss.pgh.pa.us 431 : 96844 : memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
432 : :
3220 433 : 96844 : XLogRegisterBuffer(0, metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);
562 peter@eisentraut.org 434 : 96844 : XLogRegisterData(&data, sizeof(ginxlogUpdateMeta));
435 : :
4298 heikki.linnakangas@i 436 : 96844 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE);
6365 tgl@sss.pgh.pa.us 437 : 96844 : PageSetLSN(metapage, recptr);
438 : :
6286 bruce@momjian.us 439 [ + + ]: 96844 : if (buffer != InvalidBuffer)
440 : : {
6365 tgl@sss.pgh.pa.us 441 : 96800 : PageSetLSN(page, recptr);
442 : : }
443 : : }
444 : :
445 [ + + ]: 176906 : if (buffer != InvalidBuffer)
446 : 176849 : 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 : : */
4307 fujii@postgresql.org 458 [ + - - + : 176906 : cleanupSize = GinGetPendingListCleanupSize(index);
+ + + - ]
573 tgl@sss.pgh.pa.us 459 [ - + ]: 176906 : if (metadata->nPendingPages * GIN_PAGE_FREESIZE > cleanupSize * (Size) 1024)
6365 tgl@sss.pgh.pa.us 460 :UBC 0 : needCleanup = true;
461 : :
6365 tgl@sss.pgh.pa.us 462 [ - + ]:CBC 176906 : END_CRIT_SECTION();
463 : :
189 michael@paquier.xyz 464 : 176906 : UnlockReleaseBuffer(metabuffer);
465 : :
466 : : /*
467 : : * Since it could contend with concurrent cleanup process we cleanup
468 : : * pending list not forcibly.
469 : : */
6286 bruce@momjian.us 470 [ - + ]: 176906 : if (needCleanup)
3206 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
5711 tgl@sss.pgh.pa.us 483 :CBC 256961 : 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 : 256961 : entries = ginExtractEntries(ginstate, attnum, value, isNull,
497 : : &nentries, &categories);
498 : :
499 : : /*
500 : : * Protect against integer overflow in allocation calculations
501 : : */
2808 502 [ + - ]: 256961 : if (nentries < 0 ||
503 [ - + ]: 256961 : collector->ntuples + nentries > MaxAllocSize / sizeof(IndexTuple))
2808 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 : : */
6286 bruce@momjian.us 509 [ + + ]:CBC 256961 : 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 : : */
2332 drowley@postgresql.o 516 : 176909 : collector->lentuples = pg_nextpower2_32(Max(16, nentries));
1445 peter@eisentraut.org 517 : 176909 : collector->tuples = palloc_array(IndexTuple, collector->lentuples);
518 : : }
2808 tgl@sss.pgh.pa.us 519 [ - + ]: 80052 : 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 : : */
2332 drowley@postgresql.o 526 :UBC 0 : collector->lentuples = pg_nextpower2_32(collector->ntuples + nentries);
1445 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 : : */
6365 tgl@sss.pgh.pa.us 535 [ + + ]:CBC 1029828 : for (i = 0; i < nentries; i++)
536 : : {
537 : : IndexTuple itup;
538 : :
5711 539 : 772867 : itup = GinFormTuple(ginstate, attnum, entries[i], categories[i],
540 : : NULL, 0, 0, true);
541 : 772867 : itup->t_tid = *ht_ctid;
542 : 772867 : collector->tuples[collector->ntuples++] = itup;
543 : 772867 : collector->sumsize += IndexTupleSize(itup);
544 : : }
6365 545 : 256961 : }
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 : 31 : shiftList(Relation index, Buffer metabuffer, BlockNumber newHead,
555 : : bool fill_fsm, IndexBulkDeleteResult *stats)
556 : : {
557 : : Page metapage;
558 : : GinMetaPageData *metadata;
559 : : BlockNumber blknoToDelete;
560 : :
3781 kgrittn@postgresql.o 561 : 31 : metapage = BufferGetPage(metabuffer);
6365 tgl@sss.pgh.pa.us 562 : 31 : metadata = GinPageGetMeta(metapage);
563 : 31 : blknoToDelete = metadata->head;
564 : :
565 : : do
566 : : {
567 : : Page page;
568 : : int i;
6286 bruce@momjian.us 569 : 139 : int64 nDeletedHeapTuples = 0;
570 : : ginxlogDeleteListPages data;
571 : : Buffer buffers[GIN_NDELETE_AT_ONCE];
572 : : BlockNumber freespace[GIN_NDELETE_AT_ONCE];
573 : :
6365 tgl@sss.pgh.pa.us 574 : 139 : data.ndeleted = 0;
575 [ + + + + ]: 2052 : while (data.ndeleted < GIN_NDELETE_AT_ONCE && blknoToDelete != newHead)
576 : : {
4007 teodor@sigaev.ru 577 : 1913 : freespace[data.ndeleted] = blknoToDelete;
6286 bruce@momjian.us 578 : 1913 : buffers[data.ndeleted] = ReadBuffer(index, blknoToDelete);
579 : 1913 : LockBuffer(buffers[data.ndeleted], GIN_EXCLUSIVE);
3781 kgrittn@postgresql.o 580 : 1913 : page = BufferGetPage(buffers[data.ndeleted]);
581 : :
6365 tgl@sss.pgh.pa.us 582 : 1913 : data.ndeleted++;
583 : :
3773 teodor@sigaev.ru 584 [ - + ]: 1913 : Assert(!GinPageIsDeleted(page));
585 : :
6365 tgl@sss.pgh.pa.us 586 : 1913 : nDeletedHeapTuples += GinPageGetOpaque(page)->maxoff;
6286 bruce@momjian.us 587 : 1913 : blknoToDelete = GinPageGetOpaque(page)->rightlink;
588 : : }
589 : :
6365 tgl@sss.pgh.pa.us 590 [ + + ]: 139 : if (stats)
591 : 136 : 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 : : */
4297 heikki.linnakangas@i 598 [ + + + + : 139 : if (RelationNeedsWAL(index))
+ - + - ]
599 : 63 : XLogEnsureRecordSpace(data.ndeleted, 0);
600 : :
6365 tgl@sss.pgh.pa.us 601 : 139 : START_CRIT_SECTION();
602 : :
603 : 139 : metadata->head = blknoToDelete;
604 : :
6286 bruce@momjian.us 605 [ - + ]: 139 : Assert(metadata->nPendingPages >= data.ndeleted);
6365 tgl@sss.pgh.pa.us 606 : 139 : metadata->nPendingPages -= data.ndeleted;
6286 bruce@momjian.us 607 [ - + ]: 139 : Assert(metadata->nPendingHeapTuples >= nDeletedHeapTuples);
6365 tgl@sss.pgh.pa.us 608 : 139 : metadata->nPendingHeapTuples -= nDeletedHeapTuples;
609 : :
6286 bruce@momjian.us 610 [ + + ]: 139 : if (blknoToDelete == InvalidBlockNumber)
611 : : {
6365 tgl@sss.pgh.pa.us 612 : 31 : metadata->tail = InvalidBlockNumber;
613 : 31 : metadata->tailFreeSize = 0;
614 : 31 : metadata->nPendingPages = 0;
615 : 31 : 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 : : */
3220 625 : 139 : ((PageHeader) metapage)->pd_lower =
626 : 139 : ((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage;
627 : :
6286 bruce@momjian.us 628 : 139 : MarkBufferDirty(metabuffer);
629 : :
630 [ + + ]: 2052 : for (i = 0; i < data.ndeleted; i++)
631 : : {
3781 kgrittn@postgresql.o 632 : 1913 : page = BufferGetPage(buffers[i]);
6286 bruce@momjian.us 633 : 1913 : GinPageGetOpaque(page)->flags = GIN_DELETED;
634 : 1913 : MarkBufferDirty(buffers[i]);
635 : : }
636 : :
5736 rhaas@postgresql.org 637 [ + + + + : 139 : if (RelationNeedsWAL(index))
+ - + - ]
638 : : {
639 : : XLogRecPtr recptr;
640 : :
4298 heikki.linnakangas@i 641 : 63 : XLogBeginInsert();
3220 tgl@sss.pgh.pa.us 642 : 63 : XLogRegisterBuffer(0, metabuffer,
643 : : REGBUF_WILL_INIT | REGBUF_STANDARD);
4298 heikki.linnakangas@i 644 [ + + ]: 796 : for (i = 0; i < data.ndeleted; i++)
645 : 733 : XLogRegisterBuffer(i + 1, buffers[i], REGBUF_WILL_INIT);
646 : :
6190 tgl@sss.pgh.pa.us 647 : 63 : memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
648 : :
562 peter@eisentraut.org 649 : 63 : XLogRegisterData(&data,
650 : : sizeof(ginxlogDeleteListPages));
651 : :
4298 heikki.linnakangas@i 652 : 63 : recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_DELETE_LISTPAGE);
6365 tgl@sss.pgh.pa.us 653 : 63 : PageSetLSN(metapage, recptr);
654 : :
6286 bruce@momjian.us 655 [ + + ]: 796 : for (i = 0; i < data.ndeleted; i++)
656 : : {
3781 kgrittn@postgresql.o 657 : 733 : page = BufferGetPage(buffers[i]);
6365 tgl@sss.pgh.pa.us 658 : 733 : PageSetLSN(page, recptr);
659 : : }
660 : : }
661 : :
189 michael@paquier.xyz 662 [ - + ]: 139 : END_CRIT_SECTION();
663 : :
6286 bruce@momjian.us 664 [ + + ]: 2052 : for (i = 0; i < data.ndeleted; i++)
665 : 1913 : UnlockReleaseBuffer(buffers[i]);
666 : :
3991 teodor@sigaev.ru 667 [ + + + + ]: 1946 : for (i = 0; fill_fsm && i < data.ndeleted; i++)
4007 668 : 1807 : RecordFreeIndexPage(index, freespace[i]);
669 : :
6286 bruce@momjian.us 670 [ + + ]: 139 : } while (blknoToDelete != newHead);
6365 tgl@sss.pgh.pa.us 671 : 31 : }
672 : :
673 : : /* Initialize empty KeyArray */
674 : : static void
5711 675 : 31 : initKeyArray(KeyArray *keys, int32 maxvalues)
676 : : {
1445 peter@eisentraut.org 677 : 31 : keys->keys = palloc_array(Datum, maxvalues);
678 : 31 : keys->categories = palloc_array(GinNullCategory, maxvalues);
5711 tgl@sss.pgh.pa.us 679 : 31 : keys->nvalues = 0;
680 : 31 : keys->maxvalues = maxvalues;
681 : 31 : }
682 : :
683 : : /* Add datum to KeyArray, resizing if needed */
684 : : static void
685 : 770560 : addDatum(KeyArray *keys, Datum datum, GinNullCategory category)
686 : : {
687 [ - + ]: 770560 : if (keys->nvalues >= keys->maxvalues)
688 : : {
5711 tgl@sss.pgh.pa.us 689 :UBC 0 : keys->maxvalues *= 2;
1445 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 : :
5711 tgl@sss.pgh.pa.us 694 :CBC 770560 : keys->keys[keys->nvalues] = datum;
695 : 770560 : keys->categories[keys->nvalues] = category;
696 : 770560 : keys->nvalues++;
6365 697 : 770560 : }
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
5711 709 : 1914 : 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 : 1914 : ka->nvalues = 0;
719 : :
6365 720 : 1914 : maxoff = PageGetMaxOffsetNumber(page);
6286 bruce@momjian.us 721 [ - + ]: 1914 : Assert(maxoff >= FirstOffsetNumber);
6365 tgl@sss.pgh.pa.us 722 : 1914 : ItemPointerSetInvalid(&heapptr);
723 : 1914 : attrnum = 0;
724 : :
725 [ + + ]: 772474 : for (i = startoff; i <= maxoff; i = OffsetNumberNext(i))
726 : : {
6286 bruce@momjian.us 727 : 770560 : 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 */
6365 tgl@sss.pgh.pa.us 733 : 770560 : curattnum = gintuple_get_attrnum(accum->ginstate, itup);
734 : :
6286 bruce@momjian.us 735 [ + + ]: 770560 : if (!ItemPointerIsValid(&heapptr))
736 : : {
6365 tgl@sss.pgh.pa.us 737 : 1914 : heapptr = itup->t_tid;
738 : 1914 : attrnum = curattnum;
739 : : }
6286 bruce@momjian.us 740 [ + + + + ]: 768646 : 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 : : */
5711 tgl@sss.pgh.pa.us 748 : 254535 : ginInsertBAEntries(accum, &heapptr, attrnum,
749 : : ka->keys, ka->categories, ka->nvalues);
750 : 254535 : ka->nvalues = 0;
6365 751 : 254535 : heapptr = itup->t_tid;
752 : 254535 : attrnum = curattnum;
753 : : }
754 : :
755 : : /* Add key to KeyArray */
5711 756 : 770560 : curkey = gintuple_get_key(accum->ginstate, itup, &curcategory);
757 : 770560 : addDatum(ka, curkey, curcategory);
758 : : }
759 : :
760 : : /* Dump out all remaining keys */
761 : 1914 : ginInsertBAEntries(accum, &heapptr, attrnum,
762 : : ka->keys, ka->categories, ka->nvalues);
6365 763 : 1914 : }
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
8 pg@bowt.ie 780 : 86 : ginInsertCleanup(GinState *ginstate, bool must_empty_list,
781 : : bool fill_fsm, bool forceCleanup,
782 : : IndexBulkDeleteResult *stats)
783 : : {
5711 tgl@sss.pgh.pa.us 784 : 86 : 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;
3773 teodor@sigaev.ru 796 : 86 : bool cleanupFinish = false;
4007 797 : 86 : bool fsm_vac = false;
798 : : int 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 : :
3206 rhaas@postgresql.org 807 [ + - ]: 86 : if (forceCleanup)
808 : : {
809 : : /*
810 : : * We are called from [auto]vacuum/analyze or gin_clean_pending_list()
811 : : * and we must wait for concurrent cleanup to finish. In particular,
812 : : * VACUUM must have the opportunity to remove any dead TIDs that are
813 : : * now in the pending list.
814 : : */
3773 teodor@sigaev.ru 815 : 86 : LockPage(index, GIN_METAPAGE_BLKNO, ExclusiveLock);
816 : 86 : workMemory =
906 heikki.linnakangas@i 817 [ - + ]: 11 : (AmAutoVacuumWorkerProcess() && autovacuum_work_mem != -1) ?
3731 rhaas@postgresql.org 818 [ + + ]: 97 : autovacuum_work_mem : maintenance_work_mem;
819 : : }
820 : : else
821 : : {
822 : : /*
823 : : * We are called from regular insert and if we see concurrent cleanup
824 : : * just exit in hope that concurrent process will clean up pending
825 : : * list.
826 : : */
3773 teodor@sigaev.ru 827 [ # # ]:UBC 0 : if (!ConditionalLockPage(index, GIN_METAPAGE_BLKNO, ExclusiveLock))
3773 teodor@sigaev.ru 828 :CBC 55 : return;
3773 teodor@sigaev.ru 829 :UBC 0 : workMemory = work_mem;
830 : : }
831 : :
6365 tgl@sss.pgh.pa.us 832 :CBC 86 : metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
833 : 86 : LockBuffer(metabuffer, GIN_SHARE);
3781 kgrittn@postgresql.o 834 : 86 : metapage = BufferGetPage(metabuffer);
6365 tgl@sss.pgh.pa.us 835 : 86 : metadata = GinPageGetMeta(metapage);
836 : :
6286 bruce@momjian.us 837 [ + + ]: 86 : if (metadata->head == InvalidBlockNumber)
838 : : {
839 : : /* Nothing to do */
6365 tgl@sss.pgh.pa.us 840 : 55 : UnlockReleaseBuffer(metabuffer);
3773 teodor@sigaev.ru 841 : 55 : UnlockPage(index, GIN_METAPAGE_BLKNO, ExclusiveLock);
6365 tgl@sss.pgh.pa.us 842 : 55 : return;
843 : : }
844 : :
845 : : /*
846 : : * Remember a tail page to prevent infinite cleanup if other backends add
847 : : * new tuples faster than we can cleanup.
848 : : */
3773 teodor@sigaev.ru 849 : 31 : blknoFinish = metadata->tail;
850 : :
851 : : /*
852 : : * Read and lock head of pending list
853 : : */
6365 tgl@sss.pgh.pa.us 854 : 31 : blkno = metadata->head;
855 : 31 : buffer = ReadBuffer(index, blkno);
856 : 31 : LockBuffer(buffer, GIN_SHARE);
3781 kgrittn@postgresql.o 857 : 31 : page = BufferGetPage(buffer);
858 : :
6365 tgl@sss.pgh.pa.us 859 : 31 : LockBuffer(metabuffer, GIN_UNLOCK);
860 : :
861 : : /*
862 : : * Initialize. All temporary space will be in opCtx
863 : : */
864 : 31 : opCtx = AllocSetContextCreate(CurrentMemoryContext,
865 : : "GIN insert cleanup temporary context",
866 : : ALLOCSET_DEFAULT_SIZES);
867 : :
868 : 31 : oldCtx = MemoryContextSwitchTo(opCtx);
869 : :
5711 870 : 31 : initKeyArray(&datums, 128);
6365 871 : 31 : ginInitBA(&accum);
872 : 31 : accum.ginstate = ginstate;
873 : :
874 : : /*
875 : : * At the top of this loop, we have pin and lock on the current page of
876 : : * the pending list. However, we'll release that before exiting the loop.
877 : : * Note we also have pin but not lock on the metapage.
878 : : */
879 : : for (;;)
880 : : {
3773 teodor@sigaev.ru 881 [ - + ]: 1913 : Assert(!GinPageIsDeleted(page));
882 : :
883 : : /*
884 : : * Are we walk through the page which as we remember was a tail when
885 : : * we start our cleanup? But if caller asks us to fully empty the
886 : : * pending list (not just move all items that were in the list when
887 : : * blknoFinish was established) then ignore old tail and work until
888 : : * the list is fully empty.
889 : : */
8 pg@bowt.ie 890 [ + + + + ]: 1913 : if (blkno == blknoFinish && !must_empty_list)
3773 teodor@sigaev.ru 891 : 3 : cleanupFinish = true;
892 : :
893 : : /*
894 : : * read page's datums into accum
895 : : */
6365 tgl@sss.pgh.pa.us 896 : 1913 : processPendingPage(&accum, &datums, page, FirstOffsetNumber);
897 : :
562 nathan@postgresql.or 898 : 1913 : vacuum_delay_point(false);
899 : :
900 : : /*
901 : : * Is it time to flush memory to disk? Flush if we are at the end of
902 : : * the pending list, or if we have a full row and memory is getting
903 : : * full.
904 : : */
6365 tgl@sss.pgh.pa.us 905 [ + + ]: 1913 : if (GinPageGetOpaque(page)->rightlink == InvalidBlockNumber ||
906 [ + - ]: 1882 : (GinPageHasFullRow(page) &&
573 907 [ - + ]: 1882 : accum.allocatedMemory >= workMemory * (Size) 1024))
6365 tgl@sss.pgh.pa.us 908 :UBC 0 : {
909 : : ItemPointerData *list;
910 : : uint32 nlist;
911 : : Datum key;
912 : : GinNullCategory category;
913 : : OffsetNumber maxoff,
914 : : attnum;
915 : :
916 : : /*
917 : : * Unlock current page to increase performance. Changes of page
918 : : * will be checked later by comparing maxoff after completion of
919 : : * memory flush.
920 : : */
6365 tgl@sss.pgh.pa.us 921 :CBC 31 : maxoff = PageGetMaxOffsetNumber(page);
922 : 31 : LockBuffer(buffer, GIN_UNLOCK);
923 : :
924 : : /*
925 : : * Moving collected data into regular structure can take
926 : : * significant amount of time - so, run it without locking pending
927 : : * list.
928 : : */
5870 929 : 31 : ginBeginBAScan(&accum);
5711 930 : 244124 : while ((list = ginGetBAEntry(&accum,
3354 931 [ + + ]: 244124 : &attnum, &key, &category, &nlist)) != NULL)
932 : : {
5711 933 : 244093 : ginEntryInsert(ginstate, attnum, key, category,
934 : : list, nlist, NULL);
562 nathan@postgresql.or 935 : 244093 : vacuum_delay_point(false);
936 : : }
937 : :
938 : : /*
939 : : * Lock the whole list to remove pages
940 : : */
6365 tgl@sss.pgh.pa.us 941 : 31 : LockBuffer(metabuffer, GIN_EXCLUSIVE);
942 : 31 : LockBuffer(buffer, GIN_SHARE);
943 : :
3773 teodor@sigaev.ru 944 [ - + ]: 31 : Assert(!GinPageIsDeleted(page));
945 : :
946 : : /*
947 : : * While we left the page unlocked, more stuff might have gotten
948 : : * added to it. If so, process those entries immediately. There
949 : : * shouldn't be very many, so we don't worry about the fact that
950 : : * we're doing this with exclusive lock. Insertion algorithm
951 : : * guarantees that inserted row(s) will not continue on next page.
952 : : * NOTE: intentionally no vacuum_delay_point in this loop.
953 : : */
6286 bruce@momjian.us 954 [ + + ]: 31 : if (PageGetMaxOffsetNumber(page) != maxoff)
955 : : {
6365 tgl@sss.pgh.pa.us 956 : 1 : ginInitBA(&accum);
6286 bruce@momjian.us 957 : 1 : processPendingPage(&accum, &datums, page, maxoff + 1);
958 : :
5870 tgl@sss.pgh.pa.us 959 : 1 : ginBeginBAScan(&accum);
5711 960 : 4 : while ((list = ginGetBAEntry(&accum,
3354 961 [ + + ]: 4 : &attnum, &key, &category, &nlist)) != NULL)
5711 962 : 3 : ginEntryInsert(ginstate, attnum, key, category,
963 : : list, nlist, NULL);
964 : : }
965 : :
966 : : /*
967 : : * Remember next page - it will become the new list head
968 : : */
6365 969 : 31 : blkno = GinPageGetOpaque(page)->rightlink;
3354 970 : 31 : UnlockReleaseBuffer(buffer); /* shiftList will do exclusive
971 : : * locking */
972 : :
973 : : /*
974 : : * remove read pages from pending list, at this point all content
975 : : * of read pages is in regular structure
976 : : */
3773 teodor@sigaev.ru 977 : 31 : shiftList(index, metabuffer, blkno, fill_fsm, stats);
978 : :
979 : : /* At this point, some pending pages have been freed up */
4007 980 : 31 : fsm_vac = true;
981 : :
6286 bruce@momjian.us 982 [ - + ]: 31 : Assert(blkno == metadata->head);
6365 tgl@sss.pgh.pa.us 983 : 31 : LockBuffer(metabuffer, GIN_UNLOCK);
984 : :
985 : : /*
986 : : * if we removed the whole pending list or we cleanup tail (which
987 : : * we remembered on start our cleanup process) then just exit
988 : : */
3773 teodor@sigaev.ru 989 [ - + - - ]: 31 : if (blkno == InvalidBlockNumber || cleanupFinish)
990 : : break;
991 : :
992 : : /*
993 : : * release memory used so far and reinit state
994 : : */
6365 tgl@sss.pgh.pa.us 995 :UBC 0 : MemoryContextReset(opCtx);
5711 996 : 0 : initKeyArray(&datums, datums.maxvalues);
6365 997 : 0 : ginInitBA(&accum);
998 : : }
999 : : else
1000 : : {
6365 tgl@sss.pgh.pa.us 1001 :CBC 1882 : blkno = GinPageGetOpaque(page)->rightlink;
1002 : 1882 : UnlockReleaseBuffer(buffer);
1003 : : }
1004 : :
1005 : : /*
1006 : : * Read next page in pending list
1007 : : */
562 nathan@postgresql.or 1008 : 1882 : vacuum_delay_point(false);
6365 tgl@sss.pgh.pa.us 1009 : 1882 : buffer = ReadBuffer(index, blkno);
1010 : 1882 : LockBuffer(buffer, GIN_SHARE);
3781 kgrittn@postgresql.o 1011 : 1882 : page = BufferGetPage(buffer);
1012 : : }
1013 : :
3773 teodor@sigaev.ru 1014 : 31 : UnlockPage(index, GIN_METAPAGE_BLKNO, ExclusiveLock);
6365 tgl@sss.pgh.pa.us 1015 : 31 : ReleaseBuffer(metabuffer);
1016 : :
1017 : : /*
1018 : : * As pending list pages can have a high churn rate, it is desirable to
1019 : : * recycle them immediately to the FreeSpaceMap when ordinary backends
1020 : : * clean the list.
1021 : : */
3991 teodor@sigaev.ru 1022 [ + - + + ]: 31 : if (fsm_vac && fill_fsm)
4007 1023 : 15 : IndexFreeSpaceMapVacuum(index);
1024 : :
1025 : : /* Clean up temporary space */
6365 tgl@sss.pgh.pa.us 1026 : 31 : MemoryContextSwitchTo(oldCtx);
1027 : 31 : MemoryContextDelete(opCtx);
1028 : : }
1029 : :
1030 : : /*
1031 : : * SQL-callable function to clean the insert pending list
1032 : : */
1033 : : Datum
3864 fujii@postgresql.org 1034 : 16 : gin_clean_pending_list(PG_FUNCTION_ARGS)
1035 : : {
1036 : 16 : Oid indexoid = PG_GETARG_OID(0);
2702 tgl@sss.pgh.pa.us 1037 : 16 : Relation indexRel = index_open(indexoid, RowExclusiveLock);
1038 : : IndexBulkDeleteResult stats;
1039 : :
3864 fujii@postgresql.org 1040 [ - + ]: 16 : if (RecoveryInProgress())
3862 peter_e@gmx.net 1041 [ # # ]:UBC 0 : ereport(ERROR,
1042 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1043 : : errmsg("recovery is in progress"),
1044 : : errhint("GIN pending list cannot be cleaned up during recovery.")));
1045 : :
1046 : : /* Must be a GIN index */
3864 fujii@postgresql.org 1047 [ + - ]:CBC 16 : if (indexRel->rd_rel->relkind != RELKIND_INDEX ||
1048 [ - + ]: 16 : indexRel->rd_rel->relam != GIN_AM_OID)
3864 fujii@postgresql.org 1049 [ # # ]:UBC 0 : ereport(ERROR,
1050 : : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
1051 : : errmsg("\"%s\" is not a GIN index",
1052 : : RelationGetRelationName(indexRel))));
1053 : :
1054 : : /*
1055 : : * Reject attempts to read non-local temporary relations; we would be
1056 : : * likely to get wrong data since we have no visibility into the owning
1057 : : * session's local buffers.
1058 : : */
3864 fujii@postgresql.org 1059 [ + + - + ]:CBC 16 : if (RELATION_IS_OTHER_TEMP(indexRel))
3864 fujii@postgresql.org 1060 [ # # ]:UBC 0 : ereport(ERROR,
1061 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1062 : : errmsg("cannot access temporary indexes of other sessions")));
1063 : :
1064 : : /* User must own the index (comparable to privileges needed for VACUUM) */
1383 peter@eisentraut.org 1065 [ - + ]:CBC 16 : if (!object_ownercheck(RelationRelationId, indexoid, GetUserId()))
3190 peter_e@gmx.net 1066 :UBC 0 : aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_INDEX,
3864 fujii@postgresql.org 1067 : 0 : RelationGetRelationName(indexRel));
1068 : :
3864 fujii@postgresql.org 1069 :CBC 16 : memset(&stats, 0, sizeof(stats));
1070 : :
1071 : : /*
1072 : : * Can't assume anything about the content of an !indisready index. Make
1073 : : * those a no-op, not an error, so users can just run this function on all
1074 : : * indexes of the access method. Since an indisready&&!indisvalid index
1075 : : * is merely awaiting missed aminsert calls, we're capable of processing
1076 : : * it. Decline to do so, out of an abundance of caution.
1077 : : */
1032 noah@leadboat.com 1078 [ + - ]: 16 : if (indexRel->rd_index->indisvalid)
1079 : : {
1080 : : GinState ginstate;
1081 : :
1082 : 16 : initGinState(&ginstate, indexRel);
1083 : 16 : ginInsertCleanup(&ginstate, true, true, true, &stats);
1084 : : }
1085 : : else
1032 noah@leadboat.com 1086 [ # # ]:UBC 0 : ereport(DEBUG1,
1087 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
1088 : : errmsg("index \"%s\" is not valid",
1089 : : RelationGetRelationName(indexRel))));
1090 : :
2702 tgl@sss.pgh.pa.us 1091 :CBC 16 : index_close(indexRel, RowExclusiveLock);
1092 : :
3864 fujii@postgresql.org 1093 : 16 : PG_RETURN_INT64((int64) stats.pages_deleted);
1094 : : }
|