/src/postgres/src/backend/access/nbtree/nbtpage.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * nbtpage.c |
4 | | * BTree-specific page management code for the Postgres btree access |
5 | | * method. |
6 | | * |
7 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
8 | | * Portions Copyright (c) 1994, Regents of the University of California |
9 | | * |
10 | | * |
11 | | * IDENTIFICATION |
12 | | * src/backend/access/nbtree/nbtpage.c |
13 | | * |
14 | | * NOTES |
15 | | * Postgres btree pages look like ordinary relation pages. The opaque |
16 | | * data at high addresses includes pointers to left and right siblings |
17 | | * and flag data describing page state. The first page in a btree, page |
18 | | * zero, is special -- it stores meta-information describing the tree. |
19 | | * Pages one and higher store the actual tree data. |
20 | | * |
21 | | *------------------------------------------------------------------------- |
22 | | */ |
23 | | #include "postgres.h" |
24 | | |
25 | | #include "access/nbtree.h" |
26 | | #include "access/nbtxlog.h" |
27 | | #include "access/tableam.h" |
28 | | #include "access/transam.h" |
29 | | #include "access/xlog.h" |
30 | | #include "access/xloginsert.h" |
31 | | #include "common/int.h" |
32 | | #include "miscadmin.h" |
33 | | #include "storage/indexfsm.h" |
34 | | #include "storage/predicate.h" |
35 | | #include "storage/procarray.h" |
36 | | #include "utils/injection_point.h" |
37 | | #include "utils/memdebug.h" |
38 | | #include "utils/memutils.h" |
39 | | #include "utils/snapmgr.h" |
40 | | |
41 | | static BTMetaPageData *_bt_getmeta(Relation rel, Buffer metabuf); |
42 | | static void _bt_delitems_delete(Relation rel, Buffer buf, |
43 | | TransactionId snapshotConflictHorizon, |
44 | | bool isCatalogRel, |
45 | | OffsetNumber *deletable, int ndeletable, |
46 | | BTVacuumPosting *updatable, int nupdatable); |
47 | | static char *_bt_delitems_update(BTVacuumPosting *updatable, int nupdatable, |
48 | | OffsetNumber *updatedoffsets, |
49 | | Size *updatedbuflen, bool needswal); |
50 | | static bool _bt_mark_page_halfdead(Relation rel, Relation heaprel, |
51 | | Buffer leafbuf, BTStack stack); |
52 | | static bool _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, |
53 | | BlockNumber scanblkno, |
54 | | bool *rightsib_empty, |
55 | | BTVacState *vstate); |
56 | | static bool _bt_lock_subtree_parent(Relation rel, Relation heaprel, |
57 | | BlockNumber child, BTStack stack, |
58 | | Buffer *subtreeparent, OffsetNumber *poffset, |
59 | | BlockNumber *topparent, |
60 | | BlockNumber *topparentrightsib); |
61 | | static void _bt_pendingfsm_add(BTVacState *vstate, BlockNumber target, |
62 | | FullTransactionId safexid); |
63 | | |
64 | | /* |
65 | | * _bt_initmetapage() -- Fill a page buffer with a correct metapage image |
66 | | */ |
67 | | void |
68 | | _bt_initmetapage(Page page, BlockNumber rootbknum, uint32 level, |
69 | | bool allequalimage) |
70 | 0 | { |
71 | 0 | BTMetaPageData *metad; |
72 | 0 | BTPageOpaque metaopaque; |
73 | |
|
74 | 0 | _bt_pageinit(page, BLCKSZ); |
75 | |
|
76 | 0 | metad = BTPageGetMeta(page); |
77 | 0 | metad->btm_magic = BTREE_MAGIC; |
78 | 0 | metad->btm_version = BTREE_VERSION; |
79 | 0 | metad->btm_root = rootbknum; |
80 | 0 | metad->btm_level = level; |
81 | 0 | metad->btm_fastroot = rootbknum; |
82 | 0 | metad->btm_fastlevel = level; |
83 | 0 | metad->btm_last_cleanup_num_delpages = 0; |
84 | 0 | metad->btm_last_cleanup_num_heap_tuples = -1.0; |
85 | 0 | metad->btm_allequalimage = allequalimage; |
86 | |
|
87 | 0 | metaopaque = BTPageGetOpaque(page); |
88 | 0 | metaopaque->btpo_flags = BTP_META; |
89 | | |
90 | | /* |
91 | | * Set pd_lower just past the end of the metadata. This is essential, |
92 | | * because without doing so, metadata will be lost if xlog.c compresses |
93 | | * the page. |
94 | | */ |
95 | 0 | ((PageHeader) page)->pd_lower = |
96 | 0 | ((char *) metad + sizeof(BTMetaPageData)) - (char *) page; |
97 | 0 | } |
98 | | |
99 | | /* |
100 | | * _bt_upgrademetapage() -- Upgrade a meta-page from an old format to version |
101 | | * 3, the last version that can be updated without broadly affecting |
102 | | * on-disk compatibility. (A REINDEX is required to upgrade to v4.) |
103 | | * |
104 | | * This routine does purely in-memory image upgrade. Caller is |
105 | | * responsible for locking, WAL-logging etc. |
106 | | */ |
107 | | void |
108 | | _bt_upgrademetapage(Page page) |
109 | 0 | { |
110 | 0 | BTMetaPageData *metad; |
111 | 0 | BTPageOpaque metaopaque PG_USED_FOR_ASSERTS_ONLY; |
112 | |
|
113 | 0 | metad = BTPageGetMeta(page); |
114 | 0 | metaopaque = BTPageGetOpaque(page); |
115 | | |
116 | | /* It must be really a meta page of upgradable version */ |
117 | 0 | Assert(metaopaque->btpo_flags & BTP_META); |
118 | 0 | Assert(metad->btm_version < BTREE_NOVAC_VERSION); |
119 | 0 | Assert(metad->btm_version >= BTREE_MIN_VERSION); |
120 | | |
121 | | /* Set version number and fill extra fields added into version 3 */ |
122 | 0 | metad->btm_version = BTREE_NOVAC_VERSION; |
123 | 0 | metad->btm_last_cleanup_num_delpages = 0; |
124 | 0 | metad->btm_last_cleanup_num_heap_tuples = -1.0; |
125 | | /* Only a REINDEX can set this field */ |
126 | 0 | Assert(!metad->btm_allequalimage); |
127 | 0 | metad->btm_allequalimage = false; |
128 | | |
129 | | /* Adjust pd_lower (see _bt_initmetapage() for details) */ |
130 | 0 | ((PageHeader) page)->pd_lower = |
131 | 0 | ((char *) metad + sizeof(BTMetaPageData)) - (char *) page; |
132 | 0 | } |
133 | | |
134 | | /* |
135 | | * Get metadata from share-locked buffer containing metapage, while performing |
136 | | * standard sanity checks. |
137 | | * |
138 | | * Callers that cache data returned here in local cache should note that an |
139 | | * on-the-fly upgrade using _bt_upgrademetapage() can change the version field |
140 | | * and BTREE_NOVAC_VERSION specific fields without invalidating local cache. |
141 | | */ |
142 | | static BTMetaPageData * |
143 | | _bt_getmeta(Relation rel, Buffer metabuf) |
144 | 0 | { |
145 | 0 | Page metapg; |
146 | 0 | BTPageOpaque metaopaque; |
147 | 0 | BTMetaPageData *metad; |
148 | |
|
149 | 0 | metapg = BufferGetPage(metabuf); |
150 | 0 | metaopaque = BTPageGetOpaque(metapg); |
151 | 0 | metad = BTPageGetMeta(metapg); |
152 | | |
153 | | /* sanity-check the metapage */ |
154 | 0 | if (!P_ISMETA(metaopaque) || |
155 | 0 | metad->btm_magic != BTREE_MAGIC) |
156 | 0 | ereport(ERROR, |
157 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
158 | 0 | errmsg("index \"%s\" is not a btree", |
159 | 0 | RelationGetRelationName(rel)))); |
160 | | |
161 | 0 | if (metad->btm_version < BTREE_MIN_VERSION || |
162 | 0 | metad->btm_version > BTREE_VERSION) |
163 | 0 | ereport(ERROR, |
164 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
165 | 0 | errmsg("version mismatch in index \"%s\": file version %d, " |
166 | 0 | "current version %d, minimal supported version %d", |
167 | 0 | RelationGetRelationName(rel), |
168 | 0 | metad->btm_version, BTREE_VERSION, BTREE_MIN_VERSION))); |
169 | | |
170 | 0 | return metad; |
171 | 0 | } |
172 | | |
173 | | /* |
174 | | * _bt_vacuum_needs_cleanup() -- Checks if index needs cleanup |
175 | | * |
176 | | * Called by btvacuumcleanup when btbulkdelete was never called because no |
177 | | * index tuples needed to be deleted. |
178 | | */ |
179 | | bool |
180 | | _bt_vacuum_needs_cleanup(Relation rel) |
181 | 0 | { |
182 | 0 | Buffer metabuf; |
183 | 0 | Page metapg; |
184 | 0 | BTMetaPageData *metad; |
185 | 0 | uint32 btm_version; |
186 | 0 | BlockNumber prev_num_delpages; |
187 | | |
188 | | /* |
189 | | * Copy details from metapage to local variables quickly. |
190 | | * |
191 | | * Note that we deliberately avoid using cached version of metapage here. |
192 | | */ |
193 | 0 | metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ); |
194 | 0 | metapg = BufferGetPage(metabuf); |
195 | 0 | metad = BTPageGetMeta(metapg); |
196 | 0 | btm_version = metad->btm_version; |
197 | |
|
198 | 0 | if (btm_version < BTREE_NOVAC_VERSION) |
199 | 0 | { |
200 | | /* |
201 | | * Metapage needs to be dynamically upgraded to store fields that are |
202 | | * only present when btm_version >= BTREE_NOVAC_VERSION |
203 | | */ |
204 | 0 | _bt_relbuf(rel, metabuf); |
205 | 0 | return true; |
206 | 0 | } |
207 | | |
208 | 0 | prev_num_delpages = metad->btm_last_cleanup_num_delpages; |
209 | 0 | _bt_relbuf(rel, metabuf); |
210 | | |
211 | | /* |
212 | | * Trigger cleanup in rare cases where prev_num_delpages exceeds 5% of the |
213 | | * total size of the index. We can reasonably expect (though are not |
214 | | * guaranteed) to be able to recycle this many pages if we decide to do a |
215 | | * btvacuumscan call during the ongoing btvacuumcleanup. For further |
216 | | * details see the nbtree/README section on placing deleted pages in the |
217 | | * FSM. |
218 | | */ |
219 | 0 | if (prev_num_delpages > 0 && |
220 | 0 | prev_num_delpages > RelationGetNumberOfBlocks(rel) / 20) |
221 | 0 | return true; |
222 | | |
223 | 0 | return false; |
224 | 0 | } |
225 | | |
226 | | /* |
227 | | * _bt_set_cleanup_info() -- Update metapage for btvacuumcleanup. |
228 | | * |
229 | | * Called at the end of btvacuumcleanup, when num_delpages value has been |
230 | | * finalized. |
231 | | */ |
232 | | void |
233 | | _bt_set_cleanup_info(Relation rel, BlockNumber num_delpages) |
234 | 0 | { |
235 | 0 | Buffer metabuf; |
236 | 0 | Page metapg; |
237 | 0 | BTMetaPageData *metad; |
238 | 0 | XLogRecPtr recptr; |
239 | | |
240 | | /* |
241 | | * On-disk compatibility note: The btm_last_cleanup_num_delpages metapage |
242 | | * field started out as a TransactionId field called btm_oldest_btpo_xact. |
243 | | * Both "versions" are just uint32 fields. It was convenient to repurpose |
244 | | * the field when we began to use 64-bit XIDs in deleted pages. |
245 | | * |
246 | | * It's possible that a pg_upgrade'd database will contain an XID value in |
247 | | * what is now recognized as the metapage's btm_last_cleanup_num_delpages |
248 | | * field. _bt_vacuum_needs_cleanup() may even believe that this value |
249 | | * indicates that there are lots of pages that it needs to recycle, when |
250 | | * in reality there are only one or two. The worst that can happen is |
251 | | * that there will be a call to btvacuumscan a little earlier, which will |
252 | | * set btm_last_cleanup_num_delpages to a sane value when we're called. |
253 | | * |
254 | | * Note also that the metapage's btm_last_cleanup_num_heap_tuples field is |
255 | | * no longer used as of PostgreSQL 14. We set it to -1.0 on rewrite, just |
256 | | * to be consistent. |
257 | | */ |
258 | 0 | metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ); |
259 | 0 | metapg = BufferGetPage(metabuf); |
260 | 0 | metad = BTPageGetMeta(metapg); |
261 | | |
262 | | /* Don't miss chance to upgrade index/metapage when BTREE_MIN_VERSION */ |
263 | 0 | if (metad->btm_version >= BTREE_NOVAC_VERSION && |
264 | 0 | metad->btm_last_cleanup_num_delpages == num_delpages) |
265 | 0 | { |
266 | | /* Usually means index continues to have num_delpages of 0 */ |
267 | 0 | _bt_relbuf(rel, metabuf); |
268 | 0 | return; |
269 | 0 | } |
270 | | |
271 | | /* trade in our read lock for a write lock */ |
272 | 0 | _bt_unlockbuf(rel, metabuf); |
273 | 0 | _bt_lockbuf(rel, metabuf, BT_WRITE); |
274 | |
|
275 | 0 | START_CRIT_SECTION(); |
276 | | |
277 | | /* upgrade meta-page if needed */ |
278 | 0 | if (metad->btm_version < BTREE_NOVAC_VERSION) |
279 | 0 | _bt_upgrademetapage(metapg); |
280 | | |
281 | | /* update cleanup-related information */ |
282 | 0 | metad->btm_last_cleanup_num_delpages = num_delpages; |
283 | 0 | metad->btm_last_cleanup_num_heap_tuples = -1.0; |
284 | 0 | MarkBufferDirty(metabuf); |
285 | | |
286 | | /* write wal record if needed */ |
287 | 0 | if (RelationNeedsWAL(rel)) |
288 | 0 | { |
289 | 0 | xl_btree_metadata md; |
290 | |
|
291 | 0 | XLogBeginInsert(); |
292 | 0 | XLogRegisterBuffer(0, metabuf, REGBUF_WILL_INIT | REGBUF_STANDARD); |
293 | |
|
294 | 0 | Assert(metad->btm_version >= BTREE_NOVAC_VERSION); |
295 | 0 | md.version = metad->btm_version; |
296 | 0 | md.root = metad->btm_root; |
297 | 0 | md.level = metad->btm_level; |
298 | 0 | md.fastroot = metad->btm_fastroot; |
299 | 0 | md.fastlevel = metad->btm_fastlevel; |
300 | 0 | md.last_cleanup_num_delpages = num_delpages; |
301 | 0 | md.allequalimage = metad->btm_allequalimage; |
302 | |
|
303 | 0 | XLogRegisterBufData(0, &md, sizeof(xl_btree_metadata)); |
304 | |
|
305 | 0 | recptr = XLogInsert(RM_BTREE_ID, XLOG_BTREE_META_CLEANUP); |
306 | 0 | } |
307 | 0 | else |
308 | 0 | recptr = XLogGetFakeLSN(rel); |
309 | |
|
310 | 0 | PageSetLSN(metapg, recptr); |
311 | |
|
312 | 0 | END_CRIT_SECTION(); |
313 | |
|
314 | 0 | _bt_relbuf(rel, metabuf); |
315 | 0 | } |
316 | | |
317 | | /* |
318 | | * _bt_getroot() -- Get the root page of the btree. |
319 | | * |
320 | | * Since the root page can move around the btree file, we have to read |
321 | | * its location from the metadata page, and then read the root page |
322 | | * itself. If no root page exists yet, we have to create one. |
323 | | * |
324 | | * The access type parameter (BT_READ or BT_WRITE) controls whether |
325 | | * a new root page will be created or not. If access = BT_READ, |
326 | | * and no root page exists, we just return InvalidBuffer. For |
327 | | * BT_WRITE, we try to create the root page if it doesn't exist. |
328 | | * NOTE that the returned root page will have only a read lock set |
329 | | * on it even if access = BT_WRITE! |
330 | | * |
331 | | * If access = BT_WRITE, heaprel must be set; otherwise caller can just |
332 | | * pass NULL. See _bt_allocbuf for an explanation. |
333 | | * |
334 | | * The returned page is not necessarily the true root --- it could be |
335 | | * a "fast root" (a page that is alone in its level due to deletions). |
336 | | * Also, if the root page is split while we are "in flight" to it, |
337 | | * what we will return is the old root, which is now just the leftmost |
338 | | * page on a probably-not-very-wide level. For most purposes this is |
339 | | * as good as or better than the true root, so we do not bother to |
340 | | * insist on finding the true root. We do, however, guarantee to |
341 | | * return a live (not deleted or half-dead) page. |
342 | | * |
343 | | * On successful return, the root page is pinned and read-locked. |
344 | | * The metadata page is not locked or pinned on exit. |
345 | | */ |
346 | | Buffer |
347 | | _bt_getroot(Relation rel, Relation heaprel, int access) |
348 | 0 | { |
349 | 0 | Buffer metabuf; |
350 | 0 | Buffer rootbuf; |
351 | 0 | Page rootpage; |
352 | 0 | BTPageOpaque rootopaque; |
353 | 0 | BlockNumber rootblkno; |
354 | 0 | uint32 rootlevel; |
355 | 0 | BTMetaPageData *metad; |
356 | 0 | XLogRecPtr recptr; |
357 | |
|
358 | 0 | Assert(access == BT_READ || heaprel != NULL); |
359 | | |
360 | | /* |
361 | | * Try to use previously-cached metapage data to find the root. This |
362 | | * normally saves one buffer access per index search, which is a very |
363 | | * helpful savings in bufmgr traffic and hence contention. |
364 | | */ |
365 | 0 | if (rel->rd_amcache != NULL) |
366 | 0 | { |
367 | 0 | metad = (BTMetaPageData *) rel->rd_amcache; |
368 | | /* We shouldn't have cached it if any of these fail */ |
369 | 0 | Assert(metad->btm_magic == BTREE_MAGIC); |
370 | 0 | Assert(metad->btm_version >= BTREE_MIN_VERSION); |
371 | 0 | Assert(metad->btm_version <= BTREE_VERSION); |
372 | 0 | Assert(!metad->btm_allequalimage || |
373 | 0 | metad->btm_version > BTREE_NOVAC_VERSION); |
374 | 0 | Assert(metad->btm_root != P_NONE); |
375 | |
|
376 | 0 | rootblkno = metad->btm_fastroot; |
377 | 0 | Assert(rootblkno != P_NONE); |
378 | 0 | rootlevel = metad->btm_fastlevel; |
379 | |
|
380 | 0 | rootbuf = _bt_getbuf(rel, rootblkno, BT_READ); |
381 | 0 | rootpage = BufferGetPage(rootbuf); |
382 | 0 | rootopaque = BTPageGetOpaque(rootpage); |
383 | | |
384 | | /* |
385 | | * Since the cache might be stale, we check the page more carefully |
386 | | * here than normal. We *must* check that it's not deleted. If it's |
387 | | * not alone on its level, then we reject too --- this may be overly |
388 | | * paranoid but better safe than sorry. Note we don't check P_ISROOT, |
389 | | * because that's not set in a "fast root". |
390 | | */ |
391 | 0 | if (!P_IGNORE(rootopaque) && |
392 | 0 | rootopaque->btpo_level == rootlevel && |
393 | 0 | P_LEFTMOST(rootopaque) && |
394 | 0 | P_RIGHTMOST(rootopaque)) |
395 | 0 | { |
396 | | /* OK, accept cached page as the root */ |
397 | 0 | return rootbuf; |
398 | 0 | } |
399 | 0 | _bt_relbuf(rel, rootbuf); |
400 | | /* Cache is stale, throw it away */ |
401 | 0 | if (rel->rd_amcache) |
402 | 0 | pfree(rel->rd_amcache); |
403 | 0 | rel->rd_amcache = NULL; |
404 | 0 | } |
405 | | |
406 | 0 | metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ); |
407 | 0 | metad = _bt_getmeta(rel, metabuf); |
408 | | |
409 | | /* if no root page initialized yet, do it */ |
410 | 0 | if (metad->btm_root == P_NONE) |
411 | 0 | { |
412 | 0 | Page metapg; |
413 | | |
414 | | /* If access = BT_READ, caller doesn't want us to create root yet */ |
415 | 0 | if (access == BT_READ) |
416 | 0 | { |
417 | 0 | _bt_relbuf(rel, metabuf); |
418 | 0 | return InvalidBuffer; |
419 | 0 | } |
420 | | |
421 | | /* trade in our read lock for a write lock */ |
422 | 0 | _bt_unlockbuf(rel, metabuf); |
423 | 0 | _bt_lockbuf(rel, metabuf, BT_WRITE); |
424 | | |
425 | | /* |
426 | | * Race condition: if someone else initialized the metadata between |
427 | | * the time we released the read lock and acquired the write lock, we |
428 | | * must avoid doing it again. |
429 | | */ |
430 | 0 | if (metad->btm_root != P_NONE) |
431 | 0 | { |
432 | | /* |
433 | | * Metadata initialized by someone else. In order to guarantee no |
434 | | * deadlocks, we have to release the metadata page and start all |
435 | | * over again. (Is that really true? But it's hardly worth trying |
436 | | * to optimize this case.) |
437 | | */ |
438 | 0 | _bt_relbuf(rel, metabuf); |
439 | 0 | return _bt_getroot(rel, heaprel, access); |
440 | 0 | } |
441 | | |
442 | | /* |
443 | | * Get, initialize, write, and leave a lock of the appropriate type on |
444 | | * the new root page. Since this is the first page in the tree, it's |
445 | | * a leaf as well as the root. |
446 | | */ |
447 | 0 | rootbuf = _bt_allocbuf(rel, heaprel); |
448 | 0 | rootblkno = BufferGetBlockNumber(rootbuf); |
449 | 0 | rootpage = BufferGetPage(rootbuf); |
450 | 0 | rootopaque = BTPageGetOpaque(rootpage); |
451 | 0 | rootopaque->btpo_prev = rootopaque->btpo_next = P_NONE; |
452 | 0 | rootopaque->btpo_flags = (BTP_LEAF | BTP_ROOT); |
453 | 0 | rootopaque->btpo_level = 0; |
454 | 0 | rootopaque->btpo_cycleid = 0; |
455 | | /* Get raw page pointer for metapage */ |
456 | 0 | metapg = BufferGetPage(metabuf); |
457 | | |
458 | | /* NO ELOG(ERROR) till meta is updated */ |
459 | 0 | START_CRIT_SECTION(); |
460 | | |
461 | | /* upgrade metapage if needed */ |
462 | 0 | if (metad->btm_version < BTREE_NOVAC_VERSION) |
463 | 0 | _bt_upgrademetapage(metapg); |
464 | |
|
465 | 0 | metad->btm_root = rootblkno; |
466 | 0 | metad->btm_level = 0; |
467 | 0 | metad->btm_fastroot = rootblkno; |
468 | 0 | metad->btm_fastlevel = 0; |
469 | 0 | metad->btm_last_cleanup_num_delpages = 0; |
470 | 0 | metad->btm_last_cleanup_num_heap_tuples = -1.0; |
471 | |
|
472 | 0 | MarkBufferDirty(rootbuf); |
473 | 0 | MarkBufferDirty(metabuf); |
474 | | |
475 | | /* XLOG stuff */ |
476 | 0 | if (RelationNeedsWAL(rel)) |
477 | 0 | { |
478 | 0 | xl_btree_newroot xlrec; |
479 | 0 | xl_btree_metadata md; |
480 | |
|
481 | 0 | XLogBeginInsert(); |
482 | 0 | XLogRegisterBuffer(0, rootbuf, REGBUF_WILL_INIT); |
483 | 0 | XLogRegisterBuffer(2, metabuf, REGBUF_WILL_INIT | REGBUF_STANDARD); |
484 | |
|
485 | 0 | Assert(metad->btm_version >= BTREE_NOVAC_VERSION); |
486 | 0 | md.version = metad->btm_version; |
487 | 0 | md.root = rootblkno; |
488 | 0 | md.level = 0; |
489 | 0 | md.fastroot = rootblkno; |
490 | 0 | md.fastlevel = 0; |
491 | 0 | md.last_cleanup_num_delpages = 0; |
492 | 0 | md.allequalimage = metad->btm_allequalimage; |
493 | |
|
494 | 0 | XLogRegisterBufData(2, &md, sizeof(xl_btree_metadata)); |
495 | |
|
496 | 0 | xlrec.rootblk = rootblkno; |
497 | 0 | xlrec.level = 0; |
498 | |
|
499 | 0 | XLogRegisterData(&xlrec, SizeOfBtreeNewroot); |
500 | |
|
501 | 0 | recptr = XLogInsert(RM_BTREE_ID, XLOG_BTREE_NEWROOT); |
502 | 0 | } |
503 | 0 | else |
504 | 0 | recptr = XLogGetFakeLSN(rel); |
505 | |
|
506 | 0 | PageSetLSN(rootpage, recptr); |
507 | 0 | PageSetLSN(metapg, recptr); |
508 | |
|
509 | 0 | END_CRIT_SECTION(); |
510 | | |
511 | | /* |
512 | | * swap root write lock for read lock. There is no danger of anyone |
513 | | * else accessing the new root page while it's unlocked, since no one |
514 | | * else knows where it is yet. |
515 | | */ |
516 | 0 | _bt_unlockbuf(rel, rootbuf); |
517 | 0 | _bt_lockbuf(rel, rootbuf, BT_READ); |
518 | | |
519 | | /* okay, metadata is correct, release lock on it without caching */ |
520 | 0 | _bt_relbuf(rel, metabuf); |
521 | 0 | } |
522 | 0 | else |
523 | 0 | { |
524 | 0 | rootblkno = metad->btm_fastroot; |
525 | 0 | Assert(rootblkno != P_NONE); |
526 | 0 | rootlevel = metad->btm_fastlevel; |
527 | | |
528 | | /* |
529 | | * Cache the metapage data for next time |
530 | | */ |
531 | 0 | rel->rd_amcache = MemoryContextAlloc(rel->rd_indexcxt, |
532 | 0 | sizeof(BTMetaPageData)); |
533 | 0 | memcpy(rel->rd_amcache, metad, sizeof(BTMetaPageData)); |
534 | | |
535 | | /* |
536 | | * We are done with the metapage; arrange to release it via first |
537 | | * _bt_relandgetbuf call |
538 | | */ |
539 | 0 | rootbuf = metabuf; |
540 | |
|
541 | 0 | for (;;) |
542 | 0 | { |
543 | 0 | rootbuf = _bt_relandgetbuf(rel, rootbuf, rootblkno, BT_READ); |
544 | 0 | rootpage = BufferGetPage(rootbuf); |
545 | 0 | rootopaque = BTPageGetOpaque(rootpage); |
546 | |
|
547 | 0 | if (!P_IGNORE(rootopaque)) |
548 | 0 | break; |
549 | | |
550 | | /* it's dead, Jim. step right one page */ |
551 | 0 | if (P_RIGHTMOST(rootopaque)) |
552 | 0 | elog(ERROR, "no live root page found in index \"%s\"", |
553 | 0 | RelationGetRelationName(rel)); |
554 | 0 | rootblkno = rootopaque->btpo_next; |
555 | 0 | } |
556 | | |
557 | 0 | if (rootopaque->btpo_level != rootlevel) |
558 | 0 | elog(ERROR, "root page %u of index \"%s\" has level %u, expected %u", |
559 | 0 | rootblkno, RelationGetRelationName(rel), |
560 | 0 | rootopaque->btpo_level, rootlevel); |
561 | 0 | } |
562 | | |
563 | | /* |
564 | | * By here, we have a pin and read lock on the root page, and no lock set |
565 | | * on the metadata page. Return the root page's buffer. |
566 | | */ |
567 | 0 | return rootbuf; |
568 | 0 | } |
569 | | |
570 | | /* |
571 | | * _bt_gettrueroot() -- Get the true root page of the btree. |
572 | | * |
573 | | * This is the same as the BT_READ case of _bt_getroot(), except |
574 | | * we follow the true-root link not the fast-root link. |
575 | | * |
576 | | * By the time we acquire lock on the root page, it might have been split and |
577 | | * not be the true root anymore. This is okay for the present uses of this |
578 | | * routine; we only really need to be able to move up at least one tree level |
579 | | * from whatever non-root page we were at. If we ever do need to lock the |
580 | | * one true root page, we could loop here, re-reading the metapage on each |
581 | | * failure. (Note that it wouldn't do to hold the lock on the metapage while |
582 | | * moving to the root --- that'd deadlock against any concurrent root split.) |
583 | | */ |
584 | | Buffer |
585 | | _bt_gettrueroot(Relation rel) |
586 | 0 | { |
587 | 0 | Buffer metabuf; |
588 | 0 | Buffer rootbuf; |
589 | 0 | Page rootpage; |
590 | 0 | BTPageOpaque rootopaque; |
591 | 0 | BlockNumber rootblkno; |
592 | 0 | uint32 rootlevel; |
593 | 0 | BTMetaPageData *metad; |
594 | | |
595 | | /* |
596 | | * We don't try to use cached metapage data here, since (a) this path is |
597 | | * not performance-critical, and (b) if we are here it suggests our cache |
598 | | * is out-of-date anyway. In light of point (b), it's probably safest to |
599 | | * actively flush any cached metapage info. |
600 | | */ |
601 | 0 | if (rel->rd_amcache) |
602 | 0 | pfree(rel->rd_amcache); |
603 | 0 | rel->rd_amcache = NULL; |
604 | |
|
605 | 0 | metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ); |
606 | 0 | metad = _bt_getmeta(rel, metabuf); |
607 | | |
608 | | /* if no root page initialized yet, fail */ |
609 | 0 | if (metad->btm_root == P_NONE) |
610 | 0 | { |
611 | 0 | _bt_relbuf(rel, metabuf); |
612 | 0 | return InvalidBuffer; |
613 | 0 | } |
614 | | |
615 | 0 | rootblkno = metad->btm_root; |
616 | 0 | rootlevel = metad->btm_level; |
617 | | |
618 | | /* |
619 | | * We are done with the metapage; arrange to release it via first |
620 | | * _bt_relandgetbuf call |
621 | | */ |
622 | 0 | rootbuf = metabuf; |
623 | |
|
624 | 0 | for (;;) |
625 | 0 | { |
626 | 0 | rootbuf = _bt_relandgetbuf(rel, rootbuf, rootblkno, BT_READ); |
627 | 0 | rootpage = BufferGetPage(rootbuf); |
628 | 0 | rootopaque = BTPageGetOpaque(rootpage); |
629 | |
|
630 | 0 | if (!P_IGNORE(rootopaque)) |
631 | 0 | break; |
632 | | |
633 | | /* it's dead, Jim. step right one page */ |
634 | 0 | if (P_RIGHTMOST(rootopaque)) |
635 | 0 | elog(ERROR, "no live root page found in index \"%s\"", |
636 | 0 | RelationGetRelationName(rel)); |
637 | 0 | rootblkno = rootopaque->btpo_next; |
638 | 0 | } |
639 | | |
640 | 0 | if (rootopaque->btpo_level != rootlevel) |
641 | 0 | elog(ERROR, "root page %u of index \"%s\" has level %u, expected %u", |
642 | 0 | rootblkno, RelationGetRelationName(rel), |
643 | 0 | rootopaque->btpo_level, rootlevel); |
644 | | |
645 | 0 | return rootbuf; |
646 | 0 | } |
647 | | |
648 | | /* |
649 | | * _bt_getrootheight() -- Get the height of the btree search tree. |
650 | | * |
651 | | * We return the level (counting from zero) of the current fast root. |
652 | | * This represents the number of tree levels we'd have to descend through |
653 | | * to start any btree index search. |
654 | | * |
655 | | * This is used by the planner for cost-estimation purposes. Since it's |
656 | | * only an estimate, slightly-stale data is fine, hence we don't worry |
657 | | * about updating previously cached data. |
658 | | */ |
659 | | int |
660 | | _bt_getrootheight(Relation rel) |
661 | 0 | { |
662 | 0 | BTMetaPageData *metad; |
663 | |
|
664 | 0 | if (rel->rd_amcache == NULL) |
665 | 0 | { |
666 | 0 | Buffer metabuf; |
667 | |
|
668 | 0 | metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ); |
669 | 0 | metad = _bt_getmeta(rel, metabuf); |
670 | | |
671 | | /* |
672 | | * If there's no root page yet, _bt_getroot() doesn't expect a cache |
673 | | * to be made, so just stop here and report the index height is zero. |
674 | | * (XXX perhaps _bt_getroot() should be changed to allow this case.) |
675 | | */ |
676 | 0 | if (metad->btm_root == P_NONE) |
677 | 0 | { |
678 | 0 | _bt_relbuf(rel, metabuf); |
679 | 0 | return 0; |
680 | 0 | } |
681 | | |
682 | | /* |
683 | | * Cache the metapage data for next time |
684 | | */ |
685 | 0 | rel->rd_amcache = MemoryContextAlloc(rel->rd_indexcxt, |
686 | 0 | sizeof(BTMetaPageData)); |
687 | 0 | memcpy(rel->rd_amcache, metad, sizeof(BTMetaPageData)); |
688 | 0 | _bt_relbuf(rel, metabuf); |
689 | 0 | } |
690 | | |
691 | | /* Get cached page */ |
692 | 0 | metad = (BTMetaPageData *) rel->rd_amcache; |
693 | | /* We shouldn't have cached it if any of these fail */ |
694 | 0 | Assert(metad->btm_magic == BTREE_MAGIC); |
695 | 0 | Assert(metad->btm_version >= BTREE_MIN_VERSION); |
696 | 0 | Assert(metad->btm_version <= BTREE_VERSION); |
697 | 0 | Assert(!metad->btm_allequalimage || |
698 | 0 | metad->btm_version > BTREE_NOVAC_VERSION); |
699 | 0 | Assert(metad->btm_fastroot != P_NONE); |
700 | |
|
701 | 0 | return metad->btm_fastlevel; |
702 | 0 | } |
703 | | |
704 | | /* |
705 | | * _bt_metaversion() -- Get version/status info from metapage. |
706 | | * |
707 | | * Sets caller's *heapkeyspace and *allequalimage arguments using data |
708 | | * from the B-Tree metapage (could be locally-cached version). This |
709 | | * information needs to be stashed in insertion scankey, so we provide a |
710 | | * single function that fetches both at once. |
711 | | * |
712 | | * This is used to determine the rules that must be used to descend a |
713 | | * btree. Version 4 indexes treat heap TID as a tiebreaker attribute. |
714 | | * pg_upgrade'd version 3 indexes need extra steps to preserve reasonable |
715 | | * performance when inserting a new BTScanInsert-wise duplicate tuple |
716 | | * among many leaf pages already full of such duplicates. |
717 | | * |
718 | | * Also sets allequalimage field, which indicates whether or not it is |
719 | | * safe to apply deduplication. We rely on the assumption that |
720 | | * btm_allequalimage will be zero'ed on heapkeyspace indexes that were |
721 | | * pg_upgrade'd from Postgres 12. |
722 | | */ |
723 | | void |
724 | | _bt_metaversion(Relation rel, bool *heapkeyspace, bool *allequalimage) |
725 | 0 | { |
726 | 0 | BTMetaPageData *metad; |
727 | |
|
728 | 0 | if (rel->rd_amcache == NULL) |
729 | 0 | { |
730 | 0 | Buffer metabuf; |
731 | |
|
732 | 0 | metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_READ); |
733 | 0 | metad = _bt_getmeta(rel, metabuf); |
734 | | |
735 | | /* |
736 | | * If there's no root page yet, _bt_getroot() doesn't expect a cache |
737 | | * to be made, so just stop here. (XXX perhaps _bt_getroot() should |
738 | | * be changed to allow this case.) |
739 | | */ |
740 | 0 | if (metad->btm_root == P_NONE) |
741 | 0 | { |
742 | 0 | *heapkeyspace = metad->btm_version > BTREE_NOVAC_VERSION; |
743 | 0 | *allequalimage = metad->btm_allequalimage; |
744 | |
|
745 | 0 | _bt_relbuf(rel, metabuf); |
746 | 0 | return; |
747 | 0 | } |
748 | | |
749 | | /* |
750 | | * Cache the metapage data for next time |
751 | | * |
752 | | * An on-the-fly version upgrade performed by _bt_upgrademetapage() |
753 | | * can change the nbtree version for an index without invalidating any |
754 | | * local cache. This is okay because it can only happen when moving |
755 | | * from version 2 to version 3, both of which are !heapkeyspace |
756 | | * versions. |
757 | | */ |
758 | 0 | rel->rd_amcache = MemoryContextAlloc(rel->rd_indexcxt, |
759 | 0 | sizeof(BTMetaPageData)); |
760 | 0 | memcpy(rel->rd_amcache, metad, sizeof(BTMetaPageData)); |
761 | 0 | _bt_relbuf(rel, metabuf); |
762 | 0 | } |
763 | | |
764 | | /* Get cached page */ |
765 | 0 | metad = (BTMetaPageData *) rel->rd_amcache; |
766 | | /* We shouldn't have cached it if any of these fail */ |
767 | 0 | Assert(metad->btm_magic == BTREE_MAGIC); |
768 | 0 | Assert(metad->btm_version >= BTREE_MIN_VERSION); |
769 | 0 | Assert(metad->btm_version <= BTREE_VERSION); |
770 | 0 | Assert(!metad->btm_allequalimage || |
771 | 0 | metad->btm_version > BTREE_NOVAC_VERSION); |
772 | 0 | Assert(metad->btm_fastroot != P_NONE); |
773 | |
|
774 | 0 | *heapkeyspace = metad->btm_version > BTREE_NOVAC_VERSION; |
775 | 0 | *allequalimage = metad->btm_allequalimage; |
776 | 0 | } |
777 | | |
778 | | /* |
779 | | * _bt_checkpage() -- Verify that a freshly-read page looks sane. |
780 | | */ |
781 | | void |
782 | | _bt_checkpage(Relation rel, Buffer buf) |
783 | 0 | { |
784 | 0 | Page page = BufferGetPage(buf); |
785 | | |
786 | | /* |
787 | | * ReadBuffer verifies that every newly-read page passes |
788 | | * PageHeaderIsValid, which means it either contains a reasonably sane |
789 | | * page header or is all-zero. We have to defend against the all-zero |
790 | | * case, however. |
791 | | */ |
792 | 0 | if (PageIsNew(page)) |
793 | 0 | ereport(ERROR, |
794 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
795 | 0 | errmsg("index \"%s\" contains unexpected zero page at block %u", |
796 | 0 | RelationGetRelationName(rel), |
797 | 0 | BufferGetBlockNumber(buf)), |
798 | 0 | errhint("Please REINDEX it."))); |
799 | | |
800 | | /* |
801 | | * Additionally check that the special area looks sane. |
802 | | */ |
803 | 0 | if (PageGetSpecialSize(page) != MAXALIGN(sizeof(BTPageOpaqueData))) |
804 | 0 | ereport(ERROR, |
805 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
806 | 0 | errmsg("index \"%s\" contains corrupted page at block %u", |
807 | 0 | RelationGetRelationName(rel), |
808 | 0 | BufferGetBlockNumber(buf)), |
809 | 0 | errhint("Please REINDEX it."))); |
810 | 0 | } |
811 | | |
812 | | /* |
813 | | * _bt_getbuf() -- Get an existing block in a buffer, for read or write. |
814 | | * |
815 | | * The general rule in nbtree is that it's never okay to access a |
816 | | * page without holding both a buffer pin and a buffer lock on |
817 | | * the page's buffer. |
818 | | * |
819 | | * When this routine returns, the appropriate lock is set on the |
820 | | * requested buffer and its reference count has been incremented |
821 | | * (ie, the buffer is "locked and pinned"). Also, we apply |
822 | | * _bt_checkpage to sanity-check the page, and perform Valgrind |
823 | | * client requests that help Valgrind detect unsafe page accesses. |
824 | | * |
825 | | * Note: raw LockBuffer() calls are disallowed in nbtree; all |
826 | | * buffer lock requests need to go through wrapper functions such |
827 | | * as _bt_lockbuf(). |
828 | | */ |
829 | | Buffer |
830 | | _bt_getbuf(Relation rel, BlockNumber blkno, int access) |
831 | 0 | { |
832 | 0 | Buffer buf; |
833 | |
|
834 | 0 | Assert(BlockNumberIsValid(blkno)); |
835 | | |
836 | | /* Read an existing block of the relation */ |
837 | 0 | buf = ReadBuffer(rel, blkno); |
838 | 0 | _bt_lockbuf(rel, buf, access); |
839 | 0 | _bt_checkpage(rel, buf); |
840 | |
|
841 | 0 | return buf; |
842 | 0 | } |
843 | | |
844 | | /* |
845 | | * _bt_allocbuf() -- Allocate a new block/page. |
846 | | * |
847 | | * Returns a write-locked buffer containing an unallocated nbtree page. |
848 | | * |
849 | | * Callers are required to pass a valid heaprel. We need heaprel so that we |
850 | | * can handle generating a snapshotConflictHorizon that makes reusing a page |
851 | | * from the FSM safe for queries that may be running on standbys. |
852 | | */ |
853 | | Buffer |
854 | | _bt_allocbuf(Relation rel, Relation heaprel) |
855 | 0 | { |
856 | 0 | Buffer buf; |
857 | 0 | BlockNumber blkno; |
858 | 0 | Page page; |
859 | |
|
860 | 0 | Assert(heaprel != NULL); |
861 | | |
862 | | /* |
863 | | * First see if the FSM knows of any free pages. |
864 | | * |
865 | | * We can't trust the FSM's report unreservedly; we have to check that the |
866 | | * page is still free. (For example, an already-free page could have been |
867 | | * re-used between the time the last VACUUM scanned it and the time the |
868 | | * VACUUM made its FSM updates.) |
869 | | * |
870 | | * In fact, it's worse than that: we can't even assume that it's safe to |
871 | | * take a lock on the reported page. If somebody else has a lock on it, |
872 | | * or even worse our own caller does, we could deadlock. (The own-caller |
873 | | * scenario is actually not improbable. Consider an index on a serial or |
874 | | * timestamp column. Nearly all splits will be at the rightmost page, so |
875 | | * it's entirely likely that _bt_split will call us while holding a lock |
876 | | * on the page most recently acquired from FSM. A VACUUM running |
877 | | * concurrently with the previous split could well have placed that page |
878 | | * back in FSM.) |
879 | | * |
880 | | * To get around that, we ask for only a conditional lock on the reported |
881 | | * page. If we fail, then someone else is using the page, and we may |
882 | | * reasonably assume it's not free. (If we happen to be wrong, the worst |
883 | | * consequence is the page will be lost to use till the next VACUUM, which |
884 | | * is no big problem.) |
885 | | */ |
886 | 0 | for (;;) |
887 | 0 | { |
888 | 0 | blkno = GetFreeIndexPage(rel); |
889 | 0 | if (blkno == InvalidBlockNumber) |
890 | 0 | break; |
891 | 0 | buf = ReadBuffer(rel, blkno); |
892 | 0 | if (_bt_conditionallockbuf(rel, buf)) |
893 | 0 | { |
894 | 0 | page = BufferGetPage(buf); |
895 | | |
896 | | /* |
897 | | * It's possible to find an all-zeroes page in an index. For |
898 | | * example, a backend might successfully extend the relation one |
899 | | * page and then crash before it is able to make a WAL entry for |
900 | | * adding the page. If we find a zeroed page then reclaim it |
901 | | * immediately. |
902 | | */ |
903 | 0 | if (PageIsNew(page)) |
904 | 0 | { |
905 | | /* Okay to use page. Initialize and return it. */ |
906 | 0 | _bt_pageinit(page, BufferGetPageSize(buf)); |
907 | 0 | return buf; |
908 | 0 | } |
909 | | |
910 | 0 | if (BTPageIsRecyclable(page, heaprel)) |
911 | 0 | { |
912 | | /* |
913 | | * If we are generating WAL for Hot Standby then create a WAL |
914 | | * record that will allow us to conflict with queries running |
915 | | * on standby, in case they have snapshots older than safexid |
916 | | * value |
917 | | */ |
918 | 0 | if (RelationNeedsWAL(rel) && XLogStandbyInfoActive()) |
919 | 0 | { |
920 | 0 | xl_btree_reuse_page xlrec_reuse; |
921 | | |
922 | | /* |
923 | | * Note that we don't register the buffer with the record, |
924 | | * because this operation doesn't modify the page (that |
925 | | * already happened, back when VACUUM deleted the page). |
926 | | * This record only exists to provide a conflict point for |
927 | | * Hot Standby. See record REDO routine comments. |
928 | | */ |
929 | 0 | xlrec_reuse.locator = rel->rd_locator; |
930 | 0 | xlrec_reuse.block = blkno; |
931 | 0 | xlrec_reuse.snapshotConflictHorizon = BTPageGetDeleteXid(page); |
932 | 0 | xlrec_reuse.isCatalogRel = |
933 | 0 | RelationIsAccessibleInLogicalDecoding(heaprel); |
934 | |
|
935 | 0 | XLogBeginInsert(); |
936 | 0 | XLogRegisterData(&xlrec_reuse, SizeOfBtreeReusePage); |
937 | |
|
938 | 0 | XLogInsert(RM_BTREE_ID, XLOG_BTREE_REUSE_PAGE); |
939 | 0 | } |
940 | | |
941 | | /* Okay to use page. Re-initialize and return it. */ |
942 | 0 | _bt_pageinit(page, BufferGetPageSize(buf)); |
943 | 0 | return buf; |
944 | 0 | } |
945 | 0 | elog(DEBUG2, "FSM returned nonrecyclable page"); |
946 | 0 | _bt_relbuf(rel, buf); |
947 | 0 | } |
948 | 0 | else |
949 | 0 | { |
950 | 0 | elog(DEBUG2, "FSM returned nonlockable page"); |
951 | | /* couldn't get lock, so just drop pin */ |
952 | 0 | ReleaseBuffer(buf); |
953 | 0 | } |
954 | 0 | } |
955 | | |
956 | | /* |
957 | | * Extend the relation by one page. Need to use RBM_ZERO_AND_LOCK or we |
958 | | * risk a race condition against btvacuumscan --- see comments therein. |
959 | | * This forces us to repeat the valgrind request that _bt_lockbuf() |
960 | | * otherwise would make, as we can't use _bt_lockbuf() without introducing |
961 | | * a race. |
962 | | */ |
963 | 0 | buf = ExtendBufferedRel(BMR_REL(rel), MAIN_FORKNUM, NULL, EB_LOCK_FIRST); |
964 | 0 | if (!RelationUsesLocalBuffers(rel)) |
965 | 0 | VALGRIND_MAKE_MEM_DEFINED(BufferGetPage(buf), BLCKSZ); |
966 | | |
967 | | /* Initialize the new page before returning it */ |
968 | 0 | page = BufferGetPage(buf); |
969 | 0 | Assert(PageIsNew(page)); |
970 | 0 | _bt_pageinit(page, BufferGetPageSize(buf)); |
971 | |
|
972 | 0 | return buf; |
973 | 0 | } |
974 | | |
975 | | /* |
976 | | * _bt_relandgetbuf() -- release a locked buffer and get another one. |
977 | | * |
978 | | * This is equivalent to _bt_relbuf followed by _bt_getbuf. Also, if obuf is |
979 | | * InvalidBuffer then it reduces to just _bt_getbuf; allowing this case |
980 | | * simplifies some callers. |
981 | | * |
982 | | * The original motivation for using this was to avoid two entries to the |
983 | | * bufmgr when one would do. However, now it's mainly just a notational |
984 | | * convenience. The only case where it saves work over _bt_relbuf/_bt_getbuf |
985 | | * is when the target page is the same one already in the buffer. |
986 | | */ |
987 | | Buffer |
988 | | _bt_relandgetbuf(Relation rel, Buffer obuf, BlockNumber blkno, int access) |
989 | 0 | { |
990 | 0 | Buffer buf; |
991 | |
|
992 | 0 | Assert(BlockNumberIsValid(blkno)); |
993 | 0 | if (BufferIsValid(obuf)) |
994 | 0 | { |
995 | 0 | if (BufferGetBlockNumber(obuf) == blkno) |
996 | 0 | { |
997 | | /* trade in old lock mode for new lock */ |
998 | 0 | _bt_unlockbuf(rel, obuf); |
999 | 0 | buf = obuf; |
1000 | 0 | } |
1001 | 0 | else |
1002 | 0 | { |
1003 | | /* release lock and pin at once, that's a bit more efficient */ |
1004 | 0 | _bt_relbuf(rel, obuf); |
1005 | 0 | buf = ReadBuffer(rel, blkno); |
1006 | 0 | } |
1007 | 0 | } |
1008 | 0 | else |
1009 | 0 | buf = ReadBuffer(rel, blkno); |
1010 | |
|
1011 | 0 | _bt_lockbuf(rel, buf, access); |
1012 | 0 | _bt_checkpage(rel, buf); |
1013 | |
|
1014 | 0 | return buf; |
1015 | 0 | } |
1016 | | |
1017 | | /* |
1018 | | * _bt_relbuf() -- release a locked buffer. |
1019 | | * |
1020 | | * Lock and pin (refcount) are both dropped. This is a bit more efficient than |
1021 | | * doing the two operations separately. |
1022 | | */ |
1023 | | void |
1024 | | _bt_relbuf(Relation rel, Buffer buf) |
1025 | 0 | { |
1026 | | /* |
1027 | | * Buffer is pinned and locked, which means that it is expected to be |
1028 | | * defined and addressable. Check that proactively. |
1029 | | */ |
1030 | 0 | VALGRIND_CHECK_MEM_IS_DEFINED(BufferGetPage(buf), BLCKSZ); |
1031 | 0 | if (!RelationUsesLocalBuffers(rel)) |
1032 | 0 | VALGRIND_MAKE_MEM_NOACCESS(BufferGetPage(buf), BLCKSZ); |
1033 | |
|
1034 | 0 | UnlockReleaseBuffer(buf); |
1035 | 0 | } |
1036 | | |
1037 | | /* |
1038 | | * _bt_lockbuf() -- lock a pinned buffer. |
1039 | | * |
1040 | | * Lock is acquired without acquiring another pin. This is like a raw |
1041 | | * LockBuffer() call, but performs extra steps needed by Valgrind. |
1042 | | * |
1043 | | * Note: Caller may need to call _bt_checkpage() with buf when pin on buf |
1044 | | * wasn't originally acquired in _bt_getbuf() or _bt_relandgetbuf(). |
1045 | | */ |
1046 | | void |
1047 | | _bt_lockbuf(Relation rel, Buffer buf, int access) |
1048 | 0 | { |
1049 | | /* LockBuffer() asserts that pin is held by this backend */ |
1050 | 0 | LockBuffer(buf, access); |
1051 | | |
1052 | | /* |
1053 | | * It doesn't matter that _bt_unlockbuf() won't get called in the event of |
1054 | | * an nbtree error (e.g. a unique violation error). That won't cause |
1055 | | * Valgrind false positives. |
1056 | | * |
1057 | | * The nbtree client requests are superimposed on top of the bufmgr.c |
1058 | | * buffer pin client requests. In the event of an nbtree error the buffer |
1059 | | * will certainly get marked as defined when the backend once again |
1060 | | * acquires its first pin on the buffer. (Of course, if the backend never |
1061 | | * touches the buffer again then it doesn't matter that it remains |
1062 | | * non-accessible to Valgrind.) |
1063 | | * |
1064 | | * Note: When an IndexTuple C pointer gets computed using an ItemId read |
1065 | | * from a page while a lock was held, the C pointer becomes unsafe to |
1066 | | * dereference forever as soon as the lock is released. Valgrind can only |
1067 | | * detect cases where the pointer gets dereferenced with no _current_ |
1068 | | * lock/pin held, though. |
1069 | | */ |
1070 | 0 | if (!RelationUsesLocalBuffers(rel)) |
1071 | 0 | VALGRIND_MAKE_MEM_DEFINED(BufferGetPage(buf), BLCKSZ); |
1072 | 0 | } |
1073 | | |
1074 | | /* |
1075 | | * _bt_unlockbuf() -- unlock a pinned buffer. |
1076 | | */ |
1077 | | void |
1078 | | _bt_unlockbuf(Relation rel, Buffer buf) |
1079 | 0 | { |
1080 | | /* |
1081 | | * Buffer is pinned and locked, which means that it is expected to be |
1082 | | * defined and addressable. Check that proactively. |
1083 | | */ |
1084 | 0 | VALGRIND_CHECK_MEM_IS_DEFINED(BufferGetPage(buf), BLCKSZ); |
1085 | | |
1086 | | /* LockBuffer() asserts that pin is held by this backend */ |
1087 | 0 | LockBuffer(buf, BUFFER_LOCK_UNLOCK); |
1088 | |
|
1089 | 0 | if (!RelationUsesLocalBuffers(rel)) |
1090 | 0 | VALGRIND_MAKE_MEM_NOACCESS(BufferGetPage(buf), BLCKSZ); |
1091 | 0 | } |
1092 | | |
1093 | | /* |
1094 | | * _bt_conditionallockbuf() -- conditionally BT_WRITE lock pinned |
1095 | | * buffer. |
1096 | | * |
1097 | | * Note: Caller may need to call _bt_checkpage() with buf when pin on buf |
1098 | | * wasn't originally acquired in _bt_getbuf() or _bt_relandgetbuf(). |
1099 | | */ |
1100 | | bool |
1101 | | _bt_conditionallockbuf(Relation rel, Buffer buf) |
1102 | 0 | { |
1103 | | /* ConditionalLockBuffer() asserts that pin is held by this backend */ |
1104 | 0 | if (!ConditionalLockBuffer(buf)) |
1105 | 0 | return false; |
1106 | | |
1107 | 0 | if (!RelationUsesLocalBuffers(rel)) |
1108 | 0 | VALGRIND_MAKE_MEM_DEFINED(BufferGetPage(buf), BLCKSZ); |
1109 | |
|
1110 | 0 | return true; |
1111 | 0 | } |
1112 | | |
1113 | | /* |
1114 | | * _bt_upgradelockbufcleanup() -- upgrade lock to a full cleanup lock. |
1115 | | */ |
1116 | | void |
1117 | | _bt_upgradelockbufcleanup(Relation rel, Buffer buf) |
1118 | 0 | { |
1119 | | /* |
1120 | | * Buffer is pinned and locked, which means that it is expected to be |
1121 | | * defined and addressable. Check that proactively. |
1122 | | */ |
1123 | 0 | VALGRIND_CHECK_MEM_IS_DEFINED(BufferGetPage(buf), BLCKSZ); |
1124 | | |
1125 | | /* LockBuffer() asserts that pin is held by this backend */ |
1126 | 0 | LockBuffer(buf, BUFFER_LOCK_UNLOCK); |
1127 | 0 | LockBufferForCleanup(buf); |
1128 | 0 | } |
1129 | | |
1130 | | /* |
1131 | | * _bt_pageinit() -- Initialize a new page. |
1132 | | * |
1133 | | * On return, the page header is initialized; data space is empty; |
1134 | | * special space is zeroed out. |
1135 | | */ |
1136 | | void |
1137 | | _bt_pageinit(Page page, Size size) |
1138 | 0 | { |
1139 | 0 | PageInit(page, size, sizeof(BTPageOpaqueData)); |
1140 | 0 | } |
1141 | | |
1142 | | /* |
1143 | | * Delete item(s) from a btree leaf page during VACUUM. |
1144 | | * |
1145 | | * This routine assumes that the caller already has a full cleanup lock on |
1146 | | * the buffer. Also, the given deletable and updatable arrays *must* be |
1147 | | * sorted in ascending order. |
1148 | | * |
1149 | | * Routine deals with deleting TIDs when some (but not all) of the heap TIDs |
1150 | | * in an existing posting list item are to be removed. This works by |
1151 | | * updating/overwriting an existing item with caller's new version of the item |
1152 | | * (a version that lacks the TIDs that are to be deleted). |
1153 | | * |
1154 | | * We record VACUUMs and b-tree deletes differently in WAL. Deletes must |
1155 | | * generate their own snapshotConflictHorizon directly from the tableam, |
1156 | | * whereas VACUUMs rely on the initial VACUUM table scan performing |
1157 | | * WAL-logging that takes care of the issue for the table's indexes |
1158 | | * indirectly. Also, we remove the VACUUM cycle ID from pages, which b-tree |
1159 | | * deletes don't do. |
1160 | | */ |
1161 | | void |
1162 | | _bt_delitems_vacuum(Relation rel, Buffer buf, |
1163 | | OffsetNumber *deletable, int ndeletable, |
1164 | | BTVacuumPosting *updatable, int nupdatable) |
1165 | 0 | { |
1166 | 0 | Page page = BufferGetPage(buf); |
1167 | 0 | BTPageOpaque opaque; |
1168 | 0 | bool needswal = RelationNeedsWAL(rel); |
1169 | 0 | char *updatedbuf = NULL; |
1170 | 0 | Size updatedbuflen = 0; |
1171 | 0 | OffsetNumber updatedoffsets[MaxIndexTuplesPerPage]; |
1172 | 0 | XLogRecPtr recptr; |
1173 | | |
1174 | | /* Shouldn't be called unless there's something to do */ |
1175 | 0 | Assert(ndeletable > 0 || nupdatable > 0); |
1176 | | |
1177 | | /* Generate new version of posting lists without deleted TIDs */ |
1178 | 0 | if (nupdatable > 0) |
1179 | 0 | updatedbuf = _bt_delitems_update(updatable, nupdatable, |
1180 | 0 | updatedoffsets, &updatedbuflen, |
1181 | 0 | needswal); |
1182 | | |
1183 | | /* No ereport(ERROR) until changes are logged */ |
1184 | 0 | START_CRIT_SECTION(); |
1185 | | |
1186 | | /* |
1187 | | * Handle posting tuple updates. |
1188 | | * |
1189 | | * Deliberately do this before handling simple deletes. If we did it the |
1190 | | * other way around (i.e. WAL record order -- simple deletes before |
1191 | | * updates) then we'd have to make compensating changes to the 'updatable' |
1192 | | * array of offset numbers. |
1193 | | * |
1194 | | * PageIndexTupleOverwrite() won't unset each item's LP_DEAD bit when it |
1195 | | * happens to already be set. It's important that we not interfere with |
1196 | | * any future simple index tuple deletion operations. |
1197 | | */ |
1198 | 0 | for (int i = 0; i < nupdatable; i++) |
1199 | 0 | { |
1200 | 0 | OffsetNumber updatedoffset = updatedoffsets[i]; |
1201 | 0 | IndexTuple itup; |
1202 | 0 | Size itemsz; |
1203 | |
|
1204 | 0 | itup = updatable[i]->itup; |
1205 | 0 | itemsz = MAXALIGN(IndexTupleSize(itup)); |
1206 | 0 | if (!PageIndexTupleOverwrite(page, updatedoffset, itup, itemsz)) |
1207 | 0 | elog(PANIC, "failed to update partially dead item in block %u of index \"%s\"", |
1208 | 0 | BufferGetBlockNumber(buf), RelationGetRelationName(rel)); |
1209 | 0 | } |
1210 | | |
1211 | | /* Now handle simple deletes of entire tuples */ |
1212 | 0 | if (ndeletable > 0) |
1213 | 0 | PageIndexMultiDelete(page, deletable, ndeletable); |
1214 | | |
1215 | | /* |
1216 | | * We can clear the vacuum cycle ID since this page has certainly been |
1217 | | * processed by the current vacuum scan. |
1218 | | */ |
1219 | 0 | opaque = BTPageGetOpaque(page); |
1220 | 0 | opaque->btpo_cycleid = 0; |
1221 | | |
1222 | | /* |
1223 | | * Clear the BTP_HAS_GARBAGE page flag. |
1224 | | * |
1225 | | * This flag indicates the presence of LP_DEAD items on the page (though |
1226 | | * not reliably). Note that we only rely on it with pg_upgrade'd |
1227 | | * !heapkeyspace indexes. That's why clearing it here won't usually |
1228 | | * interfere with simple index tuple deletion. |
1229 | | */ |
1230 | 0 | opaque->btpo_flags &= ~BTP_HAS_GARBAGE; |
1231 | |
|
1232 | 0 | MarkBufferDirty(buf); |
1233 | | |
1234 | | /* XLOG stuff */ |
1235 | 0 | if (needswal) |
1236 | 0 | { |
1237 | 0 | xl_btree_vacuum xlrec_vacuum; |
1238 | |
|
1239 | 0 | xlrec_vacuum.ndeleted = ndeletable; |
1240 | 0 | xlrec_vacuum.nupdated = nupdatable; |
1241 | |
|
1242 | 0 | XLogBeginInsert(); |
1243 | 0 | XLogRegisterBuffer(0, buf, REGBUF_STANDARD); |
1244 | 0 | XLogRegisterData(&xlrec_vacuum, SizeOfBtreeVacuum); |
1245 | |
|
1246 | 0 | if (ndeletable > 0) |
1247 | 0 | XLogRegisterBufData(0, deletable, |
1248 | 0 | ndeletable * sizeof(OffsetNumber)); |
1249 | |
|
1250 | 0 | if (nupdatable > 0) |
1251 | 0 | { |
1252 | 0 | XLogRegisterBufData(0, updatedoffsets, |
1253 | 0 | nupdatable * sizeof(OffsetNumber)); |
1254 | 0 | XLogRegisterBufData(0, updatedbuf, updatedbuflen); |
1255 | 0 | } |
1256 | |
|
1257 | 0 | recptr = XLogInsert(RM_BTREE_ID, XLOG_BTREE_VACUUM); |
1258 | 0 | } |
1259 | 0 | else |
1260 | 0 | recptr = XLogGetFakeLSN(rel); |
1261 | |
|
1262 | 0 | PageSetLSN(page, recptr); |
1263 | |
|
1264 | 0 | END_CRIT_SECTION(); |
1265 | | |
1266 | | /* can't leak memory here */ |
1267 | 0 | if (updatedbuf != NULL) |
1268 | 0 | pfree(updatedbuf); |
1269 | | /* free tuples allocated within _bt_delitems_update() */ |
1270 | 0 | for (int i = 0; i < nupdatable; i++) |
1271 | 0 | pfree(updatable[i]->itup); |
1272 | 0 | } |
1273 | | |
1274 | | /* |
1275 | | * Delete item(s) from a btree leaf page during single-page cleanup. |
1276 | | * |
1277 | | * This routine assumes that the caller has pinned and write locked the |
1278 | | * buffer. Also, the given deletable and updatable arrays *must* be sorted in |
1279 | | * ascending order. |
1280 | | * |
1281 | | * Routine deals with deleting TIDs when some (but not all) of the heap TIDs |
1282 | | * in an existing posting list item are to be removed. This works by |
1283 | | * updating/overwriting an existing item with caller's new version of the item |
1284 | | * (a version that lacks the TIDs that are to be deleted). |
1285 | | * |
1286 | | * This is nearly the same as _bt_delitems_vacuum as far as what it does to |
1287 | | * the page, but it needs its own snapshotConflictHorizon and isCatalogRel |
1288 | | * (from the tableam). This is used by the REDO routine to generate recovery |
1289 | | * conflicts. The other difference is that only _bt_delitems_vacuum will |
1290 | | * clear page's VACUUM cycle ID. |
1291 | | */ |
1292 | | static void |
1293 | | _bt_delitems_delete(Relation rel, Buffer buf, |
1294 | | TransactionId snapshotConflictHorizon, bool isCatalogRel, |
1295 | | OffsetNumber *deletable, int ndeletable, |
1296 | | BTVacuumPosting *updatable, int nupdatable) |
1297 | 0 | { |
1298 | 0 | Page page = BufferGetPage(buf); |
1299 | 0 | BTPageOpaque opaque; |
1300 | 0 | bool needswal = RelationNeedsWAL(rel); |
1301 | 0 | char *updatedbuf = NULL; |
1302 | 0 | Size updatedbuflen = 0; |
1303 | 0 | OffsetNumber updatedoffsets[MaxIndexTuplesPerPage]; |
1304 | 0 | XLogRecPtr recptr; |
1305 | | |
1306 | | /* Shouldn't be called unless there's something to do */ |
1307 | 0 | Assert(ndeletable > 0 || nupdatable > 0); |
1308 | | |
1309 | | /* Generate new versions of posting lists without deleted TIDs */ |
1310 | 0 | if (nupdatable > 0) |
1311 | 0 | updatedbuf = _bt_delitems_update(updatable, nupdatable, |
1312 | 0 | updatedoffsets, &updatedbuflen, |
1313 | 0 | needswal); |
1314 | | |
1315 | | /* No ereport(ERROR) until changes are logged */ |
1316 | 0 | START_CRIT_SECTION(); |
1317 | | |
1318 | | /* Handle updates and deletes just like _bt_delitems_vacuum */ |
1319 | 0 | for (int i = 0; i < nupdatable; i++) |
1320 | 0 | { |
1321 | 0 | OffsetNumber updatedoffset = updatedoffsets[i]; |
1322 | 0 | IndexTuple itup; |
1323 | 0 | Size itemsz; |
1324 | |
|
1325 | 0 | itup = updatable[i]->itup; |
1326 | 0 | itemsz = MAXALIGN(IndexTupleSize(itup)); |
1327 | 0 | if (!PageIndexTupleOverwrite(page, updatedoffset, itup, itemsz)) |
1328 | 0 | elog(PANIC, "failed to update partially dead item in block %u of index \"%s\"", |
1329 | 0 | BufferGetBlockNumber(buf), RelationGetRelationName(rel)); |
1330 | 0 | } |
1331 | | |
1332 | 0 | if (ndeletable > 0) |
1333 | 0 | PageIndexMultiDelete(page, deletable, ndeletable); |
1334 | | |
1335 | | /* |
1336 | | * Unlike _bt_delitems_vacuum, we *must not* clear the vacuum cycle ID at |
1337 | | * this point. The VACUUM command alone controls vacuum cycle IDs. |
1338 | | */ |
1339 | 0 | opaque = BTPageGetOpaque(page); |
1340 | | |
1341 | | /* |
1342 | | * Clear the BTP_HAS_GARBAGE page flag. |
1343 | | * |
1344 | | * This flag indicates the presence of LP_DEAD items on the page (though |
1345 | | * not reliably). Note that we only rely on it with pg_upgrade'd |
1346 | | * !heapkeyspace indexes. |
1347 | | */ |
1348 | 0 | opaque->btpo_flags &= ~BTP_HAS_GARBAGE; |
1349 | |
|
1350 | 0 | MarkBufferDirty(buf); |
1351 | | |
1352 | | /* XLOG stuff */ |
1353 | 0 | if (needswal) |
1354 | 0 | { |
1355 | 0 | xl_btree_delete xlrec_delete; |
1356 | |
|
1357 | 0 | xlrec_delete.snapshotConflictHorizon = snapshotConflictHorizon; |
1358 | 0 | xlrec_delete.ndeleted = ndeletable; |
1359 | 0 | xlrec_delete.nupdated = nupdatable; |
1360 | 0 | xlrec_delete.isCatalogRel = isCatalogRel; |
1361 | |
|
1362 | 0 | XLogBeginInsert(); |
1363 | 0 | XLogRegisterBuffer(0, buf, REGBUF_STANDARD); |
1364 | 0 | XLogRegisterData(&xlrec_delete, SizeOfBtreeDelete); |
1365 | |
|
1366 | 0 | if (ndeletable > 0) |
1367 | 0 | XLogRegisterBufData(0, deletable, |
1368 | 0 | ndeletable * sizeof(OffsetNumber)); |
1369 | |
|
1370 | 0 | if (nupdatable > 0) |
1371 | 0 | { |
1372 | 0 | XLogRegisterBufData(0, updatedoffsets, |
1373 | 0 | nupdatable * sizeof(OffsetNumber)); |
1374 | 0 | XLogRegisterBufData(0, updatedbuf, updatedbuflen); |
1375 | 0 | } |
1376 | |
|
1377 | 0 | recptr = XLogInsert(RM_BTREE_ID, XLOG_BTREE_DELETE); |
1378 | 0 | } |
1379 | 0 | else |
1380 | 0 | recptr = XLogGetFakeLSN(rel); |
1381 | |
|
1382 | 0 | PageSetLSN(page, recptr); |
1383 | |
|
1384 | 0 | END_CRIT_SECTION(); |
1385 | | |
1386 | | /* can't leak memory here */ |
1387 | 0 | if (updatedbuf != NULL) |
1388 | 0 | pfree(updatedbuf); |
1389 | | /* free tuples allocated within _bt_delitems_update() */ |
1390 | 0 | for (int i = 0; i < nupdatable; i++) |
1391 | 0 | pfree(updatable[i]->itup); |
1392 | 0 | } |
1393 | | |
1394 | | /* |
1395 | | * Set up state needed to delete TIDs from posting list tuples via "updating" |
1396 | | * the tuple. Performs steps common to both _bt_delitems_vacuum and |
1397 | | * _bt_delitems_delete. These steps must take place before each function's |
1398 | | * critical section begins. |
1399 | | * |
1400 | | * updatable and nupdatable are inputs, though note that we will use |
1401 | | * _bt_update_posting() to replace the original itup with a pointer to a final |
1402 | | * version in palloc()'d memory. Caller should free the tuples when its done. |
1403 | | * |
1404 | | * The first nupdatable entries from updatedoffsets are set to the page offset |
1405 | | * number for posting list tuples that caller updates. This is mostly useful |
1406 | | * because caller may need to WAL-log the page offsets (though we always do |
1407 | | * this for caller out of convenience). |
1408 | | * |
1409 | | * Returns buffer consisting of an array of xl_btree_update structs that |
1410 | | * describe the steps we perform here for caller (though only when needswal is |
1411 | | * true). Also sets *updatedbuflen to the final size of the buffer. This |
1412 | | * buffer is used by caller when WAL logging is required. |
1413 | | */ |
1414 | | static char * |
1415 | | _bt_delitems_update(BTVacuumPosting *updatable, int nupdatable, |
1416 | | OffsetNumber *updatedoffsets, Size *updatedbuflen, |
1417 | | bool needswal) |
1418 | 0 | { |
1419 | 0 | char *updatedbuf = NULL; |
1420 | 0 | Size buflen = 0; |
1421 | | |
1422 | | /* Shouldn't be called unless there's something to do */ |
1423 | 0 | Assert(nupdatable > 0); |
1424 | |
|
1425 | 0 | for (int i = 0; i < nupdatable; i++) |
1426 | 0 | { |
1427 | 0 | BTVacuumPosting vacposting = updatable[i]; |
1428 | 0 | Size itemsz; |
1429 | | |
1430 | | /* Replace work area IndexTuple with updated version */ |
1431 | 0 | _bt_update_posting(vacposting); |
1432 | | |
1433 | | /* Keep track of size of xl_btree_update for updatedbuf in passing */ |
1434 | 0 | itemsz = SizeOfBtreeUpdate + vacposting->ndeletedtids * sizeof(uint16); |
1435 | 0 | buflen += itemsz; |
1436 | | |
1437 | | /* Build updatedoffsets buffer in passing */ |
1438 | 0 | updatedoffsets[i] = vacposting->updatedoffset; |
1439 | 0 | } |
1440 | | |
1441 | | /* XLOG stuff */ |
1442 | 0 | if (needswal) |
1443 | 0 | { |
1444 | 0 | Size offset = 0; |
1445 | | |
1446 | | /* Allocate, set final size for caller */ |
1447 | 0 | updatedbuf = palloc(buflen); |
1448 | 0 | *updatedbuflen = buflen; |
1449 | 0 | for (int i = 0; i < nupdatable; i++) |
1450 | 0 | { |
1451 | 0 | BTVacuumPosting vacposting = updatable[i]; |
1452 | 0 | Size itemsz; |
1453 | 0 | xl_btree_update update; |
1454 | |
|
1455 | 0 | update.ndeletedtids = vacposting->ndeletedtids; |
1456 | 0 | memcpy(updatedbuf + offset, &update.ndeletedtids, |
1457 | 0 | SizeOfBtreeUpdate); |
1458 | 0 | offset += SizeOfBtreeUpdate; |
1459 | |
|
1460 | 0 | itemsz = update.ndeletedtids * sizeof(uint16); |
1461 | 0 | memcpy(updatedbuf + offset, vacposting->deletetids, itemsz); |
1462 | 0 | offset += itemsz; |
1463 | 0 | } |
1464 | 0 | } |
1465 | |
|
1466 | 0 | return updatedbuf; |
1467 | 0 | } |
1468 | | |
1469 | | /* |
1470 | | * Comparator used by _bt_delitems_delete_check() to restore deltids array |
1471 | | * back to its original leaf-page-wise sort order |
1472 | | */ |
1473 | | static int |
1474 | | _bt_delitems_cmp(const void *a, const void *b) |
1475 | 0 | { |
1476 | 0 | const TM_IndexDelete *indexdelete1 = a; |
1477 | 0 | const TM_IndexDelete *indexdelete2 = b; |
1478 | |
|
1479 | 0 | Assert(indexdelete1->id != indexdelete2->id); |
1480 | |
|
1481 | 0 | return pg_cmp_s16(indexdelete1->id, indexdelete2->id); |
1482 | 0 | } |
1483 | | |
1484 | | /* |
1485 | | * Try to delete item(s) from a btree leaf page during single-page cleanup. |
1486 | | * |
1487 | | * nbtree interface to table_index_delete_tuples(). Deletes a subset of index |
1488 | | * tuples from caller's deltids array: those whose TIDs are found safe to |
1489 | | * delete by the tableam (or already marked LP_DEAD in index, and so already |
1490 | | * known to be deletable by our simple index deletion caller). We physically |
1491 | | * delete index tuples from buf leaf page last of all (for index tuples where |
1492 | | * that is known to be safe following our table_index_delete_tuples() call). |
1493 | | * |
1494 | | * Simple index deletion caller only includes TIDs from index tuples marked |
1495 | | * LP_DEAD, as well as extra TIDs it found on the same leaf page that can be |
1496 | | * included without increasing the total number of distinct table blocks for |
1497 | | * the deletion operation as a whole. This approach often allows us to delete |
1498 | | * some extra index tuples that were practically free for tableam to check in |
1499 | | * passing (when they actually turn out to be safe to delete). It probably |
1500 | | * only makes sense for the tableam to go ahead with these extra checks when |
1501 | | * it is block-oriented (otherwise the checks probably won't be practically |
1502 | | * free, which we rely on). The tableam interface requires the tableam side |
1503 | | * to handle the problem, though, so this is okay (we as an index AM are free |
1504 | | * to make the simplifying assumption that all tableams must be block-based). |
1505 | | * |
1506 | | * Bottom-up index deletion caller provides all the TIDs from the leaf page, |
1507 | | * without expecting that tableam will check most of them. The tableam has |
1508 | | * considerable discretion around which entries/blocks it checks. Our role in |
1509 | | * costing the bottom-up deletion operation is strictly advisory. |
1510 | | * |
1511 | | * Note: Caller must have added deltids entries (i.e. entries that go in |
1512 | | * delstate's main array) in leaf-page-wise order: page offset number order, |
1513 | | * TID order among entries taken from the same posting list tuple (tiebreak on |
1514 | | * TID). This order is convenient to work with here. |
1515 | | * |
1516 | | * Note: We also rely on the id field of each deltids element "capturing" this |
1517 | | * original leaf-page-wise order. That is, we expect to be able to get back |
1518 | | * to the original leaf-page-wise order just by sorting deltids on the id |
1519 | | * field (tableam will sort deltids for its own reasons, so we'll need to put |
1520 | | * it back in leaf-page-wise order afterwards). |
1521 | | */ |
1522 | | void |
1523 | | _bt_delitems_delete_check(Relation rel, Buffer buf, Relation heapRel, |
1524 | | TM_IndexDeleteOp *delstate) |
1525 | 0 | { |
1526 | 0 | Page page = BufferGetPage(buf); |
1527 | 0 | TransactionId snapshotConflictHorizon; |
1528 | 0 | bool isCatalogRel; |
1529 | 0 | OffsetNumber postingidxoffnum = InvalidOffsetNumber; |
1530 | 0 | int ndeletable = 0, |
1531 | 0 | nupdatable = 0; |
1532 | 0 | OffsetNumber deletable[MaxIndexTuplesPerPage]; |
1533 | 0 | BTVacuumPosting updatable[MaxIndexTuplesPerPage]; |
1534 | | |
1535 | | /* Use tableam interface to determine which tuples to delete first */ |
1536 | 0 | snapshotConflictHorizon = table_index_delete_tuples(heapRel, delstate); |
1537 | 0 | isCatalogRel = RelationIsAccessibleInLogicalDecoding(heapRel); |
1538 | | |
1539 | | /* Should not WAL-log snapshotConflictHorizon unless it's required */ |
1540 | 0 | if (!XLogStandbyInfoActive()) |
1541 | 0 | snapshotConflictHorizon = InvalidTransactionId; |
1542 | | |
1543 | | /* |
1544 | | * Construct a leaf-page-wise description of what _bt_delitems_delete() |
1545 | | * needs to do to physically delete index tuples from the page. |
1546 | | * |
1547 | | * Must sort deltids array to restore leaf-page-wise order (original order |
1548 | | * before call to tableam). This is the order that the loop expects. |
1549 | | * |
1550 | | * Note that deltids array might be a lot smaller now. It might even have |
1551 | | * no entries at all (with bottom-up deletion caller), in which case there |
1552 | | * is nothing left to do. |
1553 | | */ |
1554 | 0 | qsort(delstate->deltids, delstate->ndeltids, sizeof(TM_IndexDelete), |
1555 | 0 | _bt_delitems_cmp); |
1556 | 0 | if (delstate->ndeltids == 0) |
1557 | 0 | { |
1558 | 0 | Assert(delstate->bottomup); |
1559 | 0 | return; |
1560 | 0 | } |
1561 | | |
1562 | | /* We definitely have to delete at least one index tuple (or one TID) */ |
1563 | 0 | for (int i = 0; i < delstate->ndeltids; i++) |
1564 | 0 | { |
1565 | 0 | TM_IndexStatus *dstatus = delstate->status + delstate->deltids[i].id; |
1566 | 0 | OffsetNumber idxoffnum = dstatus->idxoffnum; |
1567 | 0 | ItemId itemid = PageGetItemId(page, idxoffnum); |
1568 | 0 | IndexTuple itup = (IndexTuple) PageGetItem(page, itemid); |
1569 | 0 | int nestedi, |
1570 | 0 | nitem; |
1571 | 0 | BTVacuumPosting vacposting; |
1572 | |
|
1573 | 0 | Assert(OffsetNumberIsValid(idxoffnum)); |
1574 | |
|
1575 | 0 | if (idxoffnum == postingidxoffnum) |
1576 | 0 | { |
1577 | | /* |
1578 | | * This deltid entry is a TID from a posting list tuple that has |
1579 | | * already been completely processed |
1580 | | */ |
1581 | 0 | Assert(BTreeTupleIsPosting(itup)); |
1582 | 0 | Assert(ItemPointerCompare(BTreeTupleGetHeapTID(itup), |
1583 | 0 | &delstate->deltids[i].tid) < 0); |
1584 | 0 | Assert(ItemPointerCompare(BTreeTupleGetMaxHeapTID(itup), |
1585 | 0 | &delstate->deltids[i].tid) >= 0); |
1586 | 0 | continue; |
1587 | 0 | } |
1588 | | |
1589 | 0 | if (!BTreeTupleIsPosting(itup)) |
1590 | 0 | { |
1591 | | /* Plain non-pivot tuple */ |
1592 | 0 | Assert(ItemPointerEquals(&itup->t_tid, &delstate->deltids[i].tid)); |
1593 | 0 | if (dstatus->knowndeletable) |
1594 | 0 | deletable[ndeletable++] = idxoffnum; |
1595 | 0 | continue; |
1596 | 0 | } |
1597 | | |
1598 | | /* |
1599 | | * itup is a posting list tuple whose lowest deltids entry (which may |
1600 | | * or may not be for the first TID from itup) is considered here now. |
1601 | | * We should process all of the deltids entries for the posting list |
1602 | | * together now, though (not just the lowest). Remember to skip over |
1603 | | * later itup-related entries during later iterations of outermost |
1604 | | * loop. |
1605 | | */ |
1606 | 0 | postingidxoffnum = idxoffnum; /* Remember work in outermost loop */ |
1607 | 0 | nestedi = i; /* Initialize for first itup deltids entry */ |
1608 | 0 | vacposting = NULL; /* Describes final action for itup */ |
1609 | 0 | nitem = BTreeTupleGetNPosting(itup); |
1610 | 0 | for (int p = 0; p < nitem; p++) |
1611 | 0 | { |
1612 | 0 | ItemPointer ptid = BTreeTupleGetPostingN(itup, p); |
1613 | 0 | int ptidcmp = -1; |
1614 | | |
1615 | | /* |
1616 | | * This nested loop reuses work across ptid TIDs taken from itup. |
1617 | | * We take advantage of the fact that both itup's TIDs and deltids |
1618 | | * entries (within a single itup/posting list grouping) must both |
1619 | | * be in ascending TID order. |
1620 | | */ |
1621 | 0 | for (; nestedi < delstate->ndeltids; nestedi++) |
1622 | 0 | { |
1623 | 0 | TM_IndexDelete *tcdeltid = &delstate->deltids[nestedi]; |
1624 | 0 | TM_IndexStatus *tdstatus = (delstate->status + tcdeltid->id); |
1625 | | |
1626 | | /* Stop once we get past all itup related deltids entries */ |
1627 | 0 | Assert(tdstatus->idxoffnum >= idxoffnum); |
1628 | 0 | if (tdstatus->idxoffnum != idxoffnum) |
1629 | 0 | break; |
1630 | | |
1631 | | /* Skip past non-deletable itup related entries up front */ |
1632 | 0 | if (!tdstatus->knowndeletable) |
1633 | 0 | continue; |
1634 | | |
1635 | | /* Entry is first partial ptid match (or an exact match)? */ |
1636 | 0 | ptidcmp = ItemPointerCompare(&tcdeltid->tid, ptid); |
1637 | 0 | if (ptidcmp >= 0) |
1638 | 0 | { |
1639 | | /* Greater than or equal (partial or exact) match... */ |
1640 | 0 | break; |
1641 | 0 | } |
1642 | 0 | } |
1643 | | |
1644 | | /* ...exact ptid match to a deletable deltids entry? */ |
1645 | 0 | if (ptidcmp != 0) |
1646 | 0 | continue; |
1647 | | |
1648 | | /* Exact match for deletable deltids entry -- ptid gets deleted */ |
1649 | 0 | if (vacposting == NULL) |
1650 | 0 | { |
1651 | 0 | vacposting = palloc(offsetof(BTVacuumPostingData, deletetids) + |
1652 | 0 | nitem * sizeof(uint16)); |
1653 | 0 | vacposting->itup = itup; |
1654 | 0 | vacposting->updatedoffset = idxoffnum; |
1655 | 0 | vacposting->ndeletedtids = 0; |
1656 | 0 | } |
1657 | 0 | vacposting->deletetids[vacposting->ndeletedtids++] = p; |
1658 | 0 | } |
1659 | | |
1660 | | /* Final decision on itup, a posting list tuple */ |
1661 | |
|
1662 | 0 | if (vacposting == NULL) |
1663 | 0 | { |
1664 | | /* No TIDs to delete from itup -- do nothing */ |
1665 | 0 | } |
1666 | 0 | else if (vacposting->ndeletedtids == nitem) |
1667 | 0 | { |
1668 | | /* Straight delete of itup (to delete all TIDs) */ |
1669 | 0 | deletable[ndeletable++] = idxoffnum; |
1670 | | /* Turns out we won't need granular information */ |
1671 | 0 | pfree(vacposting); |
1672 | 0 | } |
1673 | 0 | else |
1674 | 0 | { |
1675 | | /* Delete some (but not all) TIDs from itup */ |
1676 | 0 | Assert(vacposting->ndeletedtids > 0 && |
1677 | 0 | vacposting->ndeletedtids < nitem); |
1678 | 0 | updatable[nupdatable++] = vacposting; |
1679 | 0 | } |
1680 | 0 | } |
1681 | | |
1682 | | /* Physically delete tuples (or TIDs) using deletable (or updatable) */ |
1683 | 0 | _bt_delitems_delete(rel, buf, snapshotConflictHorizon, isCatalogRel, |
1684 | 0 | deletable, ndeletable, updatable, nupdatable); |
1685 | | |
1686 | | /* be tidy */ |
1687 | 0 | for (int i = 0; i < nupdatable; i++) |
1688 | 0 | pfree(updatable[i]); |
1689 | 0 | } |
1690 | | |
1691 | | /* |
1692 | | * Check that leftsib page (the btpo_prev of target page) is not marked with |
1693 | | * INCOMPLETE_SPLIT flag. Used during page deletion. |
1694 | | * |
1695 | | * Returning true indicates that page flag is set in leftsib (which is |
1696 | | * definitely still the left sibling of target). When that happens, the |
1697 | | * target doesn't have a downlink in parent, and the page deletion algorithm |
1698 | | * isn't prepared to handle that. Deletion of the target page (or the whole |
1699 | | * subtree that contains the target page) cannot take place. |
1700 | | * |
1701 | | * Caller should not have a lock on the target page itself, since pages on the |
1702 | | * same level must always be locked left to right to avoid deadlocks. |
1703 | | */ |
1704 | | static bool |
1705 | | _bt_leftsib_splitflag(Relation rel, BlockNumber leftsib, BlockNumber target) |
1706 | 0 | { |
1707 | 0 | Buffer buf; |
1708 | 0 | Page page; |
1709 | 0 | BTPageOpaque opaque; |
1710 | 0 | bool result; |
1711 | | |
1712 | | /* Easy case: No left sibling */ |
1713 | 0 | if (leftsib == P_NONE) |
1714 | 0 | return false; |
1715 | | |
1716 | 0 | buf = _bt_getbuf(rel, leftsib, BT_READ); |
1717 | 0 | page = BufferGetPage(buf); |
1718 | 0 | opaque = BTPageGetOpaque(page); |
1719 | | |
1720 | | /* |
1721 | | * If the left sibling was concurrently split, so that its next-pointer |
1722 | | * doesn't point to the current page anymore, the split that created |
1723 | | * target must be completed. Caller can reasonably expect that there will |
1724 | | * be a downlink to the target page that it can relocate using its stack. |
1725 | | * (We don't allow splitting an incompletely split page again until the |
1726 | | * previous split has been completed.) |
1727 | | */ |
1728 | 0 | result = (opaque->btpo_next == target && P_INCOMPLETE_SPLIT(opaque)); |
1729 | 0 | _bt_relbuf(rel, buf); |
1730 | |
|
1731 | 0 | return result; |
1732 | 0 | } |
1733 | | |
1734 | | /* |
1735 | | * Check that leafrightsib page (the btpo_next of target leaf page) is not |
1736 | | * marked with ISHALFDEAD flag. Used during page deletion. |
1737 | | * |
1738 | | * Returning true indicates that page flag is set in leafrightsib, so page |
1739 | | * deletion cannot go ahead. Our caller is not prepared to deal with the case |
1740 | | * where the parent page does not have a pivot tuples whose downlink points to |
1741 | | * leafrightsib (due to an earlier interrupted VACUUM operation). It doesn't |
1742 | | * seem worth going to the trouble of teaching our caller to deal with it. |
1743 | | * The situation will be resolved after VACUUM finishes the deletion of the |
1744 | | * half-dead page (when a future VACUUM operation reaches the target page |
1745 | | * again). |
1746 | | * |
1747 | | * _bt_leftsib_splitflag() is called for both leaf pages and internal pages. |
1748 | | * _bt_rightsib_halfdeadflag() is only called for leaf pages, though. This is |
1749 | | * okay because of the restriction on deleting pages that are the rightmost |
1750 | | * page of their parent (i.e. that such deletions can only take place when the |
1751 | | * entire subtree must be deleted). The leaf level check made here will apply |
1752 | | * to a right "cousin" leaf page rather than a simple right sibling leaf page |
1753 | | * in cases where caller actually goes on to attempt deleting pages that are |
1754 | | * above the leaf page. The right cousin leaf page is representative of the |
1755 | | * left edge of the subtree to the right of the to-be-deleted subtree as a |
1756 | | * whole, which is exactly the condition that our caller cares about. |
1757 | | * (Besides, internal pages are never marked half-dead, so it isn't even |
1758 | | * possible to _directly_ assess if an internal page is part of some other |
1759 | | * to-be-deleted subtree.) |
1760 | | */ |
1761 | | static bool |
1762 | | _bt_rightsib_halfdeadflag(Relation rel, BlockNumber leafrightsib) |
1763 | 0 | { |
1764 | 0 | Buffer buf; |
1765 | 0 | Page page; |
1766 | 0 | BTPageOpaque opaque; |
1767 | 0 | bool result; |
1768 | |
|
1769 | 0 | Assert(leafrightsib != P_NONE); |
1770 | |
|
1771 | 0 | buf = _bt_getbuf(rel, leafrightsib, BT_READ); |
1772 | 0 | page = BufferGetPage(buf); |
1773 | 0 | opaque = BTPageGetOpaque(page); |
1774 | |
|
1775 | 0 | Assert(P_ISLEAF(opaque) && !P_ISDELETED(opaque)); |
1776 | 0 | result = P_ISHALFDEAD(opaque); |
1777 | 0 | _bt_relbuf(rel, buf); |
1778 | |
|
1779 | 0 | return result; |
1780 | 0 | } |
1781 | | |
1782 | | /* |
1783 | | * _bt_pagedel() -- Delete a leaf page from the b-tree, if legal to do so. |
1784 | | * |
1785 | | * This action unlinks the leaf page from the b-tree structure, removing all |
1786 | | * pointers leading to it --- but not touching its own left and right links. |
1787 | | * The page cannot be physically reclaimed right away, since other processes |
1788 | | * may currently be trying to follow links leading to the page; they have to |
1789 | | * be allowed to use its right-link to recover. See nbtree/README. |
1790 | | * |
1791 | | * On entry, the target buffer must be pinned and locked (either read or write |
1792 | | * lock is OK). The page must be an empty leaf page, which may be half-dead |
1793 | | * already (a half-dead page should only be passed to us when an earlier |
1794 | | * VACUUM operation was interrupted, though). Note in particular that caller |
1795 | | * should never pass a buffer containing an existing deleted page here. The |
1796 | | * lock and pin on caller's buffer will be dropped before we return. |
1797 | | * |
1798 | | * Maintains bulk delete stats for caller, which are taken from vstate. We |
1799 | | * need to cooperate closely with caller here so that whole VACUUM operation |
1800 | | * reliably avoids any double counting of subsidiary-to-leafbuf pages that we |
1801 | | * delete in passing. If such pages happen to be from a block number that is |
1802 | | * ahead of the current scanblkno position, then caller is expected to count |
1803 | | * them directly later on. It's simpler for us to understand caller's |
1804 | | * requirements than it would be for caller to understand when or how a |
1805 | | * deleted page became deleted after the fact. |
1806 | | * |
1807 | | * NOTE: this leaks memory. Rather than trying to clean up everything |
1808 | | * carefully, it's better to run it in a temp context that can be reset |
1809 | | * frequently. |
1810 | | */ |
1811 | | void |
1812 | | _bt_pagedel(Relation rel, Buffer leafbuf, BTVacState *vstate) |
1813 | 0 | { |
1814 | 0 | BlockNumber rightsib; |
1815 | 0 | bool rightsib_empty; |
1816 | 0 | Page page; |
1817 | 0 | BTPageOpaque opaque; |
1818 | | |
1819 | | /* |
1820 | | * Save original leafbuf block number from caller. Only deleted blocks |
1821 | | * that are <= scanblkno are added to bulk delete stat's pages_deleted |
1822 | | * count. |
1823 | | */ |
1824 | 0 | BlockNumber scanblkno = BufferGetBlockNumber(leafbuf); |
1825 | | |
1826 | | /* |
1827 | | * "stack" is a search stack leading (approximately) to the target page. |
1828 | | * It is initially NULL, but when iterating, we keep it to avoid |
1829 | | * duplicated search effort. |
1830 | | * |
1831 | | * Also, when "stack" is not NULL, we have already checked that the |
1832 | | * current page is not the right half of an incomplete split, i.e. the |
1833 | | * left sibling does not have its INCOMPLETE_SPLIT flag set, including |
1834 | | * when the current target page is to the right of caller's initial page |
1835 | | * (the scanblkno page). |
1836 | | */ |
1837 | 0 | BTStack stack = NULL; |
1838 | |
|
1839 | 0 | for (;;) |
1840 | 0 | { |
1841 | 0 | page = BufferGetPage(leafbuf); |
1842 | 0 | opaque = BTPageGetOpaque(page); |
1843 | | |
1844 | | /* |
1845 | | * Internal pages are never deleted directly, only as part of deleting |
1846 | | * the whole subtree all the way down to leaf level. |
1847 | | * |
1848 | | * Also check for deleted pages here. Caller never passes us a fully |
1849 | | * deleted page. Only VACUUM can delete pages, so there can't have |
1850 | | * been a concurrent deletion. Assume that we reached any deleted |
1851 | | * page encountered here by following a sibling link, and that the |
1852 | | * index is corrupt. |
1853 | | */ |
1854 | 0 | Assert(!P_ISDELETED(opaque)); |
1855 | 0 | if (!P_ISLEAF(opaque) || P_ISDELETED(opaque)) |
1856 | 0 | { |
1857 | | /* |
1858 | | * Pre-9.4 page deletion only marked internal pages as half-dead, |
1859 | | * but now we only use that flag on leaf pages. The old algorithm |
1860 | | * was never supposed to leave half-dead pages in the tree, it was |
1861 | | * just a transient state, but it was nevertheless possible in |
1862 | | * error scenarios. We don't know how to deal with them here. They |
1863 | | * are harmless as far as searches are considered, but inserts |
1864 | | * into the deleted keyspace could add out-of-order downlinks in |
1865 | | * the upper levels. Log a notice, hopefully the admin will notice |
1866 | | * and reindex. |
1867 | | */ |
1868 | 0 | if (P_ISHALFDEAD(opaque)) |
1869 | 0 | ereport(LOG, |
1870 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
1871 | 0 | errmsg("index \"%s\" contains a half-dead internal page", |
1872 | 0 | RelationGetRelationName(rel)), |
1873 | 0 | errhint("This can be caused by an interrupted VACUUM in version 9.3 or older, before upgrade. Please REINDEX it."))); |
1874 | | |
1875 | 0 | if (P_ISDELETED(opaque)) |
1876 | 0 | ereport(LOG, |
1877 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
1878 | 0 | errmsg_internal("found deleted block %u while following right link from block %u in index \"%s\"", |
1879 | 0 | BufferGetBlockNumber(leafbuf), |
1880 | 0 | scanblkno, |
1881 | 0 | RelationGetRelationName(rel)))); |
1882 | | |
1883 | 0 | _bt_relbuf(rel, leafbuf); |
1884 | 0 | return; |
1885 | 0 | } |
1886 | | |
1887 | | /* |
1888 | | * We can never delete rightmost pages nor root pages. While at it, |
1889 | | * check that page is empty, since it's possible that the leafbuf page |
1890 | | * was empty a moment ago, but has since had some inserts. |
1891 | | * |
1892 | | * To keep the algorithm simple, we also never delete an incompletely |
1893 | | * split page (they should be rare enough that this doesn't make any |
1894 | | * meaningful difference to disk usage): |
1895 | | * |
1896 | | * The INCOMPLETE_SPLIT flag on the page tells us if the page is the |
1897 | | * left half of an incomplete split, but ensuring that it's not the |
1898 | | * right half is more complicated. For that, we have to check that |
1899 | | * the left sibling doesn't have its INCOMPLETE_SPLIT flag set using |
1900 | | * _bt_leftsib_splitflag(). On the first iteration, we temporarily |
1901 | | * release the lock on scanblkno/leafbuf, check the left sibling, and |
1902 | | * construct a search stack to scanblkno. On subsequent iterations, |
1903 | | * we know we stepped right from a page that passed these tests, so |
1904 | | * it's OK. |
1905 | | */ |
1906 | 0 | if (P_RIGHTMOST(opaque) || P_ISROOT(opaque) || |
1907 | 0 | P_FIRSTDATAKEY(opaque) <= PageGetMaxOffsetNumber(page) || |
1908 | 0 | P_INCOMPLETE_SPLIT(opaque)) |
1909 | 0 | { |
1910 | | /* Should never fail to delete a half-dead page */ |
1911 | 0 | Assert(!P_ISHALFDEAD(opaque)); |
1912 | |
|
1913 | 0 | _bt_relbuf(rel, leafbuf); |
1914 | 0 | return; |
1915 | 0 | } |
1916 | | |
1917 | | /* |
1918 | | * First, remove downlink pointing to the page (or a parent of the |
1919 | | * page, if we are going to delete a taller subtree), and mark the |
1920 | | * leafbuf page half-dead |
1921 | | */ |
1922 | 0 | if (!P_ISHALFDEAD(opaque)) |
1923 | 0 | { |
1924 | | /* |
1925 | | * We need an approximate pointer to the page's parent page. We |
1926 | | * use a variant of the standard search mechanism to search for |
1927 | | * the page's high key; this will give us a link to either the |
1928 | | * current parent or someplace to its left (if there are multiple |
1929 | | * equal high keys, which is possible with !heapkeyspace indexes). |
1930 | | * |
1931 | | * Also check if this is the right-half of an incomplete split |
1932 | | * (see comment above). |
1933 | | */ |
1934 | 0 | if (!stack) |
1935 | 0 | { |
1936 | 0 | BTScanInsert itup_key; |
1937 | 0 | ItemId itemid; |
1938 | 0 | IndexTuple targetkey; |
1939 | 0 | BlockNumber leftsib, |
1940 | 0 | leafblkno; |
1941 | 0 | Buffer sleafbuf; |
1942 | |
|
1943 | 0 | itemid = PageGetItemId(page, P_HIKEY); |
1944 | 0 | targetkey = CopyIndexTuple((IndexTuple) PageGetItem(page, itemid)); |
1945 | |
|
1946 | 0 | leftsib = opaque->btpo_prev; |
1947 | 0 | leafblkno = BufferGetBlockNumber(leafbuf); |
1948 | | |
1949 | | /* |
1950 | | * To avoid deadlocks, we'd better drop the leaf page lock |
1951 | | * before going further. |
1952 | | */ |
1953 | 0 | _bt_unlockbuf(rel, leafbuf); |
1954 | | |
1955 | | /* |
1956 | | * Check that the left sibling of leafbuf (if any) is not |
1957 | | * marked with INCOMPLETE_SPLIT flag before proceeding |
1958 | | */ |
1959 | 0 | Assert(leafblkno == scanblkno); |
1960 | 0 | if (_bt_leftsib_splitflag(rel, leftsib, leafblkno)) |
1961 | 0 | { |
1962 | 0 | ReleaseBuffer(leafbuf); |
1963 | 0 | return; |
1964 | 0 | } |
1965 | | |
1966 | | /* |
1967 | | * We need an insertion scan key, so build one. |
1968 | | * |
1969 | | * _bt_search searches for the leaf page that contains any |
1970 | | * matching non-pivot tuples, but we need it to "search" for |
1971 | | * the high key pivot from the page that we're set to delete. |
1972 | | * Compensate for the mismatch by having _bt_search locate the |
1973 | | * last position < equal-to-untruncated-prefix non-pivots. |
1974 | | */ |
1975 | 0 | itup_key = _bt_mkscankey(rel, targetkey); |
1976 | | |
1977 | | /* Set up a BTLessStrategyNumber-like insertion scan key */ |
1978 | 0 | itup_key->nextkey = false; |
1979 | 0 | itup_key->backward = true; |
1980 | 0 | stack = _bt_search(rel, NULL, itup_key, &sleafbuf, BT_READ, true); |
1981 | | /* won't need a second lock or pin on leafbuf */ |
1982 | 0 | _bt_relbuf(rel, sleafbuf); |
1983 | | |
1984 | | /* |
1985 | | * Re-lock the leaf page, and start over to use our stack |
1986 | | * within _bt_mark_page_halfdead. We must do it that way |
1987 | | * because it's possible that leafbuf can no longer be |
1988 | | * deleted. We need to recheck. |
1989 | | * |
1990 | | * Note: We can't simply hold on to the sleafbuf lock instead, |
1991 | | * because it's barely possible that sleafbuf is not the same |
1992 | | * page as leafbuf. This happens when leafbuf split after our |
1993 | | * original lock was dropped, but before _bt_search finished |
1994 | | * its descent. We rely on the assumption that we'll find |
1995 | | * leafbuf isn't safe to delete anymore in this scenario. |
1996 | | * (Page deletion can cope with the stack being to the left of |
1997 | | * leafbuf, but not to the right of leafbuf.) |
1998 | | */ |
1999 | 0 | _bt_lockbuf(rel, leafbuf, BT_WRITE); |
2000 | 0 | continue; |
2001 | 0 | } |
2002 | | |
2003 | | /* |
2004 | | * See if it's safe to delete the leaf page, and determine how |
2005 | | * many parent/internal pages above the leaf level will be |
2006 | | * deleted. If it's safe then _bt_mark_page_halfdead will also |
2007 | | * perform the first phase of deletion, which includes marking the |
2008 | | * leafbuf page half-dead. |
2009 | | */ |
2010 | 0 | Assert(P_ISLEAF(opaque) && !P_IGNORE(opaque)); |
2011 | 0 | if (!_bt_mark_page_halfdead(rel, vstate->info->heaprel, leafbuf, |
2012 | 0 | stack)) |
2013 | 0 | { |
2014 | 0 | _bt_relbuf(rel, leafbuf); |
2015 | 0 | return; |
2016 | 0 | } |
2017 | 0 | } |
2018 | 0 | else |
2019 | 0 | { |
2020 | 0 | INJECTION_POINT("nbtree-finish-half-dead-page-vacuum", NULL); |
2021 | 0 | } |
2022 | | |
2023 | | /* |
2024 | | * Then unlink it from its siblings. Each call to |
2025 | | * _bt_unlink_halfdead_page unlinks the topmost page from the subtree, |
2026 | | * making it shallower. Iterate until the leafbuf page is deleted. |
2027 | | */ |
2028 | 0 | rightsib_empty = false; |
2029 | 0 | Assert(P_ISLEAF(opaque) && P_ISHALFDEAD(opaque)); |
2030 | 0 | while (P_ISHALFDEAD(opaque)) |
2031 | 0 | { |
2032 | | /* Check for interrupts in _bt_unlink_halfdead_page */ |
2033 | 0 | if (!_bt_unlink_halfdead_page(rel, leafbuf, scanblkno, |
2034 | 0 | &rightsib_empty, vstate)) |
2035 | 0 | { |
2036 | | /* |
2037 | | * _bt_unlink_halfdead_page should never fail, since we |
2038 | | * established that deletion is generally safe in |
2039 | | * _bt_mark_page_halfdead -- index must be corrupt. |
2040 | | * |
2041 | | * Note that _bt_unlink_halfdead_page already released the |
2042 | | * lock and pin on leafbuf for us. |
2043 | | */ |
2044 | 0 | Assert(false); |
2045 | 0 | return; |
2046 | 0 | } |
2047 | 0 | } |
2048 | | |
2049 | 0 | Assert(P_ISLEAF(opaque) && P_ISDELETED(opaque)); |
2050 | |
|
2051 | 0 | rightsib = opaque->btpo_next; |
2052 | |
|
2053 | 0 | _bt_relbuf(rel, leafbuf); |
2054 | | |
2055 | | /* |
2056 | | * Check here, as calling loops will have locks held, preventing |
2057 | | * interrupts from being processed. |
2058 | | */ |
2059 | 0 | CHECK_FOR_INTERRUPTS(); |
2060 | | |
2061 | | /* |
2062 | | * The page has now been deleted. If its right sibling is completely |
2063 | | * empty, it's possible that the reason we haven't deleted it earlier |
2064 | | * is that it was the rightmost child of the parent. Now that we |
2065 | | * removed the downlink for this page, the right sibling might now be |
2066 | | * the only child of the parent, and could be removed. It would be |
2067 | | * picked up by the next vacuum anyway, but might as well try to |
2068 | | * remove it now, so loop back to process the right sibling. |
2069 | | * |
2070 | | * Note: This relies on the assumption that _bt_getstackbuf() will be |
2071 | | * able to reuse our original descent stack with a different child |
2072 | | * block (provided that the child block is to the right of the |
2073 | | * original leaf page reached by _bt_search()). It will even update |
2074 | | * the descent stack each time we loop around, avoiding repeated work. |
2075 | | */ |
2076 | 0 | if (!rightsib_empty) |
2077 | 0 | break; |
2078 | | |
2079 | 0 | leafbuf = _bt_getbuf(rel, rightsib, BT_WRITE); |
2080 | 0 | } |
2081 | 0 | } |
2082 | | |
2083 | | /* |
2084 | | * First stage of page deletion. |
2085 | | * |
2086 | | * Establish the height of the to-be-deleted subtree with leafbuf at its |
2087 | | * lowest level, remove the downlink to the subtree, and mark leafbuf |
2088 | | * half-dead. The final to-be-deleted subtree is usually just leafbuf itself, |
2089 | | * but may include additional internal pages (at most one per level of the |
2090 | | * tree below the root). |
2091 | | * |
2092 | | * Caller must pass a valid heaprel, since it's just about possible that our |
2093 | | * call to _bt_lock_subtree_parent will need to allocate a new index page to |
2094 | | * complete a page split. Every call to _bt_allocbuf needs to pass a heaprel. |
2095 | | * |
2096 | | * Returns 'false' if leafbuf is unsafe to delete, usually because leafbuf is |
2097 | | * the rightmost child of its parent (and parent has more than one downlink). |
2098 | | * Returns 'true' when the first stage of page deletion completed |
2099 | | * successfully. |
2100 | | */ |
2101 | | static bool |
2102 | | _bt_mark_page_halfdead(Relation rel, Relation heaprel, Buffer leafbuf, |
2103 | | BTStack stack) |
2104 | 0 | { |
2105 | 0 | BlockNumber leafblkno; |
2106 | 0 | BlockNumber leafrightsib; |
2107 | 0 | BlockNumber topparent; |
2108 | 0 | BlockNumber topparentrightsib; |
2109 | 0 | ItemId itemid; |
2110 | 0 | Page page; |
2111 | 0 | BTPageOpaque opaque; |
2112 | 0 | Buffer subtreeparent; |
2113 | 0 | OffsetNumber poffset; |
2114 | 0 | OffsetNumber nextoffset; |
2115 | 0 | IndexTuple itup; |
2116 | 0 | IndexTupleData trunctuple; |
2117 | 0 | XLogRecPtr recptr; |
2118 | |
|
2119 | 0 | page = BufferGetPage(leafbuf); |
2120 | 0 | opaque = BTPageGetOpaque(page); |
2121 | |
|
2122 | 0 | Assert(!P_RIGHTMOST(opaque) && !P_ISROOT(opaque) && |
2123 | 0 | P_ISLEAF(opaque) && !P_IGNORE(opaque) && |
2124 | 0 | P_FIRSTDATAKEY(opaque) > PageGetMaxOffsetNumber(page)); |
2125 | 0 | Assert(heaprel != NULL); |
2126 | | |
2127 | | /* |
2128 | | * Save info about the leaf page. |
2129 | | */ |
2130 | 0 | leafblkno = BufferGetBlockNumber(leafbuf); |
2131 | 0 | leafrightsib = opaque->btpo_next; |
2132 | | |
2133 | | /* |
2134 | | * Before attempting to lock the parent page, check that the right sibling |
2135 | | * is not in half-dead state. A half-dead right sibling would have no |
2136 | | * downlink in the parent, which would be highly confusing later when we |
2137 | | * delete the downlink. It would fail the "right sibling of target page |
2138 | | * is also the next child in parent page" cross-check below. |
2139 | | */ |
2140 | 0 | if (_bt_rightsib_halfdeadflag(rel, leafrightsib)) |
2141 | 0 | { |
2142 | 0 | elog(DEBUG1, "could not delete page %u because its right sibling %u is half-dead", |
2143 | 0 | leafblkno, leafrightsib); |
2144 | 0 | return false; |
2145 | 0 | } |
2146 | | |
2147 | | /* |
2148 | | * We cannot delete a page that is the rightmost child of its immediate |
2149 | | * parent, unless it is the only child --- in which case the parent has to |
2150 | | * be deleted too, and the same condition applies recursively to it. We |
2151 | | * have to check this condition all the way up before trying to delete, |
2152 | | * and lock the parent of the root of the to-be-deleted subtree (the |
2153 | | * "subtree parent"). _bt_lock_subtree_parent() locks the subtree parent |
2154 | | * for us. We remove the downlink to the "top parent" page (subtree root |
2155 | | * page) from the subtree parent page below. |
2156 | | * |
2157 | | * Initialize topparent to be leafbuf page now. The final to-be-deleted |
2158 | | * subtree is often a degenerate one page subtree consisting only of the |
2159 | | * leafbuf page. When that happens, the leafbuf page is the final subtree |
2160 | | * root page/top parent page. |
2161 | | */ |
2162 | 0 | topparent = leafblkno; |
2163 | 0 | topparentrightsib = leafrightsib; |
2164 | 0 | if (!_bt_lock_subtree_parent(rel, heaprel, leafblkno, stack, |
2165 | 0 | &subtreeparent, &poffset, |
2166 | 0 | &topparent, &topparentrightsib)) |
2167 | 0 | return false; |
2168 | | |
2169 | 0 | page = BufferGetPage(subtreeparent); |
2170 | 0 | opaque = BTPageGetOpaque(page); |
2171 | |
|
2172 | | #ifdef USE_ASSERT_CHECKING |
2173 | | |
2174 | | /* |
2175 | | * This is just an assertion because _bt_lock_subtree_parent should have |
2176 | | * guaranteed tuple has the expected contents |
2177 | | */ |
2178 | | itemid = PageGetItemId(page, poffset); |
2179 | | itup = (IndexTuple) PageGetItem(page, itemid); |
2180 | | Assert(BTreeTupleGetDownLink(itup) == topparent); |
2181 | | #endif |
2182 | |
|
2183 | 0 | nextoffset = OffsetNumberNext(poffset); |
2184 | 0 | itemid = PageGetItemId(page, nextoffset); |
2185 | 0 | itup = (IndexTuple) PageGetItem(page, itemid); |
2186 | | |
2187 | | /* |
2188 | | * Check that the parent-page index items we're about to delete/overwrite |
2189 | | * in subtree parent page contain what we expect. This can fail if the |
2190 | | * index has become corrupt for some reason. When that happens we back |
2191 | | * out of deletion of the leafbuf subtree. (This is just like the case |
2192 | | * where _bt_lock_subtree_parent() cannot "re-find" leafbuf's downlink.) |
2193 | | */ |
2194 | 0 | if (BTreeTupleGetDownLink(itup) != topparentrightsib) |
2195 | 0 | { |
2196 | 0 | ereport(LOG, |
2197 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
2198 | 0 | errmsg_internal("right sibling %u of block %u is not next child %u of block %u in index \"%s\"", |
2199 | 0 | topparentrightsib, topparent, |
2200 | 0 | BTreeTupleGetDownLink(itup), |
2201 | 0 | BufferGetBlockNumber(subtreeparent), |
2202 | 0 | RelationGetRelationName(rel)))); |
2203 | | |
2204 | 0 | _bt_relbuf(rel, subtreeparent); |
2205 | 0 | Assert(false); |
2206 | 0 | return false; |
2207 | 0 | } |
2208 | | |
2209 | | /* |
2210 | | * Any insert which would have gone on the leaf block will now go to its |
2211 | | * right sibling. In other words, the key space moves right. |
2212 | | */ |
2213 | 0 | PredicateLockPageCombine(rel, leafblkno, leafrightsib); |
2214 | | |
2215 | | /* No ereport(ERROR) until changes are logged */ |
2216 | 0 | START_CRIT_SECTION(); |
2217 | | |
2218 | | /* |
2219 | | * Update parent of subtree. We want to delete the downlink to the top |
2220 | | * parent page/root of the subtree, and the *following* key. Easiest way |
2221 | | * is to copy the right sibling's downlink over the downlink that points |
2222 | | * to top parent page, and then delete the right sibling's original pivot |
2223 | | * tuple. |
2224 | | * |
2225 | | * Lanin and Shasha make the key space move left when deleting a page, |
2226 | | * whereas the key space moves right here. That's why we cannot simply |
2227 | | * delete the pivot tuple with the downlink to the top parent page. See |
2228 | | * nbtree/README. |
2229 | | */ |
2230 | 0 | page = BufferGetPage(subtreeparent); |
2231 | 0 | opaque = BTPageGetOpaque(page); |
2232 | |
|
2233 | 0 | itemid = PageGetItemId(page, poffset); |
2234 | 0 | itup = (IndexTuple) PageGetItem(page, itemid); |
2235 | 0 | BTreeTupleSetDownLink(itup, topparentrightsib); |
2236 | |
|
2237 | 0 | nextoffset = OffsetNumberNext(poffset); |
2238 | 0 | PageIndexTupleDelete(page, nextoffset); |
2239 | | |
2240 | | /* |
2241 | | * Mark the leaf page as half-dead, and stamp it with a link to the top |
2242 | | * parent page. When the leaf page is also the top parent page, the link |
2243 | | * is set to InvalidBlockNumber. |
2244 | | */ |
2245 | 0 | page = BufferGetPage(leafbuf); |
2246 | 0 | opaque = BTPageGetOpaque(page); |
2247 | 0 | opaque->btpo_flags |= BTP_HALF_DEAD; |
2248 | |
|
2249 | 0 | Assert(PageGetMaxOffsetNumber(page) == P_HIKEY); |
2250 | 0 | MemSet(&trunctuple, 0, sizeof(IndexTupleData)); |
2251 | 0 | trunctuple.t_info = sizeof(IndexTupleData); |
2252 | 0 | if (topparent != leafblkno) |
2253 | 0 | BTreeTupleSetTopParent(&trunctuple, topparent); |
2254 | 0 | else |
2255 | 0 | BTreeTupleSetTopParent(&trunctuple, InvalidBlockNumber); |
2256 | |
|
2257 | 0 | if (!PageIndexTupleOverwrite(page, P_HIKEY, &trunctuple, IndexTupleSize(&trunctuple))) |
2258 | 0 | elog(ERROR, "could not overwrite high key in half-dead page"); |
2259 | | |
2260 | | /* Must mark buffers dirty before XLogInsert */ |
2261 | 0 | MarkBufferDirty(subtreeparent); |
2262 | 0 | MarkBufferDirty(leafbuf); |
2263 | | |
2264 | | /* XLOG stuff */ |
2265 | 0 | if (RelationNeedsWAL(rel)) |
2266 | 0 | { |
2267 | 0 | xl_btree_mark_page_halfdead xlrec; |
2268 | |
|
2269 | 0 | xlrec.poffset = poffset; |
2270 | 0 | xlrec.leafblk = leafblkno; |
2271 | 0 | if (topparent != leafblkno) |
2272 | 0 | xlrec.topparent = topparent; |
2273 | 0 | else |
2274 | 0 | xlrec.topparent = InvalidBlockNumber; |
2275 | |
|
2276 | 0 | XLogBeginInsert(); |
2277 | 0 | XLogRegisterBuffer(0, leafbuf, REGBUF_WILL_INIT); |
2278 | 0 | XLogRegisterBuffer(1, subtreeparent, REGBUF_STANDARD); |
2279 | |
|
2280 | 0 | page = BufferGetPage(leafbuf); |
2281 | 0 | opaque = BTPageGetOpaque(page); |
2282 | 0 | xlrec.leftblk = opaque->btpo_prev; |
2283 | 0 | xlrec.rightblk = opaque->btpo_next; |
2284 | |
|
2285 | 0 | XLogRegisterData(&xlrec, SizeOfBtreeMarkPageHalfDead); |
2286 | |
|
2287 | 0 | recptr = XLogInsert(RM_BTREE_ID, XLOG_BTREE_MARK_PAGE_HALFDEAD); |
2288 | 0 | } |
2289 | 0 | else |
2290 | 0 | recptr = XLogGetFakeLSN(rel); |
2291 | |
|
2292 | 0 | page = BufferGetPage(subtreeparent); |
2293 | 0 | PageSetLSN(page, recptr); |
2294 | 0 | page = BufferGetPage(leafbuf); |
2295 | 0 | PageSetLSN(page, recptr); |
2296 | |
|
2297 | 0 | END_CRIT_SECTION(); |
2298 | |
|
2299 | 0 | _bt_relbuf(rel, subtreeparent); |
2300 | 0 | return true; |
2301 | 0 | } |
2302 | | |
2303 | | /* |
2304 | | * Second stage of page deletion. |
2305 | | * |
2306 | | * Unlinks a single page (in the subtree undergoing deletion) from its |
2307 | | * siblings. Also marks the page deleted. |
2308 | | * |
2309 | | * To get rid of the whole subtree, including the leaf page itself, call here |
2310 | | * until the leaf page is deleted. The original "top parent" established in |
2311 | | * the first stage of deletion is deleted in the first call here, while the |
2312 | | * leaf page is deleted in the last call here. Note that the leaf page itself |
2313 | | * is often the initial top parent page. |
2314 | | * |
2315 | | * Returns 'false' if the page could not be unlinked (shouldn't happen). If |
2316 | | * the right sibling of the current target page is empty, *rightsib_empty is |
2317 | | * set to true, allowing caller to delete the target's right sibling page in |
2318 | | * passing. Note that *rightsib_empty is only actually used by caller when |
2319 | | * target page is leafbuf, following last call here for leafbuf/the subtree |
2320 | | * containing leafbuf. (We always set *rightsib_empty for caller, just to be |
2321 | | * consistent.) |
2322 | | * |
2323 | | * Must hold pin and lock on leafbuf at entry (read or write doesn't matter). |
2324 | | * On success exit, we'll be holding pin and write lock. On failure exit, |
2325 | | * we'll release both pin and lock before returning (we define it that way |
2326 | | * to avoid having to reacquire a lock we already released). |
2327 | | */ |
2328 | | static bool |
2329 | | _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno, |
2330 | | bool *rightsib_empty, BTVacState *vstate) |
2331 | 0 | { |
2332 | 0 | BlockNumber leafblkno = BufferGetBlockNumber(leafbuf); |
2333 | 0 | IndexBulkDeleteResult *stats = vstate->stats; |
2334 | 0 | BlockNumber leafleftsib; |
2335 | 0 | BlockNumber leafrightsib; |
2336 | 0 | BlockNumber target; |
2337 | 0 | BlockNumber leftsib; |
2338 | 0 | BlockNumber rightsib; |
2339 | 0 | Buffer lbuf = InvalidBuffer; |
2340 | 0 | Buffer buf; |
2341 | 0 | Buffer rbuf; |
2342 | 0 | Buffer metabuf = InvalidBuffer; |
2343 | 0 | Page metapg = NULL; |
2344 | 0 | BTMetaPageData *metad = NULL; |
2345 | 0 | ItemId itemid; |
2346 | 0 | Page page; |
2347 | 0 | BTPageOpaque opaque; |
2348 | 0 | FullTransactionId safexid; |
2349 | 0 | bool rightsib_is_rightmost; |
2350 | 0 | uint32 targetlevel; |
2351 | 0 | IndexTuple leafhikey; |
2352 | 0 | BlockNumber leaftopparent; |
2353 | 0 | XLogRecPtr recptr; |
2354 | |
|
2355 | 0 | page = BufferGetPage(leafbuf); |
2356 | 0 | opaque = BTPageGetOpaque(page); |
2357 | |
|
2358 | 0 | Assert(P_ISLEAF(opaque) && !P_ISDELETED(opaque) && P_ISHALFDEAD(opaque)); |
2359 | | |
2360 | | /* |
2361 | | * Remember some information about the leaf page. |
2362 | | */ |
2363 | 0 | itemid = PageGetItemId(page, P_HIKEY); |
2364 | 0 | leafhikey = (IndexTuple) PageGetItem(page, itemid); |
2365 | 0 | target = BTreeTupleGetTopParent(leafhikey); |
2366 | 0 | leafleftsib = opaque->btpo_prev; |
2367 | 0 | leafrightsib = opaque->btpo_next; |
2368 | |
|
2369 | 0 | _bt_unlockbuf(rel, leafbuf); |
2370 | |
|
2371 | 0 | INJECTION_POINT("nbtree-leave-page-half-dead", NULL); |
2372 | | |
2373 | | /* |
2374 | | * Check here, as calling loops will have locks held, preventing |
2375 | | * interrupts from being processed. |
2376 | | */ |
2377 | 0 | CHECK_FOR_INTERRUPTS(); |
2378 | | |
2379 | | /* Unlink the current top parent of the subtree */ |
2380 | 0 | if (!BlockNumberIsValid(target)) |
2381 | 0 | { |
2382 | | /* Target is leaf page (or leaf page is top parent, if you prefer) */ |
2383 | 0 | target = leafblkno; |
2384 | |
|
2385 | 0 | buf = leafbuf; |
2386 | 0 | leftsib = leafleftsib; |
2387 | 0 | targetlevel = 0; |
2388 | 0 | } |
2389 | 0 | else |
2390 | 0 | { |
2391 | | /* Target is the internal page taken from leaf's top parent link */ |
2392 | 0 | Assert(target != leafblkno); |
2393 | | |
2394 | | /* Fetch the block number of the target's left sibling */ |
2395 | 0 | buf = _bt_getbuf(rel, target, BT_READ); |
2396 | 0 | page = BufferGetPage(buf); |
2397 | 0 | opaque = BTPageGetOpaque(page); |
2398 | 0 | leftsib = opaque->btpo_prev; |
2399 | 0 | targetlevel = opaque->btpo_level; |
2400 | 0 | Assert(targetlevel > 0); |
2401 | | |
2402 | | /* |
2403 | | * To avoid deadlocks, we'd better drop the target page lock before |
2404 | | * going further. |
2405 | | */ |
2406 | 0 | _bt_unlockbuf(rel, buf); |
2407 | 0 | } |
2408 | | |
2409 | | /* |
2410 | | * We have to lock the pages we need to modify in the standard order: |
2411 | | * moving right, then up. Else we will deadlock against other writers. |
2412 | | * |
2413 | | * So, first lock the leaf page, if it's not the target. Then find and |
2414 | | * write-lock the current left sibling of the target page. The sibling |
2415 | | * that was current a moment ago could have split, so we may have to move |
2416 | | * right. |
2417 | | */ |
2418 | 0 | if (target != leafblkno) |
2419 | 0 | _bt_lockbuf(rel, leafbuf, BT_WRITE); |
2420 | 0 | if (leftsib != P_NONE) |
2421 | 0 | { |
2422 | 0 | lbuf = _bt_getbuf(rel, leftsib, BT_WRITE); |
2423 | 0 | page = BufferGetPage(lbuf); |
2424 | 0 | opaque = BTPageGetOpaque(page); |
2425 | 0 | while (P_ISDELETED(opaque) || opaque->btpo_next != target) |
2426 | 0 | { |
2427 | 0 | bool leftsibvalid = true; |
2428 | | |
2429 | | /* |
2430 | | * Before we follow the link from the page that was the left |
2431 | | * sibling mere moments ago, validate its right link. This |
2432 | | * reduces the opportunities for loop to fail to ever make any |
2433 | | * progress in the presence of index corruption. |
2434 | | * |
2435 | | * Note: we rely on the assumption that there can only be one |
2436 | | * vacuum process running at a time (against the same index). |
2437 | | */ |
2438 | 0 | if (P_RIGHTMOST(opaque) || P_ISDELETED(opaque) || |
2439 | 0 | leftsib == opaque->btpo_next) |
2440 | 0 | leftsibvalid = false; |
2441 | |
|
2442 | 0 | leftsib = opaque->btpo_next; |
2443 | 0 | _bt_relbuf(rel, lbuf); |
2444 | |
|
2445 | 0 | if (!leftsibvalid) |
2446 | 0 | { |
2447 | | /* |
2448 | | * This is known to fail in the field; sibling link corruption |
2449 | | * is relatively common. Press on with vacuuming rather than |
2450 | | * just throwing an ERROR. |
2451 | | */ |
2452 | 0 | ereport(LOG, |
2453 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
2454 | 0 | errmsg_internal("valid left sibling for deletion target could not be located: " |
2455 | 0 | "left sibling %u of target %u with leafblkno %u and scanblkno %u on level %u of index \"%s\"", |
2456 | 0 | leftsib, target, leafblkno, scanblkno, |
2457 | 0 | targetlevel, RelationGetRelationName(rel)))); |
2458 | | |
2459 | | /* Must release all pins and locks on failure exit */ |
2460 | 0 | ReleaseBuffer(buf); |
2461 | 0 | if (target != leafblkno) |
2462 | 0 | _bt_relbuf(rel, leafbuf); |
2463 | |
|
2464 | 0 | return false; |
2465 | 0 | } |
2466 | | |
2467 | 0 | CHECK_FOR_INTERRUPTS(); |
2468 | | |
2469 | | /* step right one page */ |
2470 | 0 | lbuf = _bt_getbuf(rel, leftsib, BT_WRITE); |
2471 | 0 | page = BufferGetPage(lbuf); |
2472 | 0 | opaque = BTPageGetOpaque(page); |
2473 | 0 | } |
2474 | 0 | } |
2475 | 0 | else |
2476 | 0 | lbuf = InvalidBuffer; |
2477 | | |
2478 | | /* Next write-lock the target page itself */ |
2479 | 0 | _bt_lockbuf(rel, buf, BT_WRITE); |
2480 | 0 | page = BufferGetPage(buf); |
2481 | 0 | opaque = BTPageGetOpaque(page); |
2482 | | |
2483 | | /* |
2484 | | * Check page is still empty etc, else abandon deletion. This is just for |
2485 | | * paranoia's sake; a half-dead page cannot resurrect because there can be |
2486 | | * only one vacuum process running at a time. |
2487 | | */ |
2488 | 0 | if (P_RIGHTMOST(opaque) || P_ISROOT(opaque) || P_ISDELETED(opaque)) |
2489 | 0 | elog(ERROR, "target page changed status unexpectedly in block %u of index \"%s\"", |
2490 | 0 | target, RelationGetRelationName(rel)); |
2491 | | |
2492 | 0 | if (opaque->btpo_prev != leftsib) |
2493 | 0 | ereport(ERROR, |
2494 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
2495 | 0 | errmsg_internal("target page left link unexpectedly changed from %u to %u in block %u of index \"%s\"", |
2496 | 0 | leftsib, opaque->btpo_prev, target, |
2497 | 0 | RelationGetRelationName(rel)))); |
2498 | | |
2499 | 0 | if (target == leafblkno) |
2500 | 0 | { |
2501 | 0 | if (P_FIRSTDATAKEY(opaque) <= PageGetMaxOffsetNumber(page) || |
2502 | 0 | !P_ISLEAF(opaque) || !P_ISHALFDEAD(opaque)) |
2503 | 0 | elog(ERROR, "target leaf page changed status unexpectedly in block %u of index \"%s\"", |
2504 | 0 | target, RelationGetRelationName(rel)); |
2505 | | |
2506 | | /* Leaf page is also target page: don't set leaftopparent */ |
2507 | 0 | leaftopparent = InvalidBlockNumber; |
2508 | 0 | } |
2509 | 0 | else |
2510 | 0 | { |
2511 | 0 | IndexTuple finaldataitem; |
2512 | |
|
2513 | 0 | if (P_FIRSTDATAKEY(opaque) != PageGetMaxOffsetNumber(page) || |
2514 | 0 | P_ISLEAF(opaque)) |
2515 | 0 | elog(ERROR, "target internal page on level %u changed status unexpectedly in block %u of index \"%s\"", |
2516 | 0 | targetlevel, target, RelationGetRelationName(rel)); |
2517 | | |
2518 | | /* Target is internal: set leaftopparent for next call here... */ |
2519 | 0 | itemid = PageGetItemId(page, P_FIRSTDATAKEY(opaque)); |
2520 | 0 | finaldataitem = (IndexTuple) PageGetItem(page, itemid); |
2521 | 0 | leaftopparent = BTreeTupleGetDownLink(finaldataitem); |
2522 | | /* ...except when it would be a redundant pointer-to-self */ |
2523 | 0 | if (leaftopparent == leafblkno) |
2524 | 0 | leaftopparent = InvalidBlockNumber; |
2525 | 0 | } |
2526 | | |
2527 | | /* No leaftopparent for level 0 (leaf page) or level 1 target */ |
2528 | 0 | Assert(!BlockNumberIsValid(leaftopparent) || targetlevel > 1); |
2529 | | |
2530 | | /* |
2531 | | * And next write-lock the (current) right sibling. |
2532 | | */ |
2533 | 0 | rightsib = opaque->btpo_next; |
2534 | 0 | rbuf = _bt_getbuf(rel, rightsib, BT_WRITE); |
2535 | 0 | page = BufferGetPage(rbuf); |
2536 | 0 | opaque = BTPageGetOpaque(page); |
2537 | | |
2538 | | /* |
2539 | | * Validate target's right sibling page. Its left link must point back to |
2540 | | * the target page. |
2541 | | */ |
2542 | 0 | if (opaque->btpo_prev != target) |
2543 | 0 | { |
2544 | | /* |
2545 | | * This is known to fail in the field; sibling link corruption is |
2546 | | * relatively common. Press on with vacuuming rather than just |
2547 | | * throwing an ERROR (same approach used for left-sibling's-right-link |
2548 | | * validation check a moment ago). |
2549 | | */ |
2550 | 0 | ereport(LOG, |
2551 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
2552 | 0 | errmsg_internal("right sibling's left-link doesn't match: " |
2553 | 0 | "right sibling %u of target %u with leafblkno %u " |
2554 | 0 | "and scanblkno %u spuriously links to non-target %u " |
2555 | 0 | "on level %u of index \"%s\"", |
2556 | 0 | rightsib, target, leafblkno, |
2557 | 0 | scanblkno, opaque->btpo_prev, |
2558 | 0 | targetlevel, RelationGetRelationName(rel)))); |
2559 | | |
2560 | | /* Must release all pins and locks on failure exit */ |
2561 | 0 | if (BufferIsValid(lbuf)) |
2562 | 0 | _bt_relbuf(rel, lbuf); |
2563 | 0 | _bt_relbuf(rel, rbuf); |
2564 | 0 | _bt_relbuf(rel, buf); |
2565 | 0 | if (target != leafblkno) |
2566 | 0 | _bt_relbuf(rel, leafbuf); |
2567 | |
|
2568 | 0 | return false; |
2569 | 0 | } |
2570 | | |
2571 | 0 | rightsib_is_rightmost = P_RIGHTMOST(opaque); |
2572 | 0 | *rightsib_empty = (P_FIRSTDATAKEY(opaque) > PageGetMaxOffsetNumber(page)); |
2573 | | |
2574 | | /* |
2575 | | * If we are deleting the next-to-last page on the target's level, then |
2576 | | * the rightsib is a candidate to become the new fast root. (In theory, it |
2577 | | * might be possible to push the fast root even further down, but the odds |
2578 | | * of doing so are slim, and the locking considerations daunting.) |
2579 | | * |
2580 | | * We can safely acquire a lock on the metapage here --- see comments for |
2581 | | * _bt_newlevel(). |
2582 | | */ |
2583 | 0 | if (leftsib == P_NONE && rightsib_is_rightmost) |
2584 | 0 | { |
2585 | 0 | page = BufferGetPage(rbuf); |
2586 | 0 | opaque = BTPageGetOpaque(page); |
2587 | 0 | if (P_RIGHTMOST(opaque)) |
2588 | 0 | { |
2589 | | /* rightsib will be the only one left on the level */ |
2590 | 0 | metabuf = _bt_getbuf(rel, BTREE_METAPAGE, BT_WRITE); |
2591 | 0 | metapg = BufferGetPage(metabuf); |
2592 | 0 | metad = BTPageGetMeta(metapg); |
2593 | | |
2594 | | /* |
2595 | | * The expected case here is btm_fastlevel == targetlevel+1; if |
2596 | | * the fastlevel is <= targetlevel, something is wrong, and we |
2597 | | * choose to overwrite it to fix it. |
2598 | | */ |
2599 | 0 | if (metad->btm_fastlevel > targetlevel + 1) |
2600 | 0 | { |
2601 | | /* no update wanted */ |
2602 | 0 | _bt_relbuf(rel, metabuf); |
2603 | 0 | metabuf = InvalidBuffer; |
2604 | 0 | } |
2605 | 0 | } |
2606 | 0 | } |
2607 | | |
2608 | | /* |
2609 | | * Here we begin doing the deletion. |
2610 | | */ |
2611 | | |
2612 | | /* No ereport(ERROR) until changes are logged */ |
2613 | 0 | START_CRIT_SECTION(); |
2614 | | |
2615 | | /* |
2616 | | * Update siblings' side-links. Note the target page's side-links will |
2617 | | * continue to point to the siblings. Asserts here are just rechecking |
2618 | | * things we already verified above. |
2619 | | */ |
2620 | 0 | if (BufferIsValid(lbuf)) |
2621 | 0 | { |
2622 | 0 | page = BufferGetPage(lbuf); |
2623 | 0 | opaque = BTPageGetOpaque(page); |
2624 | 0 | Assert(opaque->btpo_next == target); |
2625 | 0 | opaque->btpo_next = rightsib; |
2626 | 0 | } |
2627 | 0 | page = BufferGetPage(rbuf); |
2628 | 0 | opaque = BTPageGetOpaque(page); |
2629 | 0 | Assert(opaque->btpo_prev == target); |
2630 | 0 | opaque->btpo_prev = leftsib; |
2631 | | |
2632 | | /* |
2633 | | * If we deleted a parent of the targeted leaf page, instead of the leaf |
2634 | | * itself, update the leaf to point to the next remaining child in the |
2635 | | * subtree. |
2636 | | * |
2637 | | * Note: We rely on the fact that a buffer pin on the leaf page has been |
2638 | | * held since leafhikey was initialized. This is safe, though only |
2639 | | * because the page was already half-dead at that point. The leaf page |
2640 | | * cannot have been modified by any other backend during the period when |
2641 | | * no lock was held. |
2642 | | */ |
2643 | 0 | if (target != leafblkno) |
2644 | 0 | BTreeTupleSetTopParent(leafhikey, leaftopparent); |
2645 | | |
2646 | | /* |
2647 | | * Mark the page itself deleted. It can be recycled when all current |
2648 | | * transactions are gone. Storing GetTopTransactionId() would work, but |
2649 | | * we're in VACUUM and would not otherwise have an XID. Having already |
2650 | | * updated links to the target, ReadNextFullTransactionId() suffices as an |
2651 | | * upper bound. Any scan having retained a now-stale link is advertising |
2652 | | * in its PGPROC an xmin less than or equal to the value we read here. It |
2653 | | * will continue to do so, holding back the xmin horizon, for the duration |
2654 | | * of that scan. |
2655 | | */ |
2656 | 0 | page = BufferGetPage(buf); |
2657 | 0 | opaque = BTPageGetOpaque(page); |
2658 | 0 | Assert(P_ISHALFDEAD(opaque) || !P_ISLEAF(opaque)); |
2659 | | |
2660 | | /* |
2661 | | * Store upper bound XID that's used to determine when deleted page is no |
2662 | | * longer needed as a tombstone |
2663 | | */ |
2664 | 0 | safexid = ReadNextFullTransactionId(); |
2665 | 0 | BTPageSetDeleted(page, safexid); |
2666 | 0 | opaque->btpo_cycleid = 0; |
2667 | | |
2668 | | /* And update the metapage, if needed */ |
2669 | 0 | if (BufferIsValid(metabuf)) |
2670 | 0 | { |
2671 | | /* upgrade metapage if needed */ |
2672 | 0 | if (metad->btm_version < BTREE_NOVAC_VERSION) |
2673 | 0 | _bt_upgrademetapage(metapg); |
2674 | 0 | metad->btm_fastroot = rightsib; |
2675 | 0 | metad->btm_fastlevel = targetlevel; |
2676 | 0 | MarkBufferDirty(metabuf); |
2677 | 0 | } |
2678 | | |
2679 | | /* Must mark buffers dirty before XLogInsert */ |
2680 | 0 | MarkBufferDirty(rbuf); |
2681 | 0 | MarkBufferDirty(buf); |
2682 | 0 | if (BufferIsValid(lbuf)) |
2683 | 0 | MarkBufferDirty(lbuf); |
2684 | 0 | if (target != leafblkno) |
2685 | 0 | MarkBufferDirty(leafbuf); |
2686 | | |
2687 | | /* XLOG stuff */ |
2688 | 0 | if (RelationNeedsWAL(rel)) |
2689 | 0 | { |
2690 | 0 | xl_btree_unlink_page xlrec; |
2691 | 0 | xl_btree_metadata xlmeta; |
2692 | 0 | uint8 xlinfo; |
2693 | |
|
2694 | 0 | XLogBeginInsert(); |
2695 | |
|
2696 | 0 | XLogRegisterBuffer(0, buf, REGBUF_WILL_INIT); |
2697 | 0 | if (BufferIsValid(lbuf)) |
2698 | 0 | XLogRegisterBuffer(1, lbuf, REGBUF_STANDARD); |
2699 | 0 | XLogRegisterBuffer(2, rbuf, REGBUF_STANDARD); |
2700 | 0 | if (target != leafblkno) |
2701 | 0 | XLogRegisterBuffer(3, leafbuf, REGBUF_WILL_INIT); |
2702 | | |
2703 | | /* information stored on the target/to-be-unlinked block */ |
2704 | 0 | xlrec.leftsib = leftsib; |
2705 | 0 | xlrec.rightsib = rightsib; |
2706 | 0 | xlrec.level = targetlevel; |
2707 | 0 | xlrec.safexid = safexid; |
2708 | | |
2709 | | /* information needed to recreate the leaf block (if not the target) */ |
2710 | 0 | xlrec.leafleftsib = leafleftsib; |
2711 | 0 | xlrec.leafrightsib = leafrightsib; |
2712 | 0 | xlrec.leaftopparent = leaftopparent; |
2713 | |
|
2714 | 0 | XLogRegisterData(&xlrec, SizeOfBtreeUnlinkPage); |
2715 | |
|
2716 | 0 | if (BufferIsValid(metabuf)) |
2717 | 0 | { |
2718 | 0 | XLogRegisterBuffer(4, metabuf, REGBUF_WILL_INIT | REGBUF_STANDARD); |
2719 | |
|
2720 | 0 | Assert(metad->btm_version >= BTREE_NOVAC_VERSION); |
2721 | 0 | xlmeta.version = metad->btm_version; |
2722 | 0 | xlmeta.root = metad->btm_root; |
2723 | 0 | xlmeta.level = metad->btm_level; |
2724 | 0 | xlmeta.fastroot = metad->btm_fastroot; |
2725 | 0 | xlmeta.fastlevel = metad->btm_fastlevel; |
2726 | 0 | xlmeta.last_cleanup_num_delpages = metad->btm_last_cleanup_num_delpages; |
2727 | 0 | xlmeta.allequalimage = metad->btm_allequalimage; |
2728 | |
|
2729 | 0 | XLogRegisterBufData(4, &xlmeta, sizeof(xl_btree_metadata)); |
2730 | 0 | xlinfo = XLOG_BTREE_UNLINK_PAGE_META; |
2731 | 0 | } |
2732 | 0 | else |
2733 | 0 | xlinfo = XLOG_BTREE_UNLINK_PAGE; |
2734 | |
|
2735 | 0 | recptr = XLogInsert(RM_BTREE_ID, xlinfo); |
2736 | 0 | } |
2737 | 0 | else |
2738 | 0 | recptr = XLogGetFakeLSN(rel); |
2739 | |
|
2740 | 0 | if (BufferIsValid(metabuf)) |
2741 | 0 | PageSetLSN(metapg, recptr); |
2742 | 0 | page = BufferGetPage(rbuf); |
2743 | 0 | PageSetLSN(page, recptr); |
2744 | 0 | page = BufferGetPage(buf); |
2745 | 0 | PageSetLSN(page, recptr); |
2746 | 0 | if (BufferIsValid(lbuf)) |
2747 | 0 | { |
2748 | 0 | page = BufferGetPage(lbuf); |
2749 | 0 | PageSetLSN(page, recptr); |
2750 | 0 | } |
2751 | 0 | if (target != leafblkno) |
2752 | 0 | { |
2753 | 0 | page = BufferGetPage(leafbuf); |
2754 | 0 | PageSetLSN(page, recptr); |
2755 | 0 | } |
2756 | |
|
2757 | 0 | END_CRIT_SECTION(); |
2758 | | |
2759 | | /* release metapage */ |
2760 | 0 | if (BufferIsValid(metabuf)) |
2761 | 0 | _bt_relbuf(rel, metabuf); |
2762 | | |
2763 | | /* release siblings */ |
2764 | 0 | if (BufferIsValid(lbuf)) |
2765 | 0 | _bt_relbuf(rel, lbuf); |
2766 | 0 | _bt_relbuf(rel, rbuf); |
2767 | | |
2768 | | /* If the target is not leafbuf, we're done with it now -- release it */ |
2769 | 0 | if (target != leafblkno) |
2770 | 0 | _bt_relbuf(rel, buf); |
2771 | | |
2772 | | /* |
2773 | | * Maintain pages_newly_deleted, which is simply the number of pages |
2774 | | * deleted by the ongoing VACUUM operation. |
2775 | | * |
2776 | | * Maintain pages_deleted in a way that takes into account how |
2777 | | * btvacuumpage() will count deleted pages that have yet to become |
2778 | | * scanblkno -- only count page when it's not going to get that treatment |
2779 | | * later on. |
2780 | | */ |
2781 | 0 | stats->pages_newly_deleted++; |
2782 | 0 | if (target <= scanblkno) |
2783 | 0 | stats->pages_deleted++; |
2784 | | |
2785 | | /* |
2786 | | * Remember information about the target page (now a newly deleted page) |
2787 | | * in dedicated vstate space for later. The page will be considered as a |
2788 | | * candidate to place in the FSM at the end of the current btvacuumscan() |
2789 | | * call. |
2790 | | */ |
2791 | 0 | _bt_pendingfsm_add(vstate, target, safexid); |
2792 | | |
2793 | | /* Success - hold on to lock on leafbuf (might also have been target) */ |
2794 | 0 | return true; |
2795 | 0 | } |
2796 | | |
2797 | | /* |
2798 | | * Establish how tall the to-be-deleted subtree will be during the first stage |
2799 | | * of page deletion. |
2800 | | * |
2801 | | * Caller's child argument is the block number of the page caller wants to |
2802 | | * delete (this is leafbuf's block number, except when we're called |
2803 | | * recursively). stack is a search stack leading to it. Note that we will |
2804 | | * update the stack entry(s) to reflect current downlink positions --- this is |
2805 | | * similar to the corresponding point in page split handling. |
2806 | | * |
2807 | | * If "first stage" caller cannot go ahead with deleting _any_ pages, returns |
2808 | | * false. Returns true on success, in which case caller can use certain |
2809 | | * details established here to perform the first stage of deletion. This |
2810 | | * function is the last point at which page deletion may be deemed unsafe |
2811 | | * (barring index corruption, or unexpected concurrent page deletions). |
2812 | | * |
2813 | | * We write lock the parent of the root of the to-be-deleted subtree for |
2814 | | * caller on success (i.e. we leave our lock on the *subtreeparent buffer for |
2815 | | * caller). Caller will have to remove a downlink from *subtreeparent. We |
2816 | | * also set a *subtreeparent offset number in *poffset, to indicate the |
2817 | | * location of the pivot tuple that contains the relevant downlink. |
2818 | | * |
2819 | | * The root of the to-be-deleted subtree is called the "top parent". Note |
2820 | | * that the leafbuf page is often the final "top parent" page (you can think |
2821 | | * of the leafbuf page as a degenerate single page subtree when that happens). |
2822 | | * Caller should initialize *topparent to the target leafbuf page block number |
2823 | | * (while *topparentrightsib should be set to leafbuf's right sibling block |
2824 | | * number). We will update *topparent (and *topparentrightsib) for caller |
2825 | | * here, though only when it turns out that caller will delete at least one |
2826 | | * internal page (i.e. only when caller needs to store a valid link to the top |
2827 | | * parent block in the leafbuf page using BTreeTupleSetTopParent()). |
2828 | | */ |
2829 | | static bool |
2830 | | _bt_lock_subtree_parent(Relation rel, Relation heaprel, BlockNumber child, |
2831 | | BTStack stack, Buffer *subtreeparent, |
2832 | | OffsetNumber *poffset, BlockNumber *topparent, |
2833 | | BlockNumber *topparentrightsib) |
2834 | 0 | { |
2835 | 0 | BlockNumber parent, |
2836 | 0 | leftsibparent; |
2837 | 0 | OffsetNumber parentoffset, |
2838 | 0 | maxoff; |
2839 | 0 | Buffer pbuf; |
2840 | 0 | Page page; |
2841 | 0 | BTPageOpaque opaque; |
2842 | | |
2843 | | /* |
2844 | | * Locate the pivot tuple whose downlink points to "child". Write lock |
2845 | | * the parent page itself. |
2846 | | */ |
2847 | 0 | pbuf = _bt_getstackbuf(rel, heaprel, stack, child); |
2848 | 0 | if (pbuf == InvalidBuffer) |
2849 | 0 | { |
2850 | | /* |
2851 | | * Failed to "re-find" a pivot tuple whose downlink matched our child |
2852 | | * block number on the parent level -- the index must be corrupt. |
2853 | | * Don't even try to delete the leafbuf subtree. Just report the |
2854 | | * issue and press on with vacuuming the index. |
2855 | | * |
2856 | | * Note: _bt_getstackbuf() recovers from concurrent page splits that |
2857 | | * take place on the parent level. Its approach is a near-exhaustive |
2858 | | * linear search. This also gives it a surprisingly good chance of |
2859 | | * recovering in the event of a buggy or inconsistent opclass. But we |
2860 | | * don't rely on that here. |
2861 | | */ |
2862 | 0 | ereport(LOG, |
2863 | 0 | (errcode(ERRCODE_INDEX_CORRUPTED), |
2864 | 0 | errmsg_internal("failed to re-find parent key in index \"%s\" for deletion target page %u", |
2865 | 0 | RelationGetRelationName(rel), child))); |
2866 | 0 | Assert(false); |
2867 | 0 | return false; |
2868 | 0 | } |
2869 | | |
2870 | 0 | parent = stack->bts_blkno; |
2871 | 0 | parentoffset = stack->bts_offset; |
2872 | |
|
2873 | 0 | page = BufferGetPage(pbuf); |
2874 | 0 | opaque = BTPageGetOpaque(page); |
2875 | 0 | maxoff = PageGetMaxOffsetNumber(page); |
2876 | 0 | leftsibparent = opaque->btpo_prev; |
2877 | | |
2878 | | /* |
2879 | | * _bt_getstackbuf() completes page splits on returned parent buffer when |
2880 | | * required. |
2881 | | * |
2882 | | * In general it's a bad idea for VACUUM to use up more disk space, which |
2883 | | * is why page deletion does not finish incomplete page splits most of the |
2884 | | * time. We allow this limited exception because the risk is much lower, |
2885 | | * and the potential downside of not proceeding is much higher: A single |
2886 | | * internal page with the INCOMPLETE_SPLIT flag set might otherwise |
2887 | | * prevent us from deleting hundreds of empty leaf pages from one level |
2888 | | * down. |
2889 | | */ |
2890 | 0 | Assert(!P_INCOMPLETE_SPLIT(opaque)); |
2891 | |
|
2892 | 0 | if (parentoffset < maxoff) |
2893 | 0 | { |
2894 | | /* |
2895 | | * Child is not the rightmost child in parent, so it's safe to delete |
2896 | | * the subtree whose root/topparent is child page |
2897 | | */ |
2898 | 0 | *subtreeparent = pbuf; |
2899 | 0 | *poffset = parentoffset; |
2900 | 0 | return true; |
2901 | 0 | } |
2902 | | |
2903 | | /* |
2904 | | * Child is the rightmost child of parent. |
2905 | | * |
2906 | | * Since it's the rightmost child of parent, deleting the child (or |
2907 | | * deleting the subtree whose root/topparent is the child page) is only |
2908 | | * safe when it's also possible to delete the parent. |
2909 | | */ |
2910 | 0 | Assert(parentoffset == maxoff); |
2911 | 0 | if (parentoffset != P_FIRSTDATAKEY(opaque) || P_RIGHTMOST(opaque)) |
2912 | 0 | { |
2913 | | /* |
2914 | | * Child isn't parent's only child, or parent is rightmost on its |
2915 | | * entire level. Definitely cannot delete any pages. |
2916 | | */ |
2917 | 0 | _bt_relbuf(rel, pbuf); |
2918 | 0 | return false; |
2919 | 0 | } |
2920 | | |
2921 | | /* |
2922 | | * Now make sure that the parent deletion is itself safe by examining the |
2923 | | * child's grandparent page. Recurse, passing the parent page as the |
2924 | | * child page (child's grandparent is the parent on the next level up). If |
2925 | | * parent deletion is unsafe, then child deletion must also be unsafe (in |
2926 | | * which case caller cannot delete any pages at all). |
2927 | | */ |
2928 | 0 | *topparent = parent; |
2929 | 0 | *topparentrightsib = opaque->btpo_next; |
2930 | | |
2931 | | /* |
2932 | | * Release lock on parent before recursing. |
2933 | | * |
2934 | | * It's OK to release page locks on parent before recursive call locks |
2935 | | * grandparent. An internal page can only acquire an entry if the child |
2936 | | * is split, but that cannot happen as long as we still hold a lock on the |
2937 | | * leafbuf page. |
2938 | | */ |
2939 | 0 | _bt_relbuf(rel, pbuf); |
2940 | | |
2941 | | /* |
2942 | | * Before recursing, check that the left sibling of parent (if any) is not |
2943 | | * marked with INCOMPLETE_SPLIT flag first (must do so after we drop the |
2944 | | * parent lock). |
2945 | | * |
2946 | | * Note: We deliberately avoid completing incomplete splits here. |
2947 | | */ |
2948 | 0 | if (_bt_leftsib_splitflag(rel, leftsibparent, parent)) |
2949 | 0 | return false; |
2950 | | |
2951 | | /* Recurse to examine child page's grandparent page */ |
2952 | 0 | return _bt_lock_subtree_parent(rel, heaprel, parent, stack->bts_parent, |
2953 | 0 | subtreeparent, poffset, |
2954 | 0 | topparent, topparentrightsib); |
2955 | 0 | } |
2956 | | |
2957 | | /* |
2958 | | * Initialize local memory state used by VACUUM for _bt_pendingfsm_finalize |
2959 | | * optimization. |
2960 | | * |
2961 | | * Called at the start of a btvacuumscan(). Caller's cleanuponly argument |
2962 | | * indicates if ongoing VACUUM has not (and will not) call btbulkdelete(). |
2963 | | * |
2964 | | * We expect to allocate memory inside VACUUM's top-level memory context here. |
2965 | | * The working buffer is subject to a limit based on work_mem. Our strategy |
2966 | | * when the array can no longer grow within the bounds of that limit is to |
2967 | | * stop saving additional newly deleted pages, while proceeding as usual with |
2968 | | * the pages that we can fit. |
2969 | | */ |
2970 | | void |
2971 | | _bt_pendingfsm_init(Relation rel, BTVacState *vstate, bool cleanuponly) |
2972 | 0 | { |
2973 | 0 | Size maxbufsize; |
2974 | | |
2975 | | /* |
2976 | | * Don't bother with optimization in cleanup-only case -- we don't expect |
2977 | | * any newly deleted pages. Besides, cleanup-only calls to btvacuumscan() |
2978 | | * can only take place because this optimization didn't work out during |
2979 | | * the last VACUUM. |
2980 | | */ |
2981 | 0 | if (cleanuponly) |
2982 | 0 | return; |
2983 | | |
2984 | | /* |
2985 | | * Cap maximum size of array so that we always respect work_mem. Avoid |
2986 | | * int overflow here. |
2987 | | */ |
2988 | 0 | vstate->bufsize = 256; |
2989 | 0 | maxbufsize = (work_mem * (Size) 1024) / sizeof(BTPendingFSM); |
2990 | 0 | maxbufsize = Min(maxbufsize, MaxAllocSize / sizeof(BTPendingFSM)); |
2991 | | /* BTVacState.maxbufsize has type int */ |
2992 | 0 | maxbufsize = Min(maxbufsize, INT_MAX); |
2993 | | /* Stay sane with small work_mem */ |
2994 | 0 | maxbufsize = Max(maxbufsize, vstate->bufsize); |
2995 | 0 | vstate->maxbufsize = (int) maxbufsize; |
2996 | | |
2997 | | /* Allocate buffer, indicate that there are currently 0 pending pages */ |
2998 | 0 | vstate->pendingpages = palloc_array(BTPendingFSM, vstate->bufsize); |
2999 | 0 | vstate->npendingpages = 0; |
3000 | 0 | } |
3001 | | |
3002 | | /* |
3003 | | * Place any newly deleted pages (i.e. pages that _bt_pagedel() deleted during |
3004 | | * the ongoing VACUUM operation) into the free space map -- though only when |
3005 | | * it is actually safe to do so by now. |
3006 | | * |
3007 | | * Called at the end of a btvacuumscan(), just before free space map vacuuming |
3008 | | * takes place. |
3009 | | * |
3010 | | * Frees memory allocated by _bt_pendingfsm_init(), if any. |
3011 | | */ |
3012 | | void |
3013 | | _bt_pendingfsm_finalize(Relation rel, BTVacState *vstate) |
3014 | 0 | { |
3015 | 0 | IndexBulkDeleteResult *stats = vstate->stats; |
3016 | 0 | Relation heaprel = vstate->info->heaprel; |
3017 | |
|
3018 | 0 | Assert(stats->pages_newly_deleted >= vstate->npendingpages); |
3019 | 0 | Assert(heaprel != NULL); |
3020 | |
|
3021 | 0 | if (vstate->npendingpages == 0) |
3022 | 0 | { |
3023 | | /* Just free memory when nothing to do */ |
3024 | 0 | if (vstate->pendingpages) |
3025 | 0 | pfree(vstate->pendingpages); |
3026 | |
|
3027 | 0 | return; |
3028 | 0 | } |
3029 | | |
3030 | | #ifdef DEBUG_BTREE_PENDING_FSM |
3031 | | |
3032 | | /* |
3033 | | * Debugging aid: Sleep for 5 seconds to greatly increase the chances of |
3034 | | * placing pending pages in the FSM. Note that the optimization will |
3035 | | * never be effective without some other backend concurrently consuming an |
3036 | | * XID. |
3037 | | */ |
3038 | | pg_usleep(5000000L); |
3039 | | #endif |
3040 | | |
3041 | | /* |
3042 | | * Recompute VACUUM XID boundaries. |
3043 | | * |
3044 | | * We don't actually care about the oldest non-removable XID. Computing |
3045 | | * the oldest such XID has a useful side-effect that we rely on: it |
3046 | | * forcibly updates the XID horizon state for this backend. This step is |
3047 | | * essential; GlobalVisCheckRemovableFullXid() will not reliably recognize |
3048 | | * that it is now safe to recycle newly deleted pages without this step. |
3049 | | */ |
3050 | 0 | GetOldestNonRemovableTransactionId(heaprel); |
3051 | |
|
3052 | 0 | for (unsigned int i = 0; i < vstate->npendingpages; i++) |
3053 | 0 | { |
3054 | 0 | BlockNumber target = vstate->pendingpages[i].target; |
3055 | 0 | FullTransactionId safexid = vstate->pendingpages[i].safexid; |
3056 | | |
3057 | | /* |
3058 | | * Do the equivalent of checking BTPageIsRecyclable(), but without |
3059 | | * accessing the page again a second time. |
3060 | | * |
3061 | | * Give up on finding the first non-recyclable page -- all later pages |
3062 | | * must be non-recyclable too, since _bt_pendingfsm_add() adds pages |
3063 | | * to the array in safexid order. |
3064 | | */ |
3065 | 0 | if (!GlobalVisCheckRemovableFullXid(heaprel, safexid)) |
3066 | 0 | break; |
3067 | | |
3068 | 0 | RecordFreeIndexPage(rel, target); |
3069 | 0 | stats->pages_free++; |
3070 | 0 | } |
3071 | |
|
3072 | 0 | pfree(vstate->pendingpages); |
3073 | 0 | } |
3074 | | |
3075 | | /* |
3076 | | * Maintain array of pages that were deleted during current btvacuumscan() |
3077 | | * call, for use in _bt_pendingfsm_finalize() |
3078 | | */ |
3079 | | static void |
3080 | | _bt_pendingfsm_add(BTVacState *vstate, |
3081 | | BlockNumber target, |
3082 | | FullTransactionId safexid) |
3083 | 0 | { |
3084 | 0 | Assert(vstate->npendingpages <= vstate->bufsize); |
3085 | 0 | Assert(vstate->bufsize <= vstate->maxbufsize); |
3086 | |
|
3087 | | #ifdef USE_ASSERT_CHECKING |
3088 | | |
3089 | | /* |
3090 | | * Verify an assumption made by _bt_pendingfsm_finalize(): pages from the |
3091 | | * array will always be in safexid order (since that is the order that we |
3092 | | * save them in here) |
3093 | | */ |
3094 | | if (vstate->npendingpages > 0) |
3095 | | { |
3096 | | FullTransactionId lastsafexid = |
3097 | | vstate->pendingpages[vstate->npendingpages - 1].safexid; |
3098 | | |
3099 | | Assert(FullTransactionIdFollowsOrEquals(safexid, lastsafexid)); |
3100 | | } |
3101 | | #endif |
3102 | | |
3103 | | /* |
3104 | | * If temp buffer reaches maxbufsize/work_mem capacity then we discard |
3105 | | * information about this page. |
3106 | | * |
3107 | | * Note that this also covers the case where we opted to not use the |
3108 | | * optimization in _bt_pendingfsm_init(). |
3109 | | */ |
3110 | 0 | if (vstate->npendingpages == vstate->maxbufsize) |
3111 | 0 | return; |
3112 | | |
3113 | | /* Consider enlarging buffer */ |
3114 | 0 | if (vstate->npendingpages == vstate->bufsize) |
3115 | 0 | { |
3116 | 0 | int newbufsize = vstate->bufsize * 2; |
3117 | | |
3118 | | /* Respect work_mem */ |
3119 | 0 | if (newbufsize > vstate->maxbufsize) |
3120 | 0 | newbufsize = vstate->maxbufsize; |
3121 | |
|
3122 | 0 | vstate->bufsize = newbufsize; |
3123 | 0 | vstate->pendingpages = |
3124 | 0 | repalloc(vstate->pendingpages, |
3125 | 0 | sizeof(BTPendingFSM) * vstate->bufsize); |
3126 | 0 | } |
3127 | | |
3128 | | /* Save metadata for newly deleted page */ |
3129 | 0 | vstate->pendingpages[vstate->npendingpages].target = target; |
3130 | 0 | vstate->pendingpages[vstate->npendingpages].safexid = safexid; |
3131 | 0 | vstate->npendingpages++; |
3132 | 0 | } |