On Tue, 9 Mar 2021 at 22:35, Tom Lane <t...@sss.pgh.pa.us> wrote: > > Matthias van de Meent <boekewurm+postg...@gmail.com> writes: > > The only two existing mechanisms that I could find (in the access/heap > > directory) that possibly could fail on shrunken line pointer arrays; > > being xlog recovery (I do not have enough knowledge on recovery to > > determine if that may touch pages that have shrunken line pointer > > arrays, or if those situations won't exist due to never using dirtied > > pages in recovery) and backwards table scans on non-MVCC snapshots > > (which would be fixed in the attached patch). > > I think you're not visualizing the problem properly. > > The case I was concerned about back when is that there are various bits of > code that may visit a page with a predetermined TID in mind to look at. > An index lookup is an obvious example, and another one is chasing an > update chain's t_ctid link. You might argue that if the tuple was dead > enough to be removed, there should be no such in-flight references to > worry about, but I think such an assumption is unsafe. There is not, for > example, any interlock that ensures that a process that has obtained a TID > from an index will have completed its heap visit before a VACUUM that > subsequently removed that index entry also removes the heap tuple.
I am aware of this problem. I will admit that I did not detected all potentially problematic accesses, so I'll show you my work. > So, to accept a patch that shortens the line pointer array, what we need > to do is verify that every such code path checks for an out-of-range > offset before trying to fetch the target line pointer. I believed > back in 2007 that there were, or once had been, code paths that omitted > such a range check, assuming that they could trust the TID they had > gotten from $wherever to point at an extant line pointer array entry. > Maybe things are all good now, but I think you should run around and > examine every place that checks for tuple deadness to see if the offset > it used is known to be within the current page bounds. In my search for problematic accesses, I make the following assumptions: * PageRepairFragmentation as found in bufpage is only applicable to heap pages; this function is not applied to other pages in core postgres. So, any problems that occur are with due to access with an OffsetNumber > PageGetMaxOffsetNumber. * Items [on heap pages] are only extracted after using PageGetItemId for that item on the page, whilst holding a lock. Under those assumptions, I ran "grep PageGetItemId" over the src directory. For all 227 results (as of 68b34b23) I checked if the page accessed (or item accessed thereafter) was a heap page or heap tuple. After analysis of the relevant references, I had the following results (attached full report, containing a line with the file & line number, and code line of the call, followed by a line containing the usage type): Count of usage type - usage type 4 - Not a call (comment) 7 - Callsite guarantees bounds 8 - Has assertion ItemIdIsNormal (asserts item is not removed; i.e. concurrent vacuum should not have been able to remove this item) 39 - Has bound checks 6 - Not a heap page (brin) 1 - Not a heap page (generic index) 24 - Not a heap page (gin) 21 - Not a heap page (gist) 14 - Not a heap page (hash) 60 - Not a heap page (nbtree) 1 - Not a heap page (sequence) 36 - Not a heap page (spgist) 2 - OffsetNumber is generated by PageAddItem 2 - problem case 1 (table scan) 1 - Problem case 2 (xlog) 1 - Problem case 3 (invalid redirect pointers) The 3 problem cases were classified based on the origin of the potentially invalid pointer. Problem case 1: table scan; heapam.c lines 678 and 811, in heapgettup The table scan maintains a state which contains a page-bound OffsetNumber, which it uses as a cursor whilst working through the pages of the relation. In forward scans, the bounds of the page are re-validated at the start of the 'while (linesleft > 0)' loop at 681, but for backwards scans this check is invalid, because it incorrectly assumes that the last OffsetNumber is guaranteed to still exist. For MVCC snapshots, this is true (the previously returned value must have been visible in its snapshot, therefore cannot have been vacuumed because the snapshot is still alive), but non-mvcc snapshots may have returned a dead tuple, which is now vacuumed and truncated away. The first occurrance of this issue is easily fixed with the changes as submitted in patch v2. The second problem case (on line 811) is for forward scans, where the line pointer array could have been truncated to 0 length. As the code uses a hardcoded offset of FirstOffsetNumber (=1), that might point into arbitrary data. The reading of this arbitrary data is saved by the 'while (linesleft > 0) check', because at that point linesleft will be PageGetMaxOffsetNumber, which would then equal 0. Problem case 2: xlog; heapam.c line 8796, in heap_xlog_freeze_page This is in the replay of transaction logs, in heap_xlog_freeze_page. As I am unaware whether or not pages to which these transaction logs are applied can contain changes from the xlog-generating instance, I flagged this as a potential problem. The application of the xlogs is probably safe (it assumes the existence of a HeapTupleHeader for that ItemId), but my limited knowledge put this on the 'potential problem' list. Please advise on this; I cannot find the right documentation Problem case 3: invalid redirect pointers; pruneheap.c line 949, in heap_get_root_tuples The heap pruning mechanism currently assumes that all redirects are valid. Currently, if a HOT root points to !ItemIdIsNormal, it breaks out of the loop, but doesn't actually fail. This code is already potentially problematic because it has no bounds or sanity checking for the nextoffnum, but with shrinking pd_linp it would also add the failure mode of HOT tuples pointing into what is now arbitrary data. This failure mode is now accompanied by an Assert, which fails when the redirect is to an invalid OffsetNumber. This is not enough to not exhibit arbitrary behaviour when accessing corrupted data with non-assert builds, but I think that that is fine; we already do not have a guaranteed behaviour for this failure mode. I have also searched the contrib modules using the same method; and all 18 usages seem to be validated correctly. With regards, Matthias van de Meent.
./backend/access/heap/heapam.c:678: lpp = PageGetItemId(dp, lineoff); + problem case 1 (table scan) ./backend/access/heap/heapam.c:811: lpp = PageGetItemId(dp, FirstOffsetNumber); + problem case 1 (table scan) ./backend/access/heap/heapam.c:8796: lp = PageGetItemId(page, xlrec_tp->offset); /* offsets are one-based */ + Problem case 2 (xlog) ./backend/access/heap/pruneheap.c:949: lp = PageGetItemId(page, nextoffnum); + Problem case 3 (invalid redirect pointers) ./backend/commands/sequence.c:1186: lp = PageGetItemId(page, FirstOffsetNumber); - Not a heap page (sequence) ./backend/access/brin/brin.c:283: ItemId lp = PageGetItemId(page, off); - Not a heap page (brin) ./backend/access/brin/brin_pageops.c:114: oldlp = PageGetItemId(oldpage, oldoff); - Not a heap page (brin) ./backend/access/brin/brin_pageops.c:541: lp = PageGetItemId(page, off); - Not a heap page (brin) ./backend/access/brin/brin_pageops.c:582: lp = PageGetItemId(page, off); - Not a heap page (brin) ./backend/access/brin/brin_revmap.c:296: lp = PageGetItemId(page, *off); - Not a heap page (brin) ./backend/access/brin/brin_revmap.c:394: lp = PageGetItemId(regPg, regOffset); - Not a heap page (brin) ./backend/access/gin/ginentrypage.c:240: return (IndexTuple) PageGetItem(page, PageGetItemId(page, maxoff)); - Not a heap page (gin) ./backend/access/gin/ginentrypage.c:311: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid)); - Not a heap page (gin) ./backend/access/gin/ginentrypage.c:336: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, high)); - Not a heap page (gin) ./backend/access/gin/ginentrypage.c:382: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, mid)); - Not a heap page (gin) ./backend/access/gin/ginentrypage.c:418: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, storedOff)); - Not a heap page (gin) ./backend/access/gin/ginentrypage.c:428: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i)); - Not a heap page (gin) ./backend/access/gin/ginentrypage.c:438: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i)); - Not a heap page (gin) ./backend/access/gin/ginentrypage.c:455: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber)); - Not a heap page (gin) ./backend/access/gin/ginentrypage.c:472: IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off)); - Not a heap page (gin) ./backend/access/gin/ginentrypage.c:505: IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off)); - Not a heap page (gin) ./backend/access/gin/ginentrypage.c:639: itup = (IndexTuple) PageGetItem(lpage, PageGetItemId(lpage, i)); - Not a heap page (gin) ./backend/access/gin/ginfast.c:721: IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, i)); - Not a heap page (gin) ./backend/access/gin/ginget.c:159: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off)); - Not a heap page (gin) ./backend/access/gin/ginget.c:274: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off)); - Not a heap page (gin) ./backend/access/gin/ginget.c:393: IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stackEntry->off)); - Not a heap page (gin) ./backend/access/gin/ginget.c:1493: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, pos->firstOffset)); - Not a heap page (gin) ./backend/access/gin/ginget.c:1502: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, pos->lastOffset)); - Not a heap page (gin) ./backend/access/gin/ginget.c:1553: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off)); - Not a heap page (gin) ./backend/access/gin/ginget.c:1670: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, StopMiddle)); - Not a heap page (gin) ./backend/access/gin/gininsert.c:201: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, stack->off)); - Not a heap page (gin) ./backend/access/gin/ginvacuum.c:469: IndexTuple itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i)); - Not a heap page (gin) ./backend/access/gin/ginvacuum.c:539: itup = (IndexTuple) PageGetItem(tmppage, PageGetItemId(tmppage, i)); - Not a heap page (gin) ./backend/access/gin/ginvacuum.c:629: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, FirstOffsetNumber)); - Not a heap page (gin) ./backend/access/gin/ginxlog.c:83: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offset)); - Not a heap page (gin) ./backend/access/gist/gistbuild.c:944: iid = PageGetItemId(page, childoffnum); - Not a heap page (gist) ./backend/access/gist/gistbuild.c:1072: ItemId iid = PageGetItemId(page, off); - Not a heap page (gist) ./backend/access/gist/gistbuild.c:1229: ItemId iid = PageGetItemId(page, *downlinkoffnum); - Not a heap page (gist) ./backend/access/gist/gistbuild.c:1248: ItemId iid = PageGetItemId(page, off); - Not a heap page (gist) ./backend/access/gist/gistbuild.c:1436: PageGetItemId(page, FirstOffsetNumber)); - Not a heap page (gist) ./backend/access/gist/gistbuild.c:1528: ItemId iid = PageGetItemId(page, off); - Not a heap page (gist) ./backend/access/gist/gist.c:749: iid = PageGetItemId(stack->page, downlinkoffnum); - Not a heap page (gist) ./backend/access/gist/gist.c:984: iid = PageGetItemId(page, i); - Not a heap page (gist) ./backend/access/gist/gist.c:1043: iid = PageGetItemId(parent->page, i); - Not a heap page (gist) ./backend/access/gist/gist.c:1119: PageGetItem(page, PageGetItemId(page, offset)); - Not a heap page (gist) ./backend/access/gist/gist.c:1150: iid = PageGetItemId(stack->parent->page, stack->downlinkoffnum); - Not a heap page (gist) ./backend/access/gist/gist.c:1660: ItemId itemId = PageGetItemId(page, offnum); - Not a heap page (gist) ./backend/access/gist/gistget.c:82: iid = PageGetItemId(page, offnum); - Not a heap page (gist) ./backend/access/gist/gistget.c:417: ItemId iid = PageGetItemId(page, i); - Not a heap page (gist) ./backend/access/gist/gistutil.c:69: IndexTuple itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, todelete)); - Not a heap page (gist) ./backend/access/gist/gistutil.c:104: itvec[i - FirstOffsetNumber] = (IndexTuple) PageGetItem(page, PageGetItemId(page, i)); - Not a heap page (gist) ./backend/access/gist/gistutil.c:439: IndexTuple itup = (IndexTuple) PageGetItem(p, PageGetItemId(p, i)); - Not a heap page (gist) ./backend/access/gist/gistvacuum.c:342: ItemId iid = PageGetItemId(page, off); - Not a heap page (gist) ./backend/access/gist/gistvacuum.c:415: ItemId iid = PageGetItemId(page, off); - Not a heap page (gist) ./backend/access/gist/gistvacuum.c:505: ItemId iid = PageGetItemId(page, off); - Not a heap page (gist) ./backend/access/gist/gistvacuum.c:628: iid = PageGetItemId(parentPage, downlink); - Not a heap page (gist) ./backend/access/hash/hash.c:732: PageGetItemId(page, offno)); - Not a heap page (hash) ./backend/access/hash/hashinsert.c:354: ItemId itemId = PageGetItemId(page, offnum); - Not a heap page (hash) ./backend/access/hash/hashovfl.c:889: if (ItemIdIsDead(PageGetItemId(rpage, roffnum))) - Not a heap page (hash) ./backend/access/hash/hashovfl.c:893: PageGetItemId(rpage, roffnum)); - Not a heap page (hash) ./backend/access/hash/hashpage.c:1128: if (ItemIdIsDead(PageGetItemId(opage, ooffnum))) - Not a heap page (hash) ./backend/access/hash/hashpage.c:1139: PageGetItemId(opage, ooffnum)); - Not a heap page (hash) ./backend/access/hash/hashpage.c:1406: PageGetItemId(npage, noffnum)); - Not a heap page (hash) ./backend/access/hash/hashsearch.c:626: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum)); - Not a heap page (hash) ./backend/access/hash/hashsearch.c:636: (ItemIdIsDead(PageGetItemId(page, offnum))))) - Not a heap page (hash) ./backend/access/hash/hashsearch.c:672: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum)); - Not a heap page (hash) ./backend/access/hash/hashsearch.c:682: (ItemIdIsDead(PageGetItemId(page, offnum))))) - Not a heap page (hash) ./backend/access/hash/hashutil.c:369: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off)); - Not a heap page (hash) ./backend/access/hash/hashutil.c:407: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off)); - Not a heap page (hash) ./backend/access/hash/hashutil.c:592: ItemId iid = PageGetItemId(page, offnum); - Not a heap page (hash) ./backend/access/index/genam.c:317: iitemid = PageGetItemId(ipage, itemnos[i]); - Not a heap page (generic index) ./backend/access/nbtree/nbtdedup.c:117: ItemId hitemid = PageGetItemId(page, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtdedup.c:130: ItemId itemid = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtdedup.c:357: ItemId itemid = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtdedup.c:647: ItemId itemid = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtdedup.c:784: itemid = PageGetItemId(page, minoff); - Not a heap page (nbtree) ./backend/access/nbtree/nbtdedup.c:789: itemid = PageGetItemId(page, PageGetMaxOffsetNumber(page)); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:501: curitemid = PageGetItemId(page, offset); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:1158: ItemId itemid = PageGetItemId(page, newitemoff); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:1607: itemid = PageGetItemId(origpage, firstrightoff); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:1631: itemid = PageGetItemId(origpage, lastleftoff); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:1741: itemid = PageGetItemId(origpage, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:1777: itemid = PageGetItemId(origpage, i); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:2013: itemid = PageGetItemId(origpage, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:2156: PageGetItemId(page, P_HIKEY)); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:2351: itemid = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:2367: itemid = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:2466: itemid = PageGetItemId(lpage, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:2688: ItemId itemId = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:2810: ItemId itemid = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtinsert.c:2936: ItemId itemid = PageGetItemId(page, deletable[i]); - Not a heap page (nbtree) ./backend/access/nbtree/nbtpage.c:1532: ItemId itemid = PageGetItemId(page, idxoffnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtpage.c:1908: itemid = PageGetItemId(page, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtpage.c:2127: itemid = PageGetItemId(page, poffset); - Not a heap page (nbtree) ./backend/access/nbtree/nbtpage.c:2133: itemid = PageGetItemId(page, nextoffset); - Not a heap page (nbtree) ./backend/access/nbtree/nbtpage.c:2168: itemid = PageGetItemId(page, poffset); - Not a heap page (nbtree) ./backend/access/nbtree/nbtpage.c:2297: itemid = PageGetItemId(page, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtpage.c:2453: itemid = PageGetItemId(page, P_FIRSTDATAKEY(opaque)); - Not a heap page (nbtree) ./backend/access/nbtree/nbtree.c:1294: PageGetItemId(page, offnum)); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsearch.c:151: itemid = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsearch.c:577: itemid = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsearch.c:669: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum)); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsearch.c:1581: ItemId iid = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsearch.c:1648: ItemId iid = PageGetItemId(page, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsearch.c:1673: ItemId iid = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsearch.c:2358: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum)); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsort.c:740: previi = PageGetItemId(rightmostpage, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsort.c:743: ItemId thisii = PageGetItemId(rightmostpage, off); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsort.c:926: ii = PageGetItemId(opage, last_off); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsort.c:940: hii = PageGetItemId(opage, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsort.c:973: ii = PageGetItemId(opage, OffsetNumberPrev(last_off)); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsort.c:985: hii = PageGetItemId(opage, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsplitloc.c:166: itemid = PageGetItemId(origpage, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsplitloc.c:215: itemid = PageGetItemId(origpage, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsplitloc.c:487: itemid = PageGetItemId(state->origpage, firstrightoff); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsplitloc.c:694: itemid = PageGetItemId(state->origpage, maxoff); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsplitloc.c:720: itemid = PageGetItemId(state->origpage, OffsetNumberPrev(state->newitemoff)); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsplitloc.c:1033: itemid = PageGetItemId(state->origpage, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsplitloc.c:1150: itemid = PageGetItemId(state->origpage, split->firstrightoff); - Not a heap page (nbtree) ./backend/access/nbtree/nbtsplitloc.c:1172: itemid = PageGetItemId(state->origpage, - Not a heap page (nbtree) ./backend/access/nbtree/nbtsplitloc.c:1188: itemid = PageGetItemId(state->origpage, split->firstrightoff); - Not a heap page (nbtree) ./backend/access/nbtree/nbtutils.c:1786: ItemId iid = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtutils.c:2491: itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, offnum)); - Not a heap page (nbtree) ./backend/access/nbtree/nbtxlog.c:218: itemid = PageGetItemId(page, OffsetNumberPrev(xlrec->offnum)); - Not a heap page (nbtree) ./backend/access/nbtree/nbtxlog.c:351: itemid = PageGetItemId(origpage, replacepostingoff); - Not a heap page (nbtree) ./backend/access/nbtree/nbtxlog.c:407: itemid = PageGetItemId(origpage, off); - Not a heap page (nbtree) ./backend/access/nbtree/nbtxlog.c:505: ItemId itemid = PageGetItemId(page, P_HIKEY); - Not a heap page (nbtree) ./backend/access/nbtree/nbtxlog.c:519: ItemId itemid = PageGetItemId(page, offnum); - Not a heap page (nbtree) ./backend/access/nbtree/nbtxlog.c:570: itemid = PageGetItemId(page, updatedoffsets[i]); - Not a heap page (nbtree) ./backend/access/nbtree/nbtxlog.c:746: itemid = PageGetItemId(page, nextoffset); - Not a heap page (nbtree) ./backend/access/nbtree/nbtxlog.c:750: itemid = PageGetItemId(page, poffset); - Not a heap page (nbtree) ./backend/access/spgist/spgdoinsert.c:192: PageGetItemId(parent->page, parent->offnum)); - Not a heap page (spgist) ./backend/access/spgist/spgdoinsert.c:253: PageGetItemId(current->page, current->offnum)); - Not a heap page (spgist) ./backend/access/spgist/spgdoinsert.c:266: PageGetItemId(current->page, current->offnum)); - Not a heap page (spgist) ./backend/access/spgist/spgdoinsert.c:355: PageGetItemId(current->page, i)); - Not a heap page (spgist) ./backend/access/spgist/spgdoinsert.c:428: PageGetItemId(current->page, i)); - Not a heap page (spgist) ./backend/access/spgist/spgdoinsert.c:470: PageGetItemId(current->page, toDelete[i])); - Not a heap page (spgist) ./backend/access/spgist/spgdoinsert.c:573: PageGetItemId(current->page, position)); - Not a heap page (spgist) ./backend/access/spgist/spgdoinsert.c:757: PageGetItemId(current->page, i)); - Not a heap page (spgist) ./backend/access/spgist/spgdoinsert.c:782: PageGetItemId(current->page, i)); - Not a heap page (spgist) ./backend/access/spgist/spgdoinsert.c:1837: PageGetItemId(current->page, current->offnum)); - Not a heap page (spgist) ./backend/access/spgist/spgdoinsert.c:2111: PageGetItemId(current.page, current.offnum)); - Not a heap page (spgist) ./backend/access/spgist/spgscan.c:718: PageGetItem(page, PageGetItemId(page, offset)); - Not a heap page (spgist) ./backend/access/spgist/spgscan.c:845: PageGetItem(page, PageGetItemId(page, offset)); - Not a heap page (spgist) ./backend/access/spgist/spgutils.c:908: PageGetItemId(page, i)); - Not a heap page (spgist) ./backend/access/spgist/spgvacuum.c:153: PageGetItemId(page, i)); - Not a heap page (spgist) ./backend/access/spgist/spgvacuum.c:242: PageGetItemId(page, i)); - Not a heap page (spgist) ./backend/access/spgist/spgvacuum.c:259: PageGetItemId(page, j)); - Not a heap page (spgist) ./backend/access/spgist/spgvacuum.c:348: ItemId idSrc = PageGetItemId(page, moveSrc[i]); - Not a heap page (spgist) ./backend/access/spgist/spgvacuum.c:349: ItemId idDest = PageGetItemId(page, moveDest[i]); - Not a heap page (spgist) ./backend/access/spgist/spgvacuum.c:367: PageGetItemId(page, chainSrc[i])); - Not a heap page (spgist) ./backend/access/spgist/spgvacuum.c:423: PageGetItemId(page, i)); - Not a heap page (spgist) ./backend/access/spgist/spgvacuum.c:525: dt = (SpGistDeadTuple) PageGetItem(page, PageGetItemId(page, i)); - Not a heap page (spgist) ./backend/access/spgist/spgvacuum.c:757: PageGetItemId(page, offset)); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:57: PageGetItemId(page, offset)); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:124: PageGetItemId(page, xldata->offnumHeadLeaf)); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:159: PageGetItemId(page, xldata->offnumParent)); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:273: PageGetItemId(page, xldata->offnumParent)); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:367: PageGetItemId(page, xldata->offnumParent)); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:414: PageGetItemId(page, xldata->offnumParent)); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:438: PageGetItemId(page, xldata->offnumParent)); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:706: PageGetItemId(page, xldata->offnumParent)); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:738: PageGetItemId(page, xldata->offnumParent)); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:803: ItemId idSrc = PageGetItemId(page, moveSrc[i]); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:804: ItemId idDest = PageGetItemId(page, moveDest[i]); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:823: PageGetItemId(page, chainSrc[i])); - Not a heap page (spgist) ./backend/access/spgist/spgxlog.c:900: PageGetItemId(page, itemToPlaceholder[i])); - Not a heap page (spgist) ./backend/access/heap/heapam.c:665: lpp = PageGetItemId(dp, lineoff); - Has assertion ItemIdIsNormal (asserts item is not removed; so concurrent vacuum cannot have removed this item & truncated the array) ./backend/access/heap/heapam.c:983: lpp = PageGetItemId(dp, lineoff); - Has assertion ItemIdIsNormal (asserts item is not removed; so concurrent vacuum cannot have removed this item & truncated the array) ./backend/access/heap/heapam.c:1005: lpp = PageGetItemId(dp, lineoff); - Has assertion ItemIdIsNormal (asserts item is not removed; so concurrent vacuum cannot have removed this item & truncated the array) ./backend/access/heap/heapam.c:2801: lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid)); - Has assertion ItemIdIsNormal (asserts item is not removed; so concurrent vacuum cannot have removed this item & truncated the array) ./backend/access/heap/heapam.c:3301: lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid)); - Has assertion ItemIdIsNormal (asserts item is not removed; so concurrent vacuum cannot have removed this item & truncated the array) ./backend/access/heap/heapam.c:4305: lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid)); - Has assertion ItemIdIsNormal (asserts item is not removed; so concurrent vacuum cannot have removed this item & truncated the array) ./backend/access/heap/heapam.c:5884: lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid)); - Has assertion ItemIdIsNormal (asserts item is not removed; so concurrent vacuum cannot have removed this item & truncated the array) ./backend/access/heap/heapam_handler.c:2241: lp = PageGetItemId(dp, targoffset); - Has assertion ItemIdIsNormal (asserts item is not removed; so concurrent vacuum cannot have removed this item & truncated the array) ./backend/access/heap/hio.c:78: ItemId itemId = PageGetItemId(pageHeader, offnum); - OffsetNumber is generated by PageAddItem ./backend/access/heap/rewriteheap.c:727: newitemid = PageGetItemId(page, newoff); - OffsetNumber is generated by PageAddItem ./backend/access/common/bufmask.c:105: ItemId itemId = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/heapam.c:449: for (lineoff = FirstOffsetNumber, lpp = PageGetItemId(dp, lineoff); - Has bound checks ./backend/access/heap/heapam.c:806: lpp = PageGetItemId(dp, lines); - Has bound checks ./backend/access/heap/heapam.c:1612: lp = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/heapam.c:1721: lp = PageGetItemId(dp, offnum); - Has bound checks ./backend/access/heap/heapam.c:1892: lp = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/heapam.c:5783: lp = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/heapam.c:6040: lp = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/heapam.c:7515: lp = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/heapam.c:8873: lp = PageGetItemId(page, xlrec->offnum); - Has bound checks ./backend/access/heap/heapam.c:9258: lp = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/heapam.c:9469: lp = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/heapam.c:9526: lp = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/heapam.c:9599: lp = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/heapam.c:9640: lp = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/heapam.c:9762: ItemId iid = PageGetItemId(page, off); - Has bound checks ./backend/access/heap/heapam_handler.c:1037: itemid = PageGetItemId(targpage, hscan->rs_cindex); - Has bound checks ./backend/access/heap/heapam_handler.c:2196: lp = PageGetItemId(dp, offnum); - Has bound checks ./backend/access/heap/heapam_handler.c:2381: itemid = PageGetItemId(page, tupoffset); - Has bound checks ./backend/access/heap/pruneheap.c:278: itemid = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/pruneheap.c:575: lp = PageGetItemId(dp, offnum); - Has bound checks ./backend/access/heap/pruneheap.c:895: ItemId lp = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/vacuumlazy.c:1271: itemid = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/vacuumlazy.c:1498: itemid = PageGetItemId(page, frozen[i].offset); - Has bound checks ./backend/access/heap/vacuumlazy.c:1953: itemid = PageGetItemId(page, toff); - Has bound checks ./backend/access/heap/vacuumlazy.c:2815: itemid = PageGetItemId(page, offnum); - Has bound checks ./backend/access/heap/vacuumlazy.c:3005: itemid = PageGetItemId(page, offnum); - Has bound checks ./backend/storage/page/bufpage.c:233: itemId = PageGetItemId(phdr, offsetNumber); - Has bound checks ./backend/storage/page/bufpage.c:260: itemId = PageGetItemId(phdr, offsetNumber); - Has bound checks ./backend/storage/page/bufpage.c:312: itemId = PageGetItemId(phdr, offsetNumber); - Has bound checks ./backend/storage/page/bufpage.c:724: lp = PageGetItemId(page, i); - Has bound checks ./backend/storage/page/bufpage.c:898: ItemId lp = PageGetItemId(page, offnum); - Has bound checks ./backend/storage/page/bufpage.c:966: tup = PageGetItemId(page, offnum); - Has bound checks ./backend/storage/page/bufpage.c:1025: ItemId ii = PageGetItemId(phdr, i); - Has bound checks ./backend/storage/page/bufpage.c:1107: lp = PageGetItemId(page, offnum); - Has bound checks ./backend/storage/page/bufpage.c:1204: tup = PageGetItemId(page, offnum); - Has bound checks ./backend/storage/page/bufpage.c:1260: ItemId ii = PageGetItemId(phdr, i); - Has bound checks ./backend/storage/page/bufpage.c:1316: tupid = PageGetItemId(page, offnum); - Has bound checks ./backend/storage/page/bufpage.c:1359: ItemId ii = PageGetItemId(phdr, i); - Has bound checks ./backend/access/heap/pruneheap.c:510: rootlp = PageGetItemId(dp, rootoffnum); - Callsite guarantees bounds ./backend/access/heap/pruneheap.c:835: ItemId fromlp = PageGetItemId(page, fromoff); - Callsite guarantees bounds ./backend/access/heap/pruneheap.c:845: ItemId lp = PageGetItemId(page, off); - Callsite guarantees bounds ./backend/access/heap/pruneheap.c:855: ItemId lp = PageGetItemId(page, off); - Callsite guarantees bounds ./backend/access/heap/vacuumlazy.c:2061: itemid = PageGetItemId(page, offnum); - Callsite guarantees bounds ./backend/storage/page/bufpage.c:531: lp = PageGetItemId(page, itemidptr->offsetindex + 1); - Callsite guarantees bounds ./backend/storage/page/bufpage.c:636: lp = PageGetItemId(page, itemidptr->offsetindex + 1); - Callsite guarantees bounds ./include/storage/bufpage.h:232: * PageGetItemId - Not a call ./include/storage/bufpage.h:235:#define PageGetItemId(page, offsetNumber) \ - Not a call ./backend/access/brin/brin_pageops.c:121: * PageGetItemId() is simple enough that it was safe to do that - Not a call ./backend/storage/page/bufpage.c:984: * PageGetItemId, because we are manipulating the _array_, not individual - Not a call
From f49e1bb31f4ad56c3f42b9ed4e9fcb5d99198ed4 Mon Sep 17 00:00:00 2001 From: Matthias van de Meent <boekewurm@gmail.com> Date: Tue, 9 Mar 2021 14:42:52 +0100 Subject: [PATCH v3] Truncate a pages' line pointer array when it has trailing unused ItemIds. This will allow reuse of what is effectively free space for data as well as new line pointers, instead of keeping it reserved for line pointers only. An additional benefit is that the HasFreeLinePointers hint-bit optimization now doesn't hint for free line pointers at the end of the array, slightly increasing the specificity of where the free lines are; and saving us from needing to search to the end of the array if all other entries are already filled. --- src/backend/access/heap/heapam.c | 9 ++++++++- src/backend/access/heap/pruneheap.c | 1 + src/backend/storage/page/bufpage.c | 13 ++++++++++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 3b435c107d..ae218cfac6 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -635,8 +635,15 @@ heapgettup(HeapScanDesc scan, } else { + /* + * The previous returned tuple may have been vacuumed since the + * previous scan when we use a non-MVCC snapshot, so we must + * re-establish the lineoff <= PageGetMaxOffsetNumber(dp) + * invariant + */ lineoff = /* previous offnum */ - OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self))); + Min(lines, + OffsetNumberPrev(ItemPointerGetOffsetNumber(&(tuple->t_self)))); } /* page and lineoff now reference the physically previous tid */ diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c index 8bb38d6406..5903cdca82 100644 --- a/src/backend/access/heap/pruneheap.c +++ b/src/backend/access/heap/pruneheap.c @@ -946,6 +946,7 @@ heap_get_root_tuples(Page page, OffsetNumber *root_offsets) */ for (;;) { + Assert(OffsetNumberIsValid(nextoffnum) && nextoffnum <= maxoff); lp = PageGetItemId(page, nextoffnum); /* Check for broken chains */ diff --git a/src/backend/storage/page/bufpage.c b/src/backend/storage/page/bufpage.c index 9ac556b4ae..10d8f26ad0 100644 --- a/src/backend/storage/page/bufpage.c +++ b/src/backend/storage/page/bufpage.c @@ -672,7 +672,11 @@ compactify_tuples(itemIdCompact itemidbase, int nitems, Page page, bool presorte * PageRepairFragmentation * * Frees fragmented space on a page. - * It doesn't remove unused line pointers! Please don't change this. + * It doesn't remove intermediate unused line pointers (that would mean + * moving ItemIds, and that would imply invalidating indexed values), but it + * does truncate the page->pd_linp array to the last unused line pointer, so + * that this space may also be reused for data, instead of only for line + * pointers. * * This routine is usable for heap pages only, but see PageIndexMultiDelete. * @@ -691,6 +695,7 @@ PageRepairFragmentation(Page page) int nline, nstorage, nunused; + OffsetNumber lastUsed = InvalidOffsetNumber; int i; Size totallen; bool presorted = true; /* For now */ @@ -724,6 +729,7 @@ PageRepairFragmentation(Page page) lp = PageGetItemId(page, i); if (ItemIdIsUsed(lp)) { + lastUsed = i; if (ItemIdHasStorage(lp)) { itemidptr->offsetindex = i - 1; @@ -771,6 +777,11 @@ PageRepairFragmentation(Page page) compactify_tuples(itemidbase, nstorage, page, presorted); } + if (lastUsed != nline) { + ((PageHeader) page)->pd_lower = SizeOfPageHeaderData + (sizeof(ItemIdData) * lastUsed); + nunused = nunused - (nline - lastUsed); + } + /* Set hint bit for PageAddItem */ if (nunused > 0) PageSetHasFreeLinePointers(page); -- 2.20.1