diff --git a/src/backend/access/nbtree/nbtpage.c b/src/backend/access/nbtree/nbtpage.c index f9811b6d3c2c8..0f4901b614c2f 100644 --- a/src/backend/access/nbtree/nbtpage.c +++ b/src/backend/access/nbtree/nbtpage.c @@ -2032,13 +2032,6 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno, leftsib = opaque->btpo_next; _bt_relbuf(rel, lbuf); - /* - * It'd be good to check for interrupts here, but it's not easy to - * do so because a lock is always held. This block isn't - * frequently reached, so hopefully the consequences of not - * checking interrupts aren't too bad. - */ - if (leftsib == P_NONE) { elog(LOG, "no left sibling (concurrent deletion?) of block %u in \"%s\"", @@ -2057,6 +2050,9 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno, } return false; } + + CHECK_FOR_INTERRUPTS(); + lbuf = _bt_getbuf(rel, leftsib, BT_WRITE); page = BufferGetPage(lbuf); opaque = (BTPageOpaque) PageGetSpecialPointer(page); @@ -2118,13 +2114,40 @@ _bt_unlink_halfdead_page(Relation rel, Buffer leafbuf, BlockNumber scanblkno, rbuf = _bt_getbuf(rel, rightsib, BT_WRITE); page = BufferGetPage(rbuf); opaque = (BTPageOpaque) PageGetSpecialPointer(page); + + /* + * Validate target's right sibling page. Its left link must point back to + * the target page. + */ if (opaque->btpo_prev != target) - ereport(ERROR, + { + /* + * This is known to fail in the field; sibling link corruption is + * relatively common. Press on with vacuuming rather than just + * throwing an ERROR (same approach used for left-sibling's-right-link + * validation check a moment ago). + */ + ereport(LOG, (errcode(ERRCODE_INDEX_CORRUPTED), errmsg_internal("right sibling's left-link doesn't match: " - "block %u links to %u instead of expected %u in index \"%s\"", - rightsib, opaque->btpo_prev, target, - RelationGetRelationName(rel)))); + "right sibling %u of target %u with leafblkno %u " + "and scanblkno %u spuriously links to non-target %u " + "on level %u of index \"%s\"", + rightsib, target, leafblkno, + scanblkno, opaque->btpo_prev, + targetlevel, RelationGetRelationName(rel)))); + + /* Must release all pins and locks on failure exit */ + if (BufferIsValid(lbuf)) + _bt_relbuf(rel, lbuf); + _bt_relbuf(rel, rbuf); + _bt_relbuf(rel, buf); + if (target != leafblkno) + _bt_relbuf(rel, leafbuf); + + return false; + } + rightsib_is_rightmost = P_RIGHTMOST(opaque); *rightsib_empty = (P_FIRSTDATAKEY(opaque) > PageGetMaxOffsetNumber(page)); diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c index e8c87e1edf2b4..73cdd52f08005 100644 --- a/src/backend/access/nbtree/nbtree.c +++ b/src/backend/access/nbtree/nbtree.c @@ -1141,8 +1141,7 @@ btvacuumpage(BTVacState *vstate, BlockNumber scanblkno) * can't be half-dead because only an interrupted VACUUM process can * leave pages in that state, so we'd definitely have dealt with it * back when the page was the scanblkno page (half-dead pages are - * always marked fully deleted by _bt_pagedel()). This assumes that - * there can be only one vacuum process running at a time. + * always marked fully deleted by _bt_pagedel(), barring corruption). */ if (!opaque || !P_ISLEAF(opaque) || P_ISHALFDEAD(opaque)) {