Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * nbtree.c
4 : : * Implementation of Lehman and Yao's btree management algorithm for
5 : : * Postgres.
6 : : *
7 : : * NOTES
8 : : * This file contains only the public interface routines.
9 : : *
10 : : *
11 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
12 : : * Portions Copyright (c) 1994, Regents of the University of California
13 : : *
14 : : * IDENTIFICATION
15 : : * src/backend/access/nbtree/nbtree.c
16 : : *
17 : : *-------------------------------------------------------------------------
18 : : */
19 : : #include "postgres.h"
20 : :
21 : : #include "access/nbtree.h"
22 : : #include "access/relscan.h"
23 : : #include "access/stratnum.h"
24 : : #include "commands/progress.h"
25 : : #include "commands/vacuum.h"
26 : : #include "nodes/execnodes.h"
27 : : #include "pgstat.h"
28 : : #include "storage/bulk_write.h"
29 : : #include "storage/condition_variable.h"
30 : : #include "storage/indexfsm.h"
31 : : #include "storage/ipc.h"
32 : : #include "storage/lmgr.h"
33 : : #include "storage/lwlock.h"
34 : : #include "storage/read_stream.h"
35 : : #include "utils/datum.h"
36 : : #include "utils/fmgrprotos.h"
37 : : #include "utils/index_selfuncs.h"
38 : : #include "utils/memutils.h"
39 : : #include "utils/wait_event.h"
40 : :
41 : :
42 : : /*
43 : : * BTPARALLEL_NOT_INITIALIZED indicates that the scan has not started.
44 : : *
45 : : * BTPARALLEL_NEED_PRIMSCAN indicates that some process must now seize the
46 : : * scan to advance it via another call to _bt_first.
47 : : *
48 : : * BTPARALLEL_ADVANCING indicates that some process is advancing the scan to
49 : : * a new page; others must wait.
50 : : *
51 : : * BTPARALLEL_IDLE indicates that no backend is currently advancing the scan
52 : : * to a new page; some process can start doing that.
53 : : *
54 : : * BTPARALLEL_DONE indicates that the scan is complete (including error exit).
55 : : */
56 : : typedef enum
57 : : {
58 : : BTPARALLEL_NOT_INITIALIZED,
59 : : BTPARALLEL_NEED_PRIMSCAN,
60 : : BTPARALLEL_ADVANCING,
61 : : BTPARALLEL_IDLE,
62 : : BTPARALLEL_DONE,
63 : : } BTPS_State;
64 : :
65 : : /*
66 : : * BTParallelScanDescData contains btree specific shared information required
67 : : * for parallel scan.
68 : : */
69 : : typedef struct BTParallelScanDescData
70 : : {
71 : : BlockNumber btps_nextScanPage; /* next page to be scanned */
72 : : BlockNumber btps_lastCurrPage; /* page whose sibling link was copied into
73 : : * btps_nextScanPage */
74 : : BTPS_State btps_pageStatus; /* indicates whether next page is
75 : : * available for scan. see above for
76 : : * possible states of parallel scan. */
77 : : LWLock btps_lock; /* protects shared parallel state */
78 : : ConditionVariable btps_cv; /* used to synchronize parallel scan */
79 : :
80 : : /*
81 : : * btps_numArrayKeys is the number of arrays that the backend which last
82 : : * serialized its array keys was working with.
83 : : */
84 : : int btps_numArrayKeys;
85 : :
86 : : /*
87 : : * btps_arrElems is used when scans need to schedule another primitive
88 : : * index scan with one or more SAOP arrays. Holds BTArrayKeyInfo.cur_elem
89 : : * offsets for each = scan key associated with a ScalarArrayOp array.
90 : : */
91 : : int btps_arrElems[FLEXIBLE_ARRAY_MEMBER];
92 : :
93 : : /*
94 : : * Additional space (at the end of the struct) is used when scans need to
95 : : * schedule another primitive index scan with one or more skip arrays.
96 : : * Holds a flattened datum representation for each = scan key associated
97 : : * with a skip array.
98 : : */
99 : : } BTParallelScanDescData;
100 : :
101 : : typedef struct BTParallelScanDescData *BTParallelScanDesc;
102 : :
103 : :
104 : : static bool _bt_start_prim_scan(IndexScanDesc scan);
105 : : static void _bt_parallel_serialize_arrays(Relation rel, BTParallelScanDesc btscan,
106 : : BTScanOpaque so);
107 : : static void _bt_parallel_restore_arrays(Relation rel, BTParallelScanDesc btscan,
108 : : BTScanOpaque so);
109 : : static void btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
110 : : IndexBulkDeleteCallback callback, void *callback_state,
111 : : BTCycleId cycleid);
112 : : static BlockNumber btvacuumpage(BTVacState *vstate, Buffer buf);
113 : : static BTVacuumPosting btreevacuumposting(BTVacState *vstate,
114 : : IndexTuple posting,
115 : : OffsetNumber updatedoffset,
116 : : int *nremaining);
117 : :
118 : :
119 : : /*
120 : : * Btree handler function: return IndexAmRoutine with access method parameters
121 : : * and callbacks.
122 : : */
123 : : Datum
124 : 2117299 : bthandler(PG_FUNCTION_ARGS)
125 : : {
126 : : static const IndexAmRoutine amroutine = {
127 : : .type = T_IndexAmRoutine,
128 : : .amstrategies = BTMaxStrategyNumber,
129 : : .amsupport = BTNProcs,
130 : : .amoptsprocnum = BTOPTIONS_PROC,
131 : : .amcanorder = true,
132 : : .amcanorderbyop = false,
133 : : .amcanhash = false,
134 : : .amconsistentequality = true,
135 : : .amconsistentordering = true,
136 : : .amcanbackward = true,
137 : : .amcanunique = true,
138 : : .amcanmulticol = true,
139 : : .amoptionalkey = true,
140 : : .amsearcharray = true,
141 : : .amsearchnulls = true,
142 : : .amstorage = false,
143 : : .amclusterable = true,
144 : : .ampredlocks = true,
145 : : .amcanparallel = true,
146 : : .amcanbuildparallel = true,
147 : : .amcaninclude = true,
148 : : .amusemaintenanceworkmem = false,
149 : : .amsummarizing = false,
150 : : .amparallelvacuumoptions =
151 : : VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_COND_CLEANUP,
152 : : .amkeytype = InvalidOid,
153 : :
154 : : .ambuild = btbuild,
155 : : .ambuildempty = btbuildempty,
156 : : .aminsert = btinsert,
157 : : .aminsertcleanup = NULL,
158 : : .ambulkdelete = btbulkdelete,
159 : : .amvacuumcleanup = btvacuumcleanup,
160 : : .amcanreturn = btcanreturn,
161 : : .amcostestimate = btcostestimate,
162 : : .amgettreeheight = btgettreeheight,
163 : : .amoptions = btoptions,
164 : : .amproperty = btproperty,
165 : : .ambuildphasename = btbuildphasename,
166 : : .amvalidate = btvalidate,
167 : : .amadjustmembers = btadjustmembers,
168 : : .ambeginscan = btbeginscan,
169 : : .amrescan = btrescan,
170 : : .amgettuple = btgettuple,
171 : : .amgetbitmap = btgetbitmap,
172 : : .amendscan = btendscan,
173 : : .ammarkpos = btmarkpos,
174 : : .amrestrpos = btrestrpos,
175 : : .amestimateparallelscan = btestimateparallelscan,
176 : : .aminitparallelscan = btinitparallelscan,
177 : : .amparallelrescan = btparallelrescan,
178 : : .amtranslatestrategy = bttranslatestrategy,
179 : : .amtranslatecmptype = bttranslatecmptype,
180 : : };
181 : :
182 : 2117299 : PG_RETURN_POINTER(&amroutine);
183 : : }
184 : :
185 : : /*
186 : : * btbuildempty() -- build an empty btree index in the initialization fork
187 : : */
188 : : void
189 : 117 : btbuildempty(Relation index)
190 : : {
191 : 117 : bool allequalimage = _bt_allequalimage(index, false);
192 : : BulkWriteState *bulkstate;
193 : : BulkWriteBuffer metabuf;
194 : :
195 : 117 : bulkstate = smgr_bulk_start_rel(index, INIT_FORKNUM);
196 : :
197 : : /* Construct metapage. */
198 : 117 : metabuf = smgr_bulk_get_buf(bulkstate);
199 : 117 : _bt_initmetapage((Page) metabuf, P_NONE, 0, allequalimage);
200 : 117 : smgr_bulk_write(bulkstate, BTREE_METAPAGE, metabuf, true);
201 : :
202 : 117 : smgr_bulk_finish(bulkstate);
203 : 117 : }
204 : :
205 : : /*
206 : : * btinsert() -- insert an index tuple into a btree.
207 : : *
208 : : * Descend the tree recursively, find the appropriate location for our
209 : : * new tuple, and put it there.
210 : : */
211 : : bool
212 : 5362271 : btinsert(Relation rel, Datum *values, bool *isnull,
213 : : ItemPointer ht_ctid, Relation heapRel,
214 : : IndexUniqueCheck checkUnique,
215 : : bool indexUnchanged,
216 : : IndexInfo *indexInfo)
217 : : {
218 : : bool result;
219 : : IndexTuple itup;
220 : :
221 : : /* generate an index tuple */
222 : 5362271 : itup = index_form_tuple(RelationGetDescr(rel), values, isnull);
223 : 5362271 : itup->t_tid = *ht_ctid;
224 : :
225 : 5362271 : result = _bt_doinsert(rel, itup, checkUnique, indexUnchanged, heapRel);
226 : :
227 : 5361885 : pfree(itup);
228 : :
229 : 5361885 : return result;
230 : : }
231 : :
232 : : /*
233 : : * btgettuple() -- Get the next tuple in the scan.
234 : : */
235 : : bool
236 : 24007653 : btgettuple(IndexScanDesc scan, ScanDirection dir)
237 : : {
238 : 24007653 : BTScanOpaque so = (BTScanOpaque) scan->opaque;
239 : : bool res;
240 : :
241 : : Assert(scan->heapRelation != NULL);
242 : :
243 : : /* btree indexes are never lossy */
244 : 24007653 : scan->xs_recheck = false;
245 : :
246 : : /* Each loop iteration performs another primitive index scan */
247 : : do
248 : : {
249 : : /*
250 : : * If we've already initialized this scan, we can just advance it in
251 : : * the appropriate direction. If we haven't done so yet, we call
252 : : * _bt_first() to get the first item in the scan.
253 : : */
254 [ + + ]: 24019257 : if (!BTScanPosIsValid(so->currPos))
255 : 10901745 : res = _bt_first(scan, dir);
256 : : else
257 : : {
258 : : /*
259 : : * Check to see if we should kill the previously-fetched tuple.
260 : : */
261 [ + + ]: 13117512 : if (scan->kill_prior_tuple)
262 : : {
263 : : /*
264 : : * Yes, remember it for later. (We'll deal with all such
265 : : * tuples at once right before leaving the index page.) The
266 : : * test for numKilled overrun is not just paranoia: if the
267 : : * caller reverses direction in the indexscan then the same
268 : : * item might get entered multiple times. It's not worth
269 : : * trying to optimize that, so we don't detect it, but instead
270 : : * just forget any excess entries.
271 : : */
272 [ + + ]: 305527 : if (so->killedItems == NULL)
273 : 108350 : so->killedItems = palloc_array(int, MaxTIDsPerBTreePage);
274 [ + - ]: 305527 : if (so->numKilled < MaxTIDsPerBTreePage)
275 : 305527 : so->killedItems[so->numKilled++] = so->currPos.itemIndex;
276 : : }
277 : :
278 : : /*
279 : : * Now continue the scan.
280 : : */
281 : 13117512 : res = _bt_next(scan, dir);
282 : : }
283 : :
284 : : /* If we have a tuple, return it ... */
285 [ + + ]: 24019253 : if (res)
286 : 19408806 : break;
287 : : /* ... otherwise see if we need another primitive index scan */
288 [ + + + + ]: 4610447 : } while (so->numArrayKeys && _bt_start_prim_scan(scan));
289 : :
290 : 24007649 : return res;
291 : : }
292 : :
293 : : /*
294 : : * btgetbitmap() -- gets all matching tuples, and adds them to a bitmap
295 : : */
296 : : int64
297 : 10545 : btgetbitmap(IndexScanDesc scan, TIDBitmap *tbm)
298 : : {
299 : 10545 : BTScanOpaque so = (BTScanOpaque) scan->opaque;
300 : 10545 : int64 ntids = 0;
301 : : ItemPointer heapTid;
302 : :
303 : : Assert(scan->heapRelation == NULL);
304 : :
305 : : /* Each loop iteration performs another primitive index scan */
306 : : do
307 : : {
308 : : /* Fetch the first page & tuple */
309 [ + + ]: 11006 : if (_bt_first(scan, ForwardScanDirection))
310 : : {
311 : : /* Save tuple ID, and continue scanning */
312 : 8811 : heapTid = &scan->xs_heaptid;
313 : 8811 : tbm_add_tuples(tbm, heapTid, 1, false);
314 : 8811 : ntids++;
315 : :
316 : : for (;;)
317 : : {
318 : : /*
319 : : * Advance to next tuple within page. This is the same as the
320 : : * easy case in _bt_next().
321 : : */
322 [ + + ]: 1323793 : if (++so->currPos.itemIndex > so->currPos.lastItem)
323 : : {
324 : : /* let _bt_next do the heavy lifting */
325 [ + + ]: 11998 : if (!_bt_next(scan, ForwardScanDirection))
326 : 8811 : break;
327 : : }
328 : :
329 : : /* Save tuple ID, and continue scanning */
330 : 1314982 : heapTid = &so->currPos.items[so->currPos.itemIndex].heapTid;
331 : 1314982 : tbm_add_tuples(tbm, heapTid, 1, false);
332 : 1314982 : ntids++;
333 : : }
334 : : }
335 : : /* Now see if we need another primitive index scan */
336 [ + + + + ]: 11006 : } while (so->numArrayKeys && _bt_start_prim_scan(scan));
337 : :
338 : 10545 : return ntids;
339 : : }
340 : :
341 : : /*
342 : : * btbeginscan() -- start a scan on a btree index
343 : : */
344 : : IndexScanDesc
345 : 10428416 : btbeginscan(Relation rel, int nkeys, int norderbys)
346 : : {
347 : : IndexScanDesc scan;
348 : : BTScanOpaque so;
349 : :
350 : : /* no order by operators allowed */
351 : : Assert(norderbys == 0);
352 : :
353 : : /* get the scan */
354 : 10428416 : scan = RelationGetIndexScan(rel, nkeys, norderbys);
355 : :
356 : : /* allocate private workspace */
357 : 10428416 : so = palloc_object(BTScanOpaqueData);
358 : 10428416 : BTScanPosInvalidate(so->currPos);
359 : 10428416 : BTScanPosInvalidate(so->markPos);
360 [ + + ]: 10428416 : if (scan->numberOfKeys > 0)
361 : 10420241 : so->keyData = palloc_array(ScanKeyData, scan->numberOfKeys);
362 : : else
363 : 8175 : so->keyData = NULL;
364 : :
365 : 10428416 : so->skipScan = false;
366 : 10428416 : so->needPrimScan = false;
367 : 10428416 : so->scanBehind = false;
368 : 10428416 : so->oppositeDirCheck = false;
369 : 10428416 : so->arrayKeys = NULL;
370 : 10428416 : so->orderProcs = NULL;
371 : 10428416 : so->arrayContext = NULL;
372 : :
373 : 10428416 : so->killedItems = NULL; /* until needed */
374 : 10428416 : so->numKilled = 0;
375 : :
376 : : /*
377 : : * We don't know yet whether the scan will be index-only, so we do not
378 : : * allocate the tuple workspace arrays until btrescan. However, we set up
379 : : * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
380 : : */
381 : 10428416 : so->currTuples = so->markTuples = NULL;
382 : :
383 : 10428416 : scan->xs_itupdesc = RelationGetDescr(rel);
384 : :
385 : 10428416 : scan->opaque = so;
386 : :
387 : 10428416 : return scan;
388 : : }
389 : :
390 : : /*
391 : : * btrescan() -- rescan an index relation
392 : : */
393 : : void
394 : 10901509 : btrescan(IndexScanDesc scan, ScanKey scankey, int nscankeys,
395 : : ScanKey orderbys, int norderbys)
396 : : {
397 : 10901509 : BTScanOpaque so = (BTScanOpaque) scan->opaque;
398 : :
399 : : /* we aren't holding any read locks, but gotta drop the pins */
400 [ + + ]: 10901509 : if (BTScanPosIsValid(so->currPos))
401 : : {
402 : : /* Before leaving current page, deal with any killed items */
403 [ + + ]: 69903 : if (so->numKilled > 0)
404 : 627 : _bt_killitems(scan);
405 [ + + ]: 69903 : BTScanPosUnpinIfPinned(so->currPos);
406 : 69903 : BTScanPosInvalidate(so->currPos);
407 : : }
408 : :
409 : : /*
410 : : * We prefer to eagerly drop leaf page pins before btgettuple returns.
411 : : * This avoids making VACUUM wait to acquire a cleanup lock on the page.
412 : : *
413 : : * We cannot safely drop leaf page pins during index-only scans due to a
414 : : * race condition involving VACUUM setting pages all-visible in the VM.
415 : : * It's also unsafe for plain index scans that use a non-MVCC snapshot.
416 : : *
417 : : * Also opt out of dropping leaf page pins eagerly during bitmap scans.
418 : : * Pins cannot be held for more than an instant during bitmap scans either
419 : : * way, so we might as well avoid wasting cycles on acquiring page LSNs.
420 : : *
421 : : * See nbtree/README section on making concurrent TID recycling safe.
422 : : *
423 : : * Note: so->dropPin should never change across rescans.
424 : : */
425 : 32468777 : so->dropPin = (!scan->xs_want_itup &&
426 [ + + + + : 20830850 : IsMVCCLikeSnapshot(scan->xs_snapshot) &&
+ + ]
427 [ + + ]: 9929341 : scan->heapRelation != NULL);
428 : :
429 : 10901509 : so->markItemIndex = -1;
430 : 10901509 : so->needPrimScan = false;
431 : 10901509 : so->scanBehind = false;
432 : 10901509 : so->oppositeDirCheck = false;
433 [ - + ]: 10901509 : BTScanPosUnpinIfPinned(so->markPos);
434 : 10901509 : BTScanPosInvalidate(so->markPos);
435 : :
436 : : /*
437 : : * Allocate tuple workspace arrays, if needed for an index-only scan and
438 : : * not already done in a previous rescan call. To save on palloc
439 : : * overhead, both workspaces are allocated as one palloc block; only this
440 : : * function and btendscan know that.
441 : : */
442 [ + + + + ]: 10901509 : if (scan->xs_want_itup && so->currTuples == NULL)
443 : : {
444 : 92213 : so->currTuples = (char *) palloc(BLCKSZ * 2);
445 : 92213 : so->markTuples = so->currTuples + BLCKSZ;
446 : : }
447 : :
448 : : /*
449 : : * Reset the scan keys
450 : : */
451 [ + + + + ]: 10901509 : if (scankey && scan->numberOfKeys > 0)
452 : 10893062 : memcpy(scan->keyData, scankey, scan->numberOfKeys * sizeof(ScanKeyData));
453 : 10901509 : so->numberOfKeys = 0; /* until _bt_preprocess_keys sets it */
454 : 10901509 : so->numArrayKeys = 0; /* ditto */
455 : 10901509 : }
456 : :
457 : : /*
458 : : * btendscan() -- close down a scan
459 : : */
460 : : void
461 : 10427081 : btendscan(IndexScanDesc scan)
462 : : {
463 : 10427081 : BTScanOpaque so = (BTScanOpaque) scan->opaque;
464 : :
465 : : /* we aren't holding any read locks, but gotta drop the pins */
466 [ + + ]: 10427081 : if (BTScanPosIsValid(so->currPos))
467 : : {
468 : : /* Before leaving current page, deal with any killed items */
469 [ + + ]: 6220422 : if (so->numKilled > 0)
470 : 54082 : _bt_killitems(scan);
471 [ + + ]: 6220422 : BTScanPosUnpinIfPinned(so->currPos);
472 : : }
473 : :
474 : 10427081 : so->markItemIndex = -1;
475 [ + + ]: 10427081 : BTScanPosUnpinIfPinned(so->markPos);
476 : :
477 : : /* No need to invalidate positions, the RAM is about to be freed. */
478 : :
479 : : /* Release storage */
480 [ + + ]: 10427081 : if (so->keyData != NULL)
481 : 10418926 : pfree(so->keyData);
482 : : /* so->arrayKeys and so->orderProcs are in arrayContext */
483 [ + + ]: 10427081 : if (so->arrayContext != NULL)
484 : 2518 : MemoryContextDelete(so->arrayContext);
485 [ + + ]: 10427081 : if (so->killedItems != NULL)
486 : 108309 : pfree(so->killedItems);
487 [ + + ]: 10427081 : if (so->currTuples != NULL)
488 : 92180 : pfree(so->currTuples);
489 : : /* so->markTuples should not be pfree'd, see btrescan */
490 : 10427081 : pfree(so);
491 : 10427081 : }
492 : :
493 : : /*
494 : : * btmarkpos() -- save current scan position
495 : : */
496 : : void
497 : 90402 : btmarkpos(IndexScanDesc scan)
498 : : {
499 : 90402 : BTScanOpaque so = (BTScanOpaque) scan->opaque;
500 : :
501 : : /* There may be an old mark with a pin (but no lock). */
502 [ + + ]: 90402 : BTScanPosUnpinIfPinned(so->markPos);
503 : :
504 : : /*
505 : : * Just record the current itemIndex. If we later step to next page
506 : : * before releasing the marked position, _bt_steppage makes a full copy of
507 : : * the currPos struct in markPos. If (as often happens) the mark is moved
508 : : * before we leave the page, we don't have to do that work.
509 : : */
510 [ + - ]: 90402 : if (BTScanPosIsValid(so->currPos))
511 : 90402 : so->markItemIndex = so->currPos.itemIndex;
512 : : else
513 : : {
514 : 0 : BTScanPosInvalidate(so->markPos);
515 : 0 : so->markItemIndex = -1;
516 : : }
517 : 90402 : }
518 : :
519 : : /*
520 : : * btrestrpos() -- restore scan to last saved position
521 : : */
522 : : void
523 : 52018 : btrestrpos(IndexScanDesc scan)
524 : : {
525 : 52018 : BTScanOpaque so = (BTScanOpaque) scan->opaque;
526 : :
527 [ + + ]: 52018 : if (so->markItemIndex >= 0)
528 : : {
529 : : /*
530 : : * The scan has never moved to a new page since the last mark. Just
531 : : * restore the itemIndex.
532 : : *
533 : : * NB: In this case we can't count on anything in so->markPos to be
534 : : * accurate.
535 : : */
536 : 51914 : so->currPos.itemIndex = so->markItemIndex;
537 : : }
538 : : else
539 : : {
540 : : /*
541 : : * The scan moved to a new page after last mark or restore, and we are
542 : : * now restoring to the marked page. We aren't holding any read
543 : : * locks, but if we're still holding the pin for the current position,
544 : : * we must drop it.
545 : : */
546 [ + - ]: 104 : if (BTScanPosIsValid(so->currPos))
547 : : {
548 : : /* Before leaving current page, deal with any killed items */
549 [ - + ]: 104 : if (so->numKilled > 0)
550 : 0 : _bt_killitems(scan);
551 [ + + ]: 104 : BTScanPosUnpinIfPinned(so->currPos);
552 : : }
553 : :
554 [ + - ]: 104 : if (BTScanPosIsValid(so->markPos))
555 : : {
556 : : /* bump pin on mark buffer for assignment to current buffer */
557 [ + + ]: 104 : if (BTScanPosIsPinned(so->markPos))
558 : 32 : IncrBufferRefCount(so->markPos.buf);
559 : 104 : memcpy(&so->currPos, &so->markPos,
560 : : offsetof(BTScanPosData, items[1]) +
561 : 104 : so->markPos.lastItem * sizeof(BTScanPosItem));
562 [ + + ]: 104 : if (so->currTuples)
563 : 32 : memcpy(so->currTuples, so->markTuples,
564 : 32 : so->markPos.nextTupleOffset);
565 : : /* Reset the scan's array keys (see _bt_steppage for why) */
566 [ - + ]: 104 : if (so->numArrayKeys)
567 : : {
568 : 0 : _bt_start_array_keys(scan, so->currPos.dir);
569 : 0 : so->needPrimScan = false;
570 : : }
571 : : }
572 : : else
573 : 0 : BTScanPosInvalidate(so->currPos);
574 : : }
575 : 52018 : }
576 : :
577 : : /*
578 : : * btestimateparallelscan -- estimate storage for BTParallelScanDescData
579 : : */
580 : : Size
581 : 42 : btestimateparallelscan(Relation rel, int nkeys, int norderbys)
582 : : {
583 : 42 : int16 nkeyatts = IndexRelationGetNumberOfKeyAttributes(rel);
584 : : Size estnbtreeshared,
585 : : genericattrspace;
586 : :
587 : : /*
588 : : * Pessimistically assume that every input scan key will be output with
589 : : * its own SAOP array
590 : : */
591 : 42 : estnbtreeshared = offsetof(BTParallelScanDescData, btps_arrElems) +
592 : : sizeof(int) * nkeys;
593 : :
594 : : /* Single column indexes cannot possibly use a skip array */
595 [ + + ]: 42 : if (nkeyatts == 1)
596 : 30 : return estnbtreeshared;
597 : :
598 : : /*
599 : : * Pessimistically assume that all attributes prior to the least
600 : : * significant attribute require a skip array (and an associated key)
601 : : */
602 : 12 : genericattrspace = datumEstimateSpace((Datum) 0, false, true,
603 : : sizeof(Datum));
604 [ + + ]: 24 : for (int attnum = 1; attnum < nkeyatts; attnum++)
605 : : {
606 : : CompactAttribute *attr;
607 : :
608 : : /*
609 : : * We make the conservative assumption that every index column will
610 : : * also require a skip array.
611 : : *
612 : : * Every skip array must have space to store its scan key's sk_flags.
613 : : * We also need space for each skip array's unused btps_arrElems slot
614 : : * (we need to be able to subscript btps_arrElems using a simple
615 : : * so->arrayKeys[]-wise offset for any subsequent SAOP arrays).
616 : : */
617 : 12 : estnbtreeshared = add_size(estnbtreeshared, sizeof(int) * 2);
618 : :
619 : : /* Consider space required to store a datum of opclass input type */
620 : 12 : attr = TupleDescCompactAttr(rel->rd_att, attnum - 1);
621 [ + - ]: 12 : if (attr->attbyval)
622 : 12 : {
623 : : /* This index attribute stores pass-by-value datums */
624 : 12 : Size estfixed = datumEstimateSpace((Datum) 0, false,
625 : 12 : true, attr->attlen);
626 : :
627 : 12 : estnbtreeshared = add_size(estnbtreeshared, estfixed);
628 : 12 : continue;
629 : : }
630 : :
631 : : /*
632 : : * This index attribute stores pass-by-reference datums.
633 : : *
634 : : * Assume that serializing this array will use just as much space as a
635 : : * pass-by-value datum, in addition to space for the largest possible
636 : : * whole index tuple (this is not just a per-datum portion of the
637 : : * largest possible tuple because that'd be almost as large anyway).
638 : : *
639 : : * This is quite conservative, but it's not clear how we could do much
640 : : * better. The executor requires an up-front storage request size
641 : : * that reliably covers the scan's high watermark memory usage. We
642 : : * can't be sure of the real high watermark until the scan is over.
643 : : */
644 : 0 : estnbtreeshared = add_size(estnbtreeshared, genericattrspace);
645 : 0 : estnbtreeshared = add_size(estnbtreeshared, BTMaxItemSize);
646 : : }
647 : :
648 : 12 : return estnbtreeshared;
649 : : }
650 : :
651 : : /*
652 : : * _bt_start_prim_scan() -- start scheduled primitive index scan?
653 : : *
654 : : * Returns true if _bt_checkkeys scheduled another primitive index scan, just
655 : : * as the last one ended. Otherwise returns false, indicating that the array
656 : : * keys are now fully exhausted.
657 : : *
658 : : * Only call here during scans with one or more equality type array scan keys,
659 : : * after _bt_first or _bt_next return false.
660 : : */
661 : : static bool
662 : 58819 : _bt_start_prim_scan(IndexScanDesc scan)
663 : : {
664 : 58819 : BTScanOpaque so = (BTScanOpaque) scan->opaque;
665 : :
666 : : Assert(so->numArrayKeys);
667 : :
668 : 58819 : so->scanBehind = so->oppositeDirCheck = false; /* reset */
669 : :
670 : : /*
671 : : * Array keys are advanced within _bt_checkkeys when the scan reaches the
672 : : * leaf level (more precisely, they're advanced when the scan reaches the
673 : : * end of each distinct set of array elements). This process avoids
674 : : * repeat access to leaf pages (across multiple primitive index scans) by
675 : : * advancing the scan's array keys when it allows the primitive index scan
676 : : * to find nearby matching tuples (or when it eliminates ranges of array
677 : : * key space that can't possibly be satisfied by any index tuple).
678 : : *
679 : : * _bt_checkkeys sets a simple flag variable to schedule another primitive
680 : : * index scan. The flag tells us what to do.
681 : : *
682 : : * We cannot rely on _bt_first always reaching _bt_checkkeys. There are
683 : : * various cases where that won't happen. For example, if the index is
684 : : * completely empty, then _bt_first won't call _bt_readpage/_bt_checkkeys.
685 : : * We also don't expect a call to _bt_checkkeys during searches for a
686 : : * non-existent value that happens to be lower/higher than any existing
687 : : * value in the index.
688 : : *
689 : : * We don't require special handling for these cases -- we don't need to
690 : : * be explicitly instructed to _not_ perform another primitive index scan.
691 : : * It's up to code under the control of _bt_first to always set the flag
692 : : * when another primitive index scan will be required.
693 : : *
694 : : * This works correctly, even with the tricky cases listed above, which
695 : : * all involve access to leaf pages "near the boundaries of the key space"
696 : : * (whether it's from a leftmost/rightmost page, or an imaginary empty
697 : : * leaf root page). If _bt_checkkeys cannot be reached by a primitive
698 : : * index scan for one set of array keys, then it also won't be reached for
699 : : * any later set ("later" in terms of the direction that we scan the index
700 : : * and advance the arrays). The array keys won't have advanced in these
701 : : * cases, but that's the correct behavior (even _bt_advance_array_keys
702 : : * won't always advance the arrays at the point they become "exhausted").
703 : : */
704 [ + + ]: 58819 : if (so->needPrimScan)
705 : : {
706 : : /*
707 : : * Flag was set -- must call _bt_first again, which will reset the
708 : : * scan's needPrimScan flag
709 : : */
710 : 12065 : return true;
711 : : }
712 : :
713 : : /* The top-level index scan ran out of tuples in this scan direction */
714 [ + + ]: 46754 : if (scan->parallel_scan != NULL)
715 : 20 : _bt_parallel_done(scan);
716 : :
717 : 46754 : return false;
718 : : }
719 : :
720 : : /*
721 : : * _bt_parallel_serialize_arrays() -- Serialize parallel array state.
722 : : *
723 : : * Caller must have exclusively locked btscan->btps_lock when called.
724 : : */
725 : : static void
726 : 24 : _bt_parallel_serialize_arrays(Relation rel, BTParallelScanDesc btscan,
727 : : BTScanOpaque so)
728 : : {
729 : : char *datumshared;
730 : :
731 : 24 : btscan->btps_numArrayKeys = so->numArrayKeys;
732 : :
733 : : /* Space for serialized datums begins immediately after btps_arrElems[] */
734 : 24 : datumshared = ((char *) &btscan->btps_arrElems[so->numArrayKeys]);
735 [ + + ]: 48 : for (int i = 0; i < so->numArrayKeys; i++)
736 : : {
737 : 24 : BTArrayKeyInfo *array = &so->arrayKeys[i];
738 : 24 : ScanKey skey = &so->keyData[array->scan_key];
739 : :
740 [ + - ]: 24 : if (array->num_elems != -1)
741 : : {
742 : : /* Save SAOP array's cur_elem (no need to copy key/datum) */
743 : : Assert(!(skey->sk_flags & SK_BT_SKIP));
744 : 24 : btscan->btps_arrElems[i] = array->cur_elem;
745 : 24 : continue;
746 : : }
747 : :
748 : : /* Save all mutable state associated with skip array's key */
749 : : Assert(skey->sk_flags & SK_BT_SKIP);
750 : 0 : memcpy(datumshared, &skey->sk_flags, sizeof(int));
751 : 0 : datumshared += sizeof(int);
752 : :
753 [ # # ]: 0 : if (skey->sk_flags & (SK_BT_MINVAL | SK_BT_MAXVAL))
754 : : {
755 : : /* No sk_argument datum to serialize */
756 : : Assert(skey->sk_argument == 0);
757 : 0 : continue;
758 : : }
759 : :
760 : 0 : datumSerialize(skey->sk_argument, (skey->sk_flags & SK_ISNULL) != 0,
761 : 0 : array->attbyval, array->attlen, &datumshared);
762 : : }
763 : 24 : }
764 : :
765 : : /*
766 : : * _bt_parallel_restore_arrays() -- Restore serialized parallel array state.
767 : : *
768 : : * Caller must have exclusively locked btscan->btps_lock when called.
769 : : */
770 : : static void
771 : 24 : _bt_parallel_restore_arrays(Relation rel, BTParallelScanDesc btscan,
772 : : BTScanOpaque so)
773 : : {
774 : : char *datumshared;
775 : :
776 : : /*
777 : : * The backend that serialized these arrays should have ended up with the
778 : : * same number of them as us, but it might not if a function used to
779 : : * compute them was mislabeled as PARALLEL SAFE or STABLE.
780 : : */
781 [ - + ]: 24 : if (btscan->btps_numArrayKeys != so->numArrayKeys)
782 [ # # ]: 0 : elog(ERROR, "parallel scan of index \"%s\" serialized %d array keys, expected %d",
783 : : RelationGetRelationName(rel),
784 : : btscan->btps_numArrayKeys, so->numArrayKeys);
785 : :
786 : : /* Space for serialized datums begins immediately after btps_arrElems[] */
787 : 24 : datumshared = ((char *) &btscan->btps_arrElems[so->numArrayKeys]);
788 [ + + ]: 48 : for (int i = 0; i < so->numArrayKeys; i++)
789 : : {
790 : 24 : BTArrayKeyInfo *array = &so->arrayKeys[i];
791 : 24 : ScanKey skey = &so->keyData[array->scan_key];
792 : : bool isnull;
793 : :
794 [ + - ]: 24 : if (array->num_elems != -1)
795 : : {
796 : : /* Restore SAOP array using its saved cur_elem */
797 : : Assert(!(skey->sk_flags & SK_BT_SKIP));
798 : :
799 : : /*
800 : : * The saved value might not be in range for our array, since the
801 : : * backend that saved it could have ended up with a different
802 : : * array if a function used to compute it was mislabeled as
803 : : * PARALLEL SAFE or STABLE.
804 : : */
805 [ + - ]: 24 : if (btscan->btps_arrElems[i] < 0 ||
806 [ - + ]: 24 : btscan->btps_arrElems[i] >= array->num_elems)
807 [ # # ]: 0 : elog(ERROR, "parallel scan of index \"%s\" serialized array element offset %d, but the array has %d elements",
808 : : RelationGetRelationName(rel),
809 : : btscan->btps_arrElems[i], array->num_elems);
810 : :
811 : 24 : array->cur_elem = btscan->btps_arrElems[i];
812 : 24 : skey->sk_argument = array->elem_values[array->cur_elem];
813 : 24 : continue;
814 : : }
815 : :
816 : : /* Restore skip array by restoring its key directly */
817 [ # # # # ]: 0 : if (!array->attbyval && skey->sk_argument)
818 : 0 : pfree(DatumGetPointer(skey->sk_argument));
819 : 0 : skey->sk_argument = (Datum) 0;
820 : 0 : memcpy(&skey->sk_flags, datumshared, sizeof(int));
821 : 0 : datumshared += sizeof(int);
822 : :
823 : : Assert(skey->sk_flags & SK_BT_SKIP);
824 : :
825 [ # # ]: 0 : if (skey->sk_flags & (SK_BT_MINVAL | SK_BT_MAXVAL))
826 : : {
827 : : /* No sk_argument datum to restore */
828 : 0 : continue;
829 : : }
830 : :
831 : 0 : skey->sk_argument = datumRestore(&datumshared, &isnull);
832 : 0 : if (isnull)
833 : : {
834 : : Assert(skey->sk_argument == 0);
835 : : Assert(skey->sk_flags & SK_SEARCHNULL);
836 : : Assert(skey->sk_flags & SK_ISNULL);
837 : : }
838 : : }
839 : 24 : }
840 : :
841 : : /*
842 : : * btinitparallelscan -- initialize BTParallelScanDesc for parallel btree scan
843 : : */
844 : : void
845 : 42 : btinitparallelscan(void *target)
846 : : {
847 : 42 : BTParallelScanDesc bt_target = (BTParallelScanDesc) target;
848 : :
849 : 42 : LWLockInitialize(&bt_target->btps_lock,
850 : : LWTRANCHE_PARALLEL_BTREE_SCAN);
851 : 42 : bt_target->btps_nextScanPage = InvalidBlockNumber;
852 : 42 : bt_target->btps_lastCurrPage = InvalidBlockNumber;
853 : 42 : bt_target->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
854 : 42 : ConditionVariableInit(&bt_target->btps_cv);
855 : 42 : }
856 : :
857 : : /*
858 : : * btparallelrescan() -- reset parallel scan
859 : : */
860 : : void
861 : 16 : btparallelrescan(IndexScanDesc scan)
862 : : {
863 : : BTParallelScanDesc btscan;
864 : 16 : ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
865 : :
866 : : Assert(parallel_scan);
867 : :
868 : 16 : btscan = (BTParallelScanDesc) OffsetToPointer(parallel_scan,
869 : : parallel_scan->ps_offset_am);
870 : :
871 : : /*
872 : : * In theory, we don't need to acquire the LWLock here, because there
873 : : * shouldn't be any other workers running at this point, but we do so for
874 : : * consistency.
875 : : */
876 : 16 : LWLockAcquire(&btscan->btps_lock, LW_EXCLUSIVE);
877 : 16 : btscan->btps_nextScanPage = InvalidBlockNumber;
878 : 16 : btscan->btps_lastCurrPage = InvalidBlockNumber;
879 : 16 : btscan->btps_pageStatus = BTPARALLEL_NOT_INITIALIZED;
880 : 16 : LWLockRelease(&btscan->btps_lock);
881 : 16 : }
882 : :
883 : : /*
884 : : * _bt_parallel_seize() -- Begin the process of advancing the scan to a new
885 : : * page. Other scans must wait until we call _bt_parallel_release()
886 : : * or _bt_parallel_done().
887 : : *
888 : : * The return value is true if we successfully seized the scan and false
889 : : * if we did not. The latter case occurs when no pages remain, or when
890 : : * another primitive index scan is scheduled that caller's backend cannot
891 : : * start just yet (only backends that call from _bt_first are capable of
892 : : * starting primitive index scans, which they indicate by passing first=true).
893 : : *
894 : : * If the return value is true, *next_scan_page returns the next page of the
895 : : * scan, and *last_curr_page returns the page that *next_scan_page came from.
896 : : * An invalid *next_scan_page means the scan hasn't yet started, or that
897 : : * caller needs to start the next primitive index scan (if it's the latter
898 : : * case we'll set so.needPrimScan).
899 : : *
900 : : * Callers should ignore the value of *next_scan_page and *last_curr_page if
901 : : * the return value is false.
902 : : */
903 : : bool
904 : 1104 : _bt_parallel_seize(IndexScanDesc scan, BlockNumber *next_scan_page,
905 : : BlockNumber *last_curr_page, bool first)
906 : : {
907 : 1104 : Relation rel = scan->indexRelation;
908 : 1104 : BTScanOpaque so = (BTScanOpaque) scan->opaque;
909 : 1104 : bool exit_loop = false,
910 : 1104 : status = true,
911 : 1104 : endscan = false;
912 : 1104 : ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
913 : : BTParallelScanDesc btscan;
914 : :
915 : 1104 : *next_scan_page = InvalidBlockNumber;
916 : 1104 : *last_curr_page = InvalidBlockNumber;
917 : :
918 : : /*
919 : : * Reset so->currPos, and initialize moreLeft/moreRight such that the next
920 : : * call to _bt_readnextpage treats this backend similarly to a serial
921 : : * backend that steps from *last_curr_page to *next_scan_page (unless this
922 : : * backend's so->currPos is initialized by _bt_readfirstpage before then).
923 : : */
924 : 1104 : BTScanPosInvalidate(so->currPos);
925 : 1104 : so->currPos.moreLeft = so->currPos.moreRight = true;
926 : :
927 [ + + ]: 1104 : if (first)
928 : : {
929 : : /*
930 : : * Initialize array related state when called from _bt_first, assuming
931 : : * that this will be the first primitive index scan for the scan
932 : : */
933 : 296 : so->needPrimScan = false;
934 : 296 : so->scanBehind = false;
935 : 296 : so->oppositeDirCheck = false;
936 : : }
937 : : else
938 : : {
939 : : /*
940 : : * Don't attempt to seize the scan when it requires another primitive
941 : : * index scan, since caller's backend cannot start it right now
942 : : */
943 [ - + ]: 808 : if (so->needPrimScan)
944 : 0 : return false;
945 : : }
946 : :
947 : 1104 : btscan = (BTParallelScanDesc) OffsetToPointer(parallel_scan,
948 : : parallel_scan->ps_offset_am);
949 : :
950 : : while (1)
951 : : {
952 : 1108 : LWLockAcquire(&btscan->btps_lock, LW_EXCLUSIVE);
953 : :
954 [ + + ]: 1108 : if (btscan->btps_pageStatus == BTPARALLEL_DONE)
955 : : {
956 : : /* We're done with this parallel index scan */
957 : 205 : status = false;
958 : : }
959 [ + + ]: 903 : else if (btscan->btps_pageStatus == BTPARALLEL_IDLE &&
960 [ + + ]: 817 : btscan->btps_nextScanPage == P_NONE)
961 : : {
962 : : /* End this parallel index scan */
963 : 9 : status = false;
964 : 9 : endscan = true;
965 : : }
966 [ + + ]: 894 : else if (btscan->btps_pageStatus == BTPARALLEL_NEED_PRIMSCAN)
967 : : {
968 : : Assert(so->numArrayKeys);
969 : :
970 [ + - ]: 24 : if (first)
971 : : {
972 : : /* Can start scheduled primitive scan right away, so do so */
973 : 24 : btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
974 : :
975 : : /* Restore scan's array keys from serialized values */
976 : 24 : _bt_parallel_restore_arrays(rel, btscan, so);
977 : 24 : exit_loop = true;
978 : : }
979 : : else
980 : : {
981 : : /*
982 : : * Don't attempt to seize the scan when it requires another
983 : : * primitive index scan, since caller's backend cannot start
984 : : * it right now
985 : : */
986 : 0 : status = false;
987 : : }
988 : :
989 : : /*
990 : : * Either way, update backend local state to indicate that a
991 : : * pending primitive scan is required
992 : : */
993 : 24 : so->needPrimScan = true;
994 : 24 : so->scanBehind = false;
995 : 24 : so->oppositeDirCheck = false;
996 : : }
997 [ + + ]: 870 : else if (btscan->btps_pageStatus != BTPARALLEL_ADVANCING)
998 : : {
999 : : /*
1000 : : * We have successfully seized control of the scan for the purpose
1001 : : * of advancing it to a new page!
1002 : : */
1003 : 866 : btscan->btps_pageStatus = BTPARALLEL_ADVANCING;
1004 : : Assert(btscan->btps_nextScanPage != P_NONE);
1005 : 866 : *next_scan_page = btscan->btps_nextScanPage;
1006 : 866 : *last_curr_page = btscan->btps_lastCurrPage;
1007 : 866 : exit_loop = true;
1008 : : }
1009 : 1108 : LWLockRelease(&btscan->btps_lock);
1010 [ + + + + ]: 1108 : if (exit_loop || !status)
1011 : : break;
1012 : 4 : ConditionVariableSleep(&btscan->btps_cv, WAIT_EVENT_BTREE_PAGE);
1013 : : }
1014 : 1104 : ConditionVariableCancelSleep();
1015 : :
1016 : : /* When the scan has reached the rightmost (or leftmost) page, end it */
1017 [ + + ]: 1104 : if (endscan)
1018 : 9 : _bt_parallel_done(scan);
1019 : :
1020 : 1104 : return status;
1021 : : }
1022 : :
1023 : : /*
1024 : : * _bt_parallel_release() -- Complete the process of advancing the scan to a
1025 : : * new page. We now have the new value btps_nextScanPage; another backend
1026 : : * can now begin advancing the scan.
1027 : : *
1028 : : * Callers whose scan uses array keys must save their curr_page argument so
1029 : : * that it can be passed to _bt_parallel_primscan_schedule, should caller
1030 : : * determine that another primitive index scan is required.
1031 : : *
1032 : : * If caller's next_scan_page is P_NONE, the scan has reached the index's
1033 : : * rightmost/leftmost page. This is treated as reaching the end of the scan
1034 : : * within _bt_parallel_seize.
1035 : : *
1036 : : * Note: unlike the serial case, parallel scans don't need to remember both
1037 : : * sibling links. next_scan_page is whichever link is next given the scan's
1038 : : * direction. That's all we'll ever need, since the direction of a parallel
1039 : : * scan can never change.
1040 : : */
1041 : : void
1042 : 890 : _bt_parallel_release(IndexScanDesc scan, BlockNumber next_scan_page,
1043 : : BlockNumber curr_page)
1044 : : {
1045 : 890 : ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
1046 : : BTParallelScanDesc btscan;
1047 : :
1048 : : Assert(BlockNumberIsValid(next_scan_page));
1049 : :
1050 : 890 : btscan = (BTParallelScanDesc) OffsetToPointer(parallel_scan,
1051 : : parallel_scan->ps_offset_am);
1052 : :
1053 : 890 : LWLockAcquire(&btscan->btps_lock, LW_EXCLUSIVE);
1054 : 890 : btscan->btps_nextScanPage = next_scan_page;
1055 : 890 : btscan->btps_lastCurrPage = curr_page;
1056 : 890 : btscan->btps_pageStatus = BTPARALLEL_IDLE;
1057 : 890 : LWLockRelease(&btscan->btps_lock);
1058 : 890 : ConditionVariableSignal(&btscan->btps_cv);
1059 : 890 : }
1060 : :
1061 : : /*
1062 : : * _bt_parallel_done() -- Mark the parallel scan as complete.
1063 : : *
1064 : : * When there are no pages left to scan, this function should be called to
1065 : : * notify other workers. Otherwise, they might wait forever for the scan to
1066 : : * advance to the next page.
1067 : : */
1068 : : void
1069 : 4621268 : _bt_parallel_done(IndexScanDesc scan)
1070 : : {
1071 : 4621268 : BTScanOpaque so = (BTScanOpaque) scan->opaque;
1072 : 4621268 : ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
1073 : : BTParallelScanDesc btscan;
1074 : 4621268 : bool status_changed = false;
1075 : :
1076 : : Assert(!BTScanPosIsValid(so->currPos));
1077 : :
1078 : : /* Do nothing, for non-parallel scans */
1079 [ + + ]: 4621268 : if (parallel_scan == NULL)
1080 : 4621161 : return;
1081 : :
1082 : : /*
1083 : : * Should not mark parallel scan done when there's still a pending
1084 : : * primitive index scan
1085 : : */
1086 [ + + ]: 107 : if (so->needPrimScan)
1087 : 24 : return;
1088 : :
1089 : 83 : btscan = (BTParallelScanDesc) OffsetToPointer(parallel_scan,
1090 : : parallel_scan->ps_offset_am);
1091 : :
1092 : : /*
1093 : : * Mark the parallel scan as done, unless some other process did so
1094 : : * already
1095 : : */
1096 : 83 : LWLockAcquire(&btscan->btps_lock, LW_EXCLUSIVE);
1097 : : Assert(btscan->btps_pageStatus != BTPARALLEL_NEED_PRIMSCAN);
1098 [ + + ]: 83 : if (btscan->btps_pageStatus != BTPARALLEL_DONE)
1099 : : {
1100 : 58 : btscan->btps_pageStatus = BTPARALLEL_DONE;
1101 : 58 : status_changed = true;
1102 : : }
1103 : 83 : LWLockRelease(&btscan->btps_lock);
1104 : :
1105 : : /* wake up all the workers associated with this parallel scan */
1106 [ + + ]: 83 : if (status_changed)
1107 : 58 : ConditionVariableBroadcast(&btscan->btps_cv);
1108 : : }
1109 : :
1110 : : /*
1111 : : * _bt_parallel_primscan_schedule() -- Schedule another primitive index scan.
1112 : : *
1113 : : * Caller passes the curr_page most recently passed to _bt_parallel_release
1114 : : * by its backend. Caller successfully schedules the next primitive index scan
1115 : : * if the shared parallel state hasn't been seized since caller's backend last
1116 : : * advanced the scan.
1117 : : */
1118 : : void
1119 : 24 : _bt_parallel_primscan_schedule(IndexScanDesc scan, BlockNumber curr_page)
1120 : : {
1121 : 24 : Relation rel = scan->indexRelation;
1122 : 24 : BTScanOpaque so = (BTScanOpaque) scan->opaque;
1123 : 24 : ParallelIndexScanDesc parallel_scan = scan->parallel_scan;
1124 : : BTParallelScanDesc btscan;
1125 : :
1126 : : Assert(so->numArrayKeys);
1127 : :
1128 : 24 : btscan = (BTParallelScanDesc) OffsetToPointer(parallel_scan,
1129 : : parallel_scan->ps_offset_am);
1130 : :
1131 : 24 : LWLockAcquire(&btscan->btps_lock, LW_EXCLUSIVE);
1132 [ + - ]: 24 : if (btscan->btps_lastCurrPage == curr_page &&
1133 [ + - ]: 24 : btscan->btps_pageStatus == BTPARALLEL_IDLE)
1134 : : {
1135 : 24 : btscan->btps_nextScanPage = InvalidBlockNumber;
1136 : 24 : btscan->btps_lastCurrPage = InvalidBlockNumber;
1137 : 24 : btscan->btps_pageStatus = BTPARALLEL_NEED_PRIMSCAN;
1138 : :
1139 : : /* Serialize scan's current array keys */
1140 : 24 : _bt_parallel_serialize_arrays(rel, btscan, so);
1141 : : }
1142 : 24 : LWLockRelease(&btscan->btps_lock);
1143 : 24 : }
1144 : :
1145 : : /*
1146 : : * Bulk deletion of all index entries pointing to a set of heap tuples.
1147 : : * The set of target tuples is specified via a callback routine that tells
1148 : : * whether any given heap tuple (identified by ItemPointer) is being deleted.
1149 : : *
1150 : : * Result: a palloc'd struct containing statistical info for VACUUM displays.
1151 : : */
1152 : : IndexBulkDeleteResult *
1153 : 1939 : btbulkdelete(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
1154 : : IndexBulkDeleteCallback callback, void *callback_state)
1155 : : {
1156 : 1939 : Relation rel = info->index;
1157 : : BTCycleId cycleid;
1158 : :
1159 : : /* allocate stats if first time through, else re-use existing struct */
1160 [ + + ]: 1939 : if (stats == NULL)
1161 : 1921 : stats = palloc0_object(IndexBulkDeleteResult);
1162 : :
1163 : : /* Establish the vacuum cycle ID to use for this scan */
1164 : : /* The ENSURE stuff ensures we clean up shared memory on failure */
1165 [ + + ]: 1939 : PG_ENSURE_ERROR_CLEANUP(_bt_end_vacuum_callback, PointerGetDatum(rel));
1166 : : {
1167 : 1939 : cycleid = _bt_start_vacuum(rel);
1168 : :
1169 : 1939 : btvacuumscan(info, stats, callback, callback_state, cycleid);
1170 : : }
1171 [ - + ]: 1939 : PG_END_ENSURE_ERROR_CLEANUP(_bt_end_vacuum_callback, PointerGetDatum(rel));
1172 : 1938 : _bt_end_vacuum(rel);
1173 : :
1174 : 1938 : return stats;
1175 : : }
1176 : :
1177 : : /*
1178 : : * Post-VACUUM cleanup.
1179 : : *
1180 : : * Result: a palloc'd struct containing statistical info for VACUUM displays.
1181 : : */
1182 : : IndexBulkDeleteResult *
1183 : 154043 : btvacuumcleanup(IndexVacuumInfo *info, IndexBulkDeleteResult *stats)
1184 : : {
1185 : : BlockNumber num_delpages;
1186 : :
1187 : : /* No-op in ANALYZE ONLY mode */
1188 [ + + ]: 154043 : if (info->analyze_only)
1189 : 10754 : return stats;
1190 : :
1191 : : /*
1192 : : * If btbulkdelete was called, we need not do anything (we just maintain
1193 : : * the information used within _bt_vacuum_needs_cleanup() by calling
1194 : : * _bt_set_cleanup_info() below).
1195 : : *
1196 : : * If btbulkdelete was _not_ called, then we have a choice to make: we
1197 : : * must decide whether or not a btvacuumscan() call is needed now (i.e.
1198 : : * whether the ongoing VACUUM operation can entirely avoid a physical scan
1199 : : * of the index). A call to _bt_vacuum_needs_cleanup() decides it for us
1200 : : * now.
1201 : : */
1202 [ + + ]: 143289 : if (stats == NULL)
1203 : : {
1204 : : /* Check if VACUUM operation can entirely avoid btvacuumscan() call */
1205 [ + + ]: 141749 : if (!_bt_vacuum_needs_cleanup(info->index))
1206 : 141741 : return NULL;
1207 : :
1208 : : /*
1209 : : * Since we aren't going to actually delete any leaf items, there's no
1210 : : * need to go through all the vacuum-cycle-ID pushups here.
1211 : : *
1212 : : * Posting list tuples are a source of inaccuracy for cleanup-only
1213 : : * scans. btvacuumscan() will assume that the number of index tuples
1214 : : * from each page can be used as num_index_tuples, even though
1215 : : * num_index_tuples is supposed to represent the number of TIDs in the
1216 : : * index. This naive approach can underestimate the number of tuples
1217 : : * in the index significantly.
1218 : : *
1219 : : * We handle the problem by making num_index_tuples an estimate in
1220 : : * cleanup-only case.
1221 : : */
1222 : 8 : stats = palloc0_object(IndexBulkDeleteResult);
1223 : 8 : btvacuumscan(info, stats, NULL, NULL, 0);
1224 : 8 : stats->estimated_count = true;
1225 : : }
1226 : :
1227 : : /*
1228 : : * Maintain num_delpages value in metapage for _bt_vacuum_needs_cleanup().
1229 : : *
1230 : : * num_delpages is the number of deleted pages now in the index that were
1231 : : * not safe to place in the FSM to be recycled just yet. num_delpages is
1232 : : * greater than 0 only when _bt_pagedel() actually deleted pages during
1233 : : * our call to btvacuumscan(). Even then, _bt_pendingfsm_finalize() must
1234 : : * have failed to place any newly deleted pages in the FSM just moments
1235 : : * ago. (Actually, there are edge cases where recycling of the current
1236 : : * VACUUM's newly deleted pages does not even become safe by the time the
1237 : : * next VACUUM comes around. See nbtree/README.)
1238 : : */
1239 : : Assert(stats->pages_deleted >= stats->pages_free);
1240 : 1548 : num_delpages = stats->pages_deleted - stats->pages_free;
1241 : 1548 : _bt_set_cleanup_info(info->index, num_delpages);
1242 : :
1243 : : /*
1244 : : * It's quite possible for us to be fooled by concurrent page splits into
1245 : : * double-counting some index tuples, so disbelieve any total that exceeds
1246 : : * the underlying heap's count ... if we know that accurately. Otherwise
1247 : : * this might just make matters worse.
1248 : : */
1249 [ + + ]: 1548 : if (!info->estimated_count)
1250 : : {
1251 [ + + ]: 1505 : if (stats->num_index_tuples > info->num_heap_tuples)
1252 : 37 : stats->num_index_tuples = info->num_heap_tuples;
1253 : : }
1254 : :
1255 : 1548 : return stats;
1256 : : }
1257 : :
1258 : : /*
1259 : : * btvacuumscan --- scan the index for VACUUMing purposes
1260 : : *
1261 : : * This combines the functions of looking for leaf tuples that are deletable
1262 : : * according to the vacuum callback, looking for empty pages that can be
1263 : : * deleted, and looking for old deleted pages that can be recycled. Both
1264 : : * btbulkdelete and btvacuumcleanup invoke this (the latter only if no
1265 : : * btbulkdelete call occurred and _bt_vacuum_needs_cleanup returned true).
1266 : : *
1267 : : * The caller is responsible for initially allocating/zeroing a stats struct
1268 : : * and for obtaining a vacuum cycle ID if necessary.
1269 : : */
1270 : : static void
1271 : 1947 : btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
1272 : : IndexBulkDeleteCallback callback, void *callback_state,
1273 : : BTCycleId cycleid)
1274 : : {
1275 : 1947 : Relation rel = info->index;
1276 : : BTVacState vstate;
1277 : : BlockNumber num_pages;
1278 : : bool needLock;
1279 : : BlockRangeReadStreamPrivate p;
1280 : 1947 : ReadStream *stream = NULL;
1281 : :
1282 : : /*
1283 : : * Reset fields that track information about the entire index now. This
1284 : : * avoids double-counting in the case where a single VACUUM command
1285 : : * requires multiple scans of the index.
1286 : : *
1287 : : * Avoid resetting the tuples_removed and pages_newly_deleted fields here,
1288 : : * since they track information about the VACUUM command, and so must last
1289 : : * across each call to btvacuumscan().
1290 : : *
1291 : : * (Note that pages_free is treated as state about the whole index, not
1292 : : * the current VACUUM. This is appropriate because RecordFreeIndexPage()
1293 : : * calls are idempotent, and get repeated for the same deleted pages in
1294 : : * some scenarios. The point for us is to track the number of recyclable
1295 : : * pages in the index at the end of the VACUUM command.)
1296 : : */
1297 : 1947 : stats->num_pages = 0;
1298 : 1947 : stats->num_index_tuples = 0;
1299 : 1947 : stats->pages_deleted = 0;
1300 : 1947 : stats->pages_free = 0;
1301 : :
1302 : : /* Set up info to pass down to btvacuumpage */
1303 : 1947 : vstate.info = info;
1304 : 1947 : vstate.stats = stats;
1305 : 1947 : vstate.callback = callback;
1306 : 1947 : vstate.callback_state = callback_state;
1307 : 1947 : vstate.cycleid = cycleid;
1308 : :
1309 : : /* Create a temporary memory context to run _bt_pagedel in */
1310 : 1947 : vstate.pagedelcontext = AllocSetContextCreate(CurrentMemoryContext,
1311 : : "_bt_pagedel",
1312 : : ALLOCSET_DEFAULT_SIZES);
1313 : :
1314 : : /* Initialize vstate fields used by _bt_pendingfsm_finalize */
1315 : 1947 : vstate.bufsize = 0;
1316 : 1947 : vstate.maxbufsize = 0;
1317 : 1947 : vstate.pendingpages = NULL;
1318 : 1947 : vstate.npendingpages = 0;
1319 : : /* Consider applying _bt_pendingfsm_finalize optimization */
1320 : 1947 : _bt_pendingfsm_init(rel, &vstate, (callback == NULL));
1321 : :
1322 : : /*
1323 : : * The outer loop iterates over all index pages except the metapage, in
1324 : : * physical order (we hope the kernel will cooperate in providing
1325 : : * read-ahead for speed). It is critical that we visit all leaf pages,
1326 : : * including ones added after we start the scan, else we might fail to
1327 : : * delete some deletable tuples. Hence, we must repeatedly check the
1328 : : * relation length. We must acquire the relation-extension lock while
1329 : : * doing so to avoid a race condition: if someone else is extending the
1330 : : * relation, there is a window where bufmgr/smgr have created a new
1331 : : * all-zero page but it hasn't yet been write-locked by _bt_getbuf(). If
1332 : : * we manage to scan such a page here, we'll improperly assume it can be
1333 : : * recycled. Taking the lock synchronizes things enough to prevent a
1334 : : * problem: either num_pages won't include the new page, or _bt_getbuf
1335 : : * already has write lock on the buffer and it will be fully initialized
1336 : : * before we can examine it. Also, we need not worry if a page is added
1337 : : * immediately after we look; the page splitting code already has
1338 : : * write-lock on the left page before it adds a right page, so we must
1339 : : * already have processed any tuples due to be moved into such a page.
1340 : : *
1341 : : * XXX: Now that new pages are locked with RBM_ZERO_AND_LOCK, I don't
1342 : : * think the use of the extension lock is still required.
1343 : : *
1344 : : * We can skip locking for new or temp relations, however, since no one
1345 : : * else could be accessing them.
1346 : : */
1347 [ + + + - ]: 1947 : needLock = !RELATION_IS_LOCAL(rel);
1348 : :
1349 : 1947 : p.current_blocknum = BTREE_METAPAGE + 1;
1350 : :
1351 : : /*
1352 : : * It is safe to use batchmode as block_range_read_stream_cb takes no
1353 : : * locks.
1354 : : */
1355 : 1947 : stream = read_stream_begin_relation(READ_STREAM_MAINTENANCE |
1356 : : READ_STREAM_FULL |
1357 : : READ_STREAM_USE_BATCHING,
1358 : : info->strategy,
1359 : : rel,
1360 : : MAIN_FORKNUM,
1361 : : block_range_read_stream_cb,
1362 : : &p,
1363 : : 0);
1364 : : for (;;)
1365 : : {
1366 : : /* Get the current relation length */
1367 [ + + ]: 3703 : if (needLock)
1368 : 3701 : LockRelationForExtension(rel, ExclusiveLock);
1369 : 3703 : num_pages = RelationGetNumberOfBlocks(rel);
1370 [ + + ]: 3703 : if (needLock)
1371 : 3701 : UnlockRelationForExtension(rel, ExclusiveLock);
1372 : :
1373 [ + - ]: 3703 : if (info->report_progress)
1374 : 3703 : pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL,
1375 : : num_pages);
1376 : :
1377 : : /* Quit if we've scanned the whole relation */
1378 [ + + ]: 3703 : if (p.current_blocknum >= num_pages)
1379 : 1946 : break;
1380 : :
1381 : 1757 : p.last_exclusive = num_pages;
1382 : :
1383 : : /* Iterate over pages, then loop back to recheck relation length */
1384 : : while (true)
1385 : 19344 : {
1386 : : BlockNumber current_block;
1387 : : Buffer buf;
1388 : :
1389 : : /* call vacuum_delay_point while not holding any buffer lock */
1390 : 21101 : vacuum_delay_point(false);
1391 : :
1392 : 21101 : buf = read_stream_next_buffer(stream, NULL);
1393 : :
1394 [ + + ]: 21101 : if (!BufferIsValid(buf))
1395 : 1756 : break;
1396 : :
1397 : 19345 : current_block = btvacuumpage(&vstate, buf);
1398 : :
1399 [ + - ]: 19344 : if (info->report_progress)
1400 : 19344 : pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE,
1401 : : current_block);
1402 : : }
1403 : :
1404 : : /*
1405 : : * We have to reset the read stream to use it again. After returning
1406 : : * InvalidBuffer, the read stream API won't invoke our callback again
1407 : : * until the stream has been reset.
1408 : : */
1409 : 1756 : read_stream_reset(stream);
1410 : : }
1411 : :
1412 : 1946 : read_stream_end(stream);
1413 : :
1414 : : /* Set statistics num_pages field to final size of index */
1415 : 1946 : stats->num_pages = num_pages;
1416 : :
1417 : 1946 : MemoryContextDelete(vstate.pagedelcontext);
1418 : :
1419 : : /*
1420 : : * If there were any calls to _bt_pagedel() during scan of the index then
1421 : : * see if any of the resulting pages can be placed in the FSM now. When
1422 : : * it's not safe we'll have to leave it up to a future VACUUM operation.
1423 : : *
1424 : : * Finally, if we placed any pages in the FSM (either just now or during
1425 : : * the scan), forcibly update the upper-level FSM pages to ensure that
1426 : : * searchers can find them.
1427 : : */
1428 : 1946 : _bt_pendingfsm_finalize(rel, &vstate);
1429 [ + + ]: 1946 : if (stats->pages_free > 0)
1430 : 63 : IndexFreeSpaceMapVacuum(rel);
1431 : 1946 : }
1432 : :
1433 : : /*
1434 : : * btvacuumpage --- VACUUM one page
1435 : : *
1436 : : * This processes a single page for btvacuumscan(). In some cases we must
1437 : : * backtrack to re-examine and VACUUM pages that were on buf's page during
1438 : : * a previous call here. This is how we handle page splits (that happened
1439 : : * after our cycleid was acquired) whose right half page happened to reuse
1440 : : * a block that we might have processed at some point before it was
1441 : : * recycled (i.e. before the page split).
1442 : : *
1443 : : * Returns BlockNumber of a scanned page (not backtracked).
1444 : : */
1445 : : static BlockNumber
1446 : 19345 : btvacuumpage(BTVacState *vstate, Buffer buf)
1447 : : {
1448 : 19345 : IndexVacuumInfo *info = vstate->info;
1449 : 19345 : IndexBulkDeleteResult *stats = vstate->stats;
1450 : 19345 : IndexBulkDeleteCallback callback = vstate->callback;
1451 : 19345 : void *callback_state = vstate->callback_state;
1452 : 19345 : Relation rel = info->index;
1453 : 19345 : Relation heaprel = info->heaprel;
1454 : : bool attempt_pagedel;
1455 : : BlockNumber blkno,
1456 : : backtrack_to;
1457 : 19345 : BlockNumber scanblkno = BufferGetBlockNumber(buf);
1458 : : Page page;
1459 : : BTPageOpaque opaque;
1460 : :
1461 : 19345 : blkno = scanblkno;
1462 : :
1463 : 19345 : backtrack:
1464 : :
1465 : 19345 : attempt_pagedel = false;
1466 : 19345 : backtrack_to = P_NONE;
1467 : :
1468 : 19345 : _bt_lockbuf(rel, buf, BT_READ);
1469 : 19345 : page = BufferGetPage(buf);
1470 : 19345 : opaque = NULL;
1471 [ + - ]: 19345 : if (!PageIsNew(page))
1472 : : {
1473 : 19345 : _bt_checkpage(rel, buf);
1474 : 19345 : opaque = BTPageGetOpaque(page);
1475 : : }
1476 : :
1477 : : Assert(blkno <= scanblkno);
1478 [ - + ]: 19345 : if (blkno != scanblkno)
1479 : : {
1480 : : /*
1481 : : * We're backtracking.
1482 : : *
1483 : : * We followed a right link to a sibling leaf page (a page that
1484 : : * happens to be from a block located before scanblkno). The only
1485 : : * case we want to do anything with is a live leaf page having the
1486 : : * current vacuum cycle ID.
1487 : : *
1488 : : * The page had better be in a state that's consistent with what we
1489 : : * expect. Check for conditions that imply corruption in passing. It
1490 : : * can't be half-dead because only an interrupted VACUUM process can
1491 : : * leave pages in that state, so we'd definitely have dealt with it
1492 : : * back when the page was the scanblkno page (half-dead pages are
1493 : : * always marked fully deleted by _bt_pagedel(), barring corruption).
1494 : : */
1495 [ # # # # : 0 : if (!opaque || !P_ISLEAF(opaque) || P_ISHALFDEAD(opaque))
# # ]
1496 : : {
1497 : : Assert(false);
1498 [ # # ]: 0 : ereport(LOG,
1499 : : (errcode(ERRCODE_INDEX_CORRUPTED),
1500 : : errmsg_internal("right sibling %u of scanblkno %u unexpectedly in an inconsistent state in index \"%s\"",
1501 : : blkno, scanblkno, RelationGetRelationName(rel))));
1502 : 0 : _bt_relbuf(rel, buf);
1503 : 0 : return scanblkno;
1504 : : }
1505 : :
1506 : : /*
1507 : : * We may have already processed the page in an earlier call, when the
1508 : : * page was scanblkno. This happens when the leaf page split occurred
1509 : : * after the scan began, but before the right sibling page became the
1510 : : * scanblkno.
1511 : : *
1512 : : * Page may also have been deleted by current btvacuumpage() call,
1513 : : * since _bt_pagedel() sometimes deletes the right sibling page of
1514 : : * scanblkno in passing (it does so after we decided where to
1515 : : * backtrack to). We don't need to process this page as a deleted
1516 : : * page a second time now (in fact, it would be wrong to count it as a
1517 : : * deleted page in the bulk delete statistics a second time).
1518 : : */
1519 [ # # # # ]: 0 : if (opaque->btpo_cycleid != vstate->cycleid || P_ISDELETED(opaque))
1520 : : {
1521 : : /* Done with current scanblkno (and all lower split pages) */
1522 : 0 : _bt_relbuf(rel, buf);
1523 : 0 : return scanblkno;
1524 : : }
1525 : : }
1526 : :
1527 [ + - + + ]: 19345 : if (!opaque || BTPageIsRecyclable(page, heaprel))
1528 : : {
1529 : : /* Okay to recycle this page (which could be leaf or internal) */
1530 : 1257 : RecordFreeIndexPage(rel, blkno);
1531 : 1257 : stats->pages_deleted++;
1532 : 1257 : stats->pages_free++;
1533 : : }
1534 [ + + ]: 18088 : else if (P_ISDELETED(opaque))
1535 : : {
1536 : : /*
1537 : : * Already deleted page (which could be leaf or internal). Can't
1538 : : * recycle yet.
1539 : : */
1540 : 208 : stats->pages_deleted++;
1541 : : }
1542 [ + + ]: 17880 : else if (P_ISHALFDEAD(opaque))
1543 : : {
1544 : : /* Half-dead leaf page (from interrupted VACUUM) -- finish deleting */
1545 : 1 : attempt_pagedel = true;
1546 : :
1547 : : /*
1548 : : * _bt_pagedel() will increment both pages_newly_deleted and
1549 : : * pages_deleted stats in all cases (barring corruption)
1550 : : */
1551 : : }
1552 [ + + ]: 17879 : else if (P_ISLEAF(opaque))
1553 : : {
1554 : : OffsetNumber deletable[MaxIndexTuplesPerPage];
1555 : : int ndeletable;
1556 : : BTVacuumPosting updatable[MaxIndexTuplesPerPage];
1557 : : int nupdatable;
1558 : : OffsetNumber offnum,
1559 : : minoff,
1560 : : maxoff;
1561 : : int nhtidsdead,
1562 : : nhtidslive;
1563 : :
1564 : : /*
1565 : : * Trade in the initial read lock for a full cleanup lock on this
1566 : : * page. We must get such a lock on every leaf page over the course
1567 : : * of the vacuum scan, whether or not it actually contains any
1568 : : * deletable tuples --- see nbtree/README.
1569 : : */
1570 : 16827 : _bt_upgradelockbufcleanup(rel, buf);
1571 : :
1572 : : /*
1573 : : * Check whether we need to backtrack to earlier pages. What we are
1574 : : * concerned about is a page split that happened since we started the
1575 : : * vacuum scan. If the split moved tuples on the right half of the
1576 : : * split (i.e. the tuples that sort high) to a block that we already
1577 : : * passed over, then we might have missed the tuples. We need to
1578 : : * backtrack now. (Must do this before possibly clearing btpo_cycleid
1579 : : * or deleting scanblkno page below!)
1580 : : */
1581 [ + + ]: 16827 : if (vstate->cycleid != 0 &&
1582 [ - + ]: 16747 : opaque->btpo_cycleid == vstate->cycleid &&
1583 [ # # ]: 0 : !(opaque->btpo_flags & BTP_SPLIT_END) &&
1584 [ # # ]: 0 : !P_RIGHTMOST(opaque) &&
1585 [ # # ]: 0 : opaque->btpo_next < scanblkno)
1586 : 0 : backtrack_to = opaque->btpo_next;
1587 : :
1588 : 16827 : ndeletable = 0;
1589 : 16827 : nupdatable = 0;
1590 [ + + ]: 16827 : minoff = P_FIRSTDATAKEY(opaque);
1591 : 16827 : maxoff = PageGetMaxOffsetNumber(page);
1592 : 16827 : nhtidsdead = 0;
1593 : 16827 : nhtidslive = 0;
1594 [ + + ]: 16827 : if (callback)
1595 : : {
1596 : : /* btbulkdelete callback tells us what to delete (or update) */
1597 : 16747 : for (offnum = minoff;
1598 [ + + ]: 3401275 : offnum <= maxoff;
1599 : 3384528 : offnum = OffsetNumberNext(offnum))
1600 : : {
1601 : : IndexTuple itup;
1602 : :
1603 : 3384528 : itup = (IndexTuple) PageGetItem(page,
1604 : 3384528 : PageGetItemId(page, offnum));
1605 : :
1606 : : Assert(!BTreeTupleIsPivot(itup));
1607 [ + + ]: 3384528 : if (!BTreeTupleIsPosting(itup))
1608 : : {
1609 : : /* Regular tuple, standard table TID representation */
1610 [ + + ]: 3224111 : if (callback(&itup->t_tid, callback_state))
1611 : : {
1612 : 1220597 : deletable[ndeletable++] = offnum;
1613 : 1220597 : nhtidsdead++;
1614 : : }
1615 : : else
1616 : 2003514 : nhtidslive++;
1617 : : }
1618 : : else
1619 : : {
1620 : : BTVacuumPosting vacposting;
1621 : : int nremaining;
1622 : :
1623 : : /* Posting list tuple */
1624 : 160417 : vacposting = btreevacuumposting(vstate, itup, offnum,
1625 : : &nremaining);
1626 [ + + ]: 160417 : if (vacposting == NULL)
1627 : : {
1628 : : /*
1629 : : * All table TIDs from the posting tuple remain, so no
1630 : : * delete or update required
1631 : : */
1632 : : Assert(nremaining == BTreeTupleGetNPosting(itup));
1633 : : }
1634 [ + + ]: 124151 : else if (nremaining > 0)
1635 : : {
1636 : :
1637 : : /*
1638 : : * Store metadata about posting list tuple in
1639 : : * updatable array for entire page. Existing tuple
1640 : : * will be updated during the later call to
1641 : : * _bt_delitems_vacuum().
1642 : : */
1643 : : Assert(nremaining < BTreeTupleGetNPosting(itup));
1644 : 83454 : updatable[nupdatable++] = vacposting;
1645 : 83454 : nhtidsdead += BTreeTupleGetNPosting(itup) - nremaining;
1646 : : }
1647 : : else
1648 : : {
1649 : : /*
1650 : : * All table TIDs from the posting list must be
1651 : : * deleted. We'll delete the index tuple completely
1652 : : * (no update required).
1653 : : */
1654 : : Assert(nremaining == 0);
1655 : 40697 : deletable[ndeletable++] = offnum;
1656 : 40697 : nhtidsdead += BTreeTupleGetNPosting(itup);
1657 : 40697 : pfree(vacposting);
1658 : : }
1659 : :
1660 : 160417 : nhtidslive += nremaining;
1661 : : }
1662 : : }
1663 : : }
1664 : :
1665 : : /*
1666 : : * Apply any needed deletes or updates. We issue just one
1667 : : * _bt_delitems_vacuum() call per page, so as to minimize WAL traffic.
1668 : : */
1669 [ + + + + ]: 16827 : if (ndeletable > 0 || nupdatable > 0)
1670 : : {
1671 : : Assert(nhtidsdead >= ndeletable + nupdatable);
1672 : 10190 : _bt_delitems_vacuum(rel, buf, deletable, ndeletable, updatable,
1673 : : nupdatable);
1674 : :
1675 : 10190 : stats->tuples_removed += nhtidsdead;
1676 : : /* must recompute maxoff */
1677 : 10190 : maxoff = PageGetMaxOffsetNumber(page);
1678 : :
1679 : : /* can't leak memory here */
1680 [ + + ]: 93644 : for (int i = 0; i < nupdatable; i++)
1681 : 83454 : pfree(updatable[i]);
1682 : : }
1683 : : else
1684 : : {
1685 : : /*
1686 : : * If the leaf page has been split during this vacuum cycle, it
1687 : : * seems worth expending a write to clear btpo_cycleid even if we
1688 : : * don't have any deletions to do. (If we do, _bt_delitems_vacuum
1689 : : * takes care of this.) This ensures we won't process the page
1690 : : * again.
1691 : : *
1692 : : * We treat this like a hint-bit update because there's no need to
1693 : : * WAL-log it.
1694 : : */
1695 : : Assert(nhtidsdead == 0);
1696 [ + + ]: 6637 : if (vstate->cycleid != 0 &&
1697 [ - + ]: 6557 : opaque->btpo_cycleid == vstate->cycleid)
1698 : : {
1699 : 0 : opaque->btpo_cycleid = 0;
1700 : 0 : MarkBufferDirtyHint(buf, true);
1701 : : }
1702 : : }
1703 : :
1704 : : /*
1705 : : * If the leaf page is now empty, try to delete it; else count the
1706 : : * live tuples (live table TIDs in posting lists are counted as
1707 : : * separate live tuples). We don't delete when backtracking, though,
1708 : : * since that would require teaching _bt_pagedel() about backtracking
1709 : : * (doesn't seem worth adding more complexity to deal with that).
1710 : : *
1711 : : * We don't count the number of live TIDs during cleanup-only calls to
1712 : : * btvacuumscan (i.e. when callback is not set). We count the number
1713 : : * of index tuples directly instead. This avoids the expense of
1714 : : * directly examining all of the tuples on each page. VACUUM will
1715 : : * treat num_index_tuples as an estimate in cleanup-only case, so it
1716 : : * doesn't matter that this underestimates num_index_tuples
1717 : : * significantly in some cases.
1718 : : */
1719 [ + + ]: 16827 : if (minoff > maxoff)
1720 : 3985 : attempt_pagedel = (blkno == scanblkno);
1721 [ + + ]: 12842 : else if (callback)
1722 : 12766 : stats->num_index_tuples += nhtidslive;
1723 : : else
1724 : 76 : stats->num_index_tuples += maxoff - minoff + 1;
1725 : :
1726 : : Assert(!attempt_pagedel || nhtidslive == 0);
1727 : : }
1728 : :
1729 [ + + ]: 19345 : if (attempt_pagedel)
1730 : : {
1731 : : MemoryContext oldcontext;
1732 : :
1733 : : /* Run pagedel in a temp context to avoid memory leakage */
1734 : 3986 : MemoryContextReset(vstate->pagedelcontext);
1735 : 3986 : oldcontext = MemoryContextSwitchTo(vstate->pagedelcontext);
1736 : :
1737 : : /*
1738 : : * _bt_pagedel maintains the bulk delete stats on our behalf;
1739 : : * pages_newly_deleted and pages_deleted are likely to be incremented
1740 : : * during call
1741 : : */
1742 : : Assert(blkno == scanblkno);
1743 : 3986 : _bt_pagedel(rel, buf, vstate);
1744 : :
1745 : 3985 : MemoryContextSwitchTo(oldcontext);
1746 : : /* pagedel released buffer, so we shouldn't */
1747 : : }
1748 : : else
1749 : 15359 : _bt_relbuf(rel, buf);
1750 : :
1751 [ - + ]: 19344 : if (backtrack_to != P_NONE)
1752 : : {
1753 : 0 : blkno = backtrack_to;
1754 : :
1755 : : /* check for vacuum delay while not holding any buffer lock */
1756 : 0 : vacuum_delay_point(false);
1757 : :
1758 : : /*
1759 : : * We can't use _bt_getbuf() here because it always applies
1760 : : * _bt_checkpage(), which will barf on an all-zero page. We want to
1761 : : * recycle all-zero pages, not fail. Also, we want to use a
1762 : : * nondefault buffer access strategy.
1763 : : */
1764 : 0 : buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
1765 : : info->strategy);
1766 : 0 : goto backtrack;
1767 : : }
1768 : :
1769 : 19344 : return scanblkno;
1770 : : }
1771 : :
1772 : : /*
1773 : : * btreevacuumposting --- determine TIDs still needed in posting list
1774 : : *
1775 : : * Returns metadata describing how to build replacement tuple without the TIDs
1776 : : * that VACUUM needs to delete. Returned value is NULL in the common case
1777 : : * where no changes are needed to caller's posting list tuple (we avoid
1778 : : * allocating memory here as an optimization).
1779 : : *
1780 : : * The number of TIDs that should remain in the posting list tuple is set for
1781 : : * caller in *nremaining.
1782 : : */
1783 : : static BTVacuumPosting
1784 : 160417 : btreevacuumposting(BTVacState *vstate, IndexTuple posting,
1785 : : OffsetNumber updatedoffset, int *nremaining)
1786 : : {
1787 : 160417 : int live = 0;
1788 : 160417 : int nitem = BTreeTupleGetNPosting(posting);
1789 : 160417 : ItemPointer items = BTreeTupleGetPosting(posting);
1790 : 160417 : BTVacuumPosting vacposting = NULL;
1791 : :
1792 [ + + ]: 795423 : for (int i = 0; i < nitem; i++)
1793 : : {
1794 [ + + ]: 635006 : if (!vstate->callback(items + i, vstate->callback_state))
1795 : : {
1796 : : /* Live table TID */
1797 : 318992 : live++;
1798 : : }
1799 [ + + ]: 316014 : else if (vacposting == NULL)
1800 : : {
1801 : : /*
1802 : : * First dead table TID encountered.
1803 : : *
1804 : : * It's now clear that we need to delete one or more dead table
1805 : : * TIDs, so start maintaining metadata describing how to update
1806 : : * existing posting list tuple.
1807 : : */
1808 : 124151 : vacposting = palloc(offsetof(BTVacuumPostingData, deletetids) +
1809 : : nitem * sizeof(uint16));
1810 : :
1811 : 124151 : vacposting->itup = posting;
1812 : 124151 : vacposting->updatedoffset = updatedoffset;
1813 : 124151 : vacposting->ndeletedtids = 0;
1814 : 124151 : vacposting->deletetids[vacposting->ndeletedtids++] = i;
1815 : : }
1816 : : else
1817 : : {
1818 : : /* Second or subsequent dead table TID */
1819 : 191863 : vacposting->deletetids[vacposting->ndeletedtids++] = i;
1820 : : }
1821 : : }
1822 : :
1823 : 160417 : *nremaining = live;
1824 : 160417 : return vacposting;
1825 : : }
1826 : :
1827 : : /*
1828 : : * btcanreturn() -- Check whether btree indexes support index-only scans.
1829 : : *
1830 : : * btrees always do, so this is trivial.
1831 : : */
1832 : : bool
1833 : 883956 : btcanreturn(Relation index, int attno)
1834 : : {
1835 : 883956 : return true;
1836 : : }
1837 : :
1838 : : /*
1839 : : * btgettreeheight() -- Compute tree height for use by btcostestimate().
1840 : : */
1841 : : int
1842 : 582723 : btgettreeheight(Relation rel)
1843 : : {
1844 : 582723 : return _bt_getrootheight(rel);
1845 : : }
1846 : :
1847 : : CompareType
1848 : 0 : bttranslatestrategy(StrategyNumber strategy, Oid opfamily)
1849 : : {
1850 [ # # # # : 0 : switch (strategy)
# # ]
1851 : : {
1852 : 0 : case BTLessStrategyNumber:
1853 : 0 : return COMPARE_LT;
1854 : 0 : case BTLessEqualStrategyNumber:
1855 : 0 : return COMPARE_LE;
1856 : 0 : case BTEqualStrategyNumber:
1857 : 0 : return COMPARE_EQ;
1858 : 0 : case BTGreaterEqualStrategyNumber:
1859 : 0 : return COMPARE_GE;
1860 : 0 : case BTGreaterStrategyNumber:
1861 : 0 : return COMPARE_GT;
1862 : 0 : default:
1863 : 0 : return COMPARE_INVALID;
1864 : : }
1865 : : }
1866 : :
1867 : : StrategyNumber
1868 : 0 : bttranslatecmptype(CompareType cmptype, Oid opfamily)
1869 : : {
1870 [ # # # # : 0 : switch (cmptype)
# # ]
1871 : : {
1872 : 0 : case COMPARE_LT:
1873 : 0 : return BTLessStrategyNumber;
1874 : 0 : case COMPARE_LE:
1875 : 0 : return BTLessEqualStrategyNumber;
1876 : 0 : case COMPARE_EQ:
1877 : 0 : return BTEqualStrategyNumber;
1878 : 0 : case COMPARE_GE:
1879 : 0 : return BTGreaterEqualStrategyNumber;
1880 : 0 : case COMPARE_GT:
1881 : 0 : return BTGreaterStrategyNumber;
1882 : 0 : default:
1883 : 0 : return InvalidStrategy;
1884 : : }
1885 : : }
|