*** a/src/backend/access/nbtree/nbtree.c
--- b/src/backend/access/nbtree/nbtree.c
***************
*** 404,410 **** btbeginscan(PG_FUNCTION_ARGS)
  
  	/* allocate private workspace */
  	so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData));
! 	so->currPos.buf = so->markPos.buf = InvalidBuffer;
  	if (scan->numberOfKeys > 0)
  		so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
  	else
--- 404,411 ----
  
  	/* allocate private workspace */
  	so = (BTScanOpaque) palloc(sizeof(BTScanOpaqueData));
! 	BTScanPosInvalidate(so->currPos);
! 	BTScanPosInvalidate(so->markPos);
  	if (scan->numberOfKeys > 0)
  		so->keyData = (ScanKey) palloc(scan->numberOfKeys * sizeof(ScanKeyData));
  	else
***************
*** 424,431 **** btbeginscan(PG_FUNCTION_ARGS)
  	 * scan->xs_itupdesc whether we'll need it or not, since that's so cheap.
  	 */
  	so->currTuples = so->markTuples = NULL;
- 	so->currPos.nextTupleOffset = 0;
- 	so->markPos.nextTupleOffset = 0;
  
  	scan->xs_itupdesc = RelationGetDescr(rel);
  
--- 425,430 ----
***************
*** 451,467 **** btrescan(PG_FUNCTION_ARGS)
  	{
  		/* Before leaving current page, deal with any killed items */
  		if (so->numKilled > 0)
! 			_bt_killitems(scan, false);
! 		ReleaseBuffer(so->currPos.buf);
! 		so->currPos.buf = InvalidBuffer;
  	}
  
- 	if (BTScanPosIsValid(so->markPos))
- 	{
- 		ReleaseBuffer(so->markPos.buf);
- 		so->markPos.buf = InvalidBuffer;
- 	}
  	so->markItemIndex = -1;
  
  	/*
  	 * Allocate tuple workspace arrays, if needed for an index-only scan and
--- 450,463 ----
  	{
  		/* Before leaving current page, deal with any killed items */
  		if (so->numKilled > 0)
! 			_bt_killitems(scan);
! 		BTScanPosUnpinIfPinned(so->currPos);
! 		BTScanPosInvalidate(so->currPos);
  	}
  
  	so->markItemIndex = -1;
+ 	BTScanPosUnpinIfPinned(so->markPos);
+ 	BTScanPosInvalidate(so->markPos);
  
  	/*
  	 * Allocate tuple workspace arrays, if needed for an index-only scan and
***************
*** 515,531 **** btendscan(PG_FUNCTION_ARGS)
  	{
  		/* Before leaving current page, deal with any killed items */
  		if (so->numKilled > 0)
! 			_bt_killitems(scan, false);
! 		ReleaseBuffer(so->currPos.buf);
! 		so->currPos.buf = InvalidBuffer;
  	}
  
- 	if (BTScanPosIsValid(so->markPos))
- 	{
- 		ReleaseBuffer(so->markPos.buf);
- 		so->markPos.buf = InvalidBuffer;
- 	}
  	so->markItemIndex = -1;
  
  	/* Release storage */
  	if (so->keyData != NULL)
--- 511,524 ----
  	{
  		/* Before leaving current page, deal with any killed items */
  		if (so->numKilled > 0)
! 			_bt_killitems(scan);
! 		BTScanPosUnpinIfPinned(so->currPos);
  	}
  
  	so->markItemIndex = -1;
+ 	BTScanPosUnpinIfPinned(so->markPos);
+ 
+ 	/* No need to invalidate positions, the RAM is about to be freed. */
  
  	/* Release storage */
  	if (so->keyData != NULL)
***************
*** 551,563 **** btmarkpos(PG_FUNCTION_ARGS)
  {
  	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
  	BTScanOpaque so = (BTScanOpaque) scan->opaque;
  
! 	/* we aren't holding any read locks, but gotta drop the pin */
! 	if (BTScanPosIsValid(so->markPos))
! 	{
! 		ReleaseBuffer(so->markPos.buf);
! 		so->markPos.buf = InvalidBuffer;
! 	}
  
  	/*
  	 * Just record the current itemIndex.  If we later step to next page
--- 544,553 ----
  {
  	IndexScanDesc scan = (IndexScanDesc) PG_GETARG_POINTER(0);
  	BTScanOpaque so = (BTScanOpaque) scan->opaque;
+ 	so->markPos.currPage = so->currPos.currPage;
  
! 	/* We don't take out an extra pin for the mark position. */
! 	so->markPos.buf = InvalidBuffer;
  
  	/*
  	 * Just record the current itemIndex.  If we later step to next page
***************
*** 596,601 **** btrestrpos(PG_FUNCTION_ARGS)
--- 586,592 ----
  		 * The mark position is on the same page we are currently on. Just
  		 * restore the itemIndex.
  		 */
+ 		Assert(so->markPos.currPage == so->currPos.currPage);
  		so->currPos.itemIndex = so->markItemIndex;
  	}
  	else
***************
*** 606,620 **** btrestrpos(PG_FUNCTION_ARGS)
  			/* Before leaving current page, deal with any killed items */
  			if (so->numKilled > 0 &&
  				so->currPos.buf != so->markPos.buf)
! 				_bt_killitems(scan, false);
! 			ReleaseBuffer(so->currPos.buf);
! 			so->currPos.buf = InvalidBuffer;
  		}
  
  		if (BTScanPosIsValid(so->markPos))
  		{
- 			/* bump pin on mark buffer for assignment to current buffer */
- 			IncrBufferRefCount(so->markPos.buf);
  			memcpy(&so->currPos, &so->markPos,
  				   offsetof(BTScanPosData, items[1]) +
  				   so->markPos.lastItem * sizeof(BTScanPosItem));
--- 597,608 ----
  			/* Before leaving current page, deal with any killed items */
  			if (so->numKilled > 0 &&
  				so->currPos.buf != so->markPos.buf)
! 				_bt_killitems(scan);
! 			BTScanPosUnpinIfPinned(so->currPos);
  		}
  
  		if (BTScanPosIsValid(so->markPos))
  		{
  			memcpy(&so->currPos, &so->markPos,
  				   offsetof(BTScanPosData, items[1]) +
  				   so->markPos.lastItem * sizeof(BTScanPosItem));
***************
*** 622,627 **** btrestrpos(PG_FUNCTION_ARGS)
--- 610,617 ----
  				memcpy(so->currTuples, so->markTuples,
  					   so->markPos.nextTupleOffset);
  		}
+ 		else
+ 			BTScanPosInvalidate(so->currPos);
  	}
  
  	PG_RETURN_VOID();
*** a/src/backend/access/nbtree/nbtsearch.c
--- b/src/backend/access/nbtree/nbtsearch.c
***************
*** 22,27 ****
--- 22,28 ----
  #include "storage/predicate.h"
  #include "utils/lsyscache.h"
  #include "utils/rel.h"
+ #include "utils/tqual.h"
  
  
  static bool _bt_readpage(IndexScanDesc scan, ScanDirection dir,
***************
*** 31,36 **** static void _bt_saveitem(BTScanOpaque so, int itemIndex,
--- 32,67 ----
  static bool _bt_steppage(IndexScanDesc scan, ScanDirection dir);
  static Buffer _bt_walk_left(Relation rel, Buffer buf);
  static bool _bt_endpoint(IndexScanDesc scan, ScanDirection dir);
+ static void _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp);
+ 
+ 
+ /*
+  *	_bt_drop_lock_and_maybe_pin()
+  *
+  * Unlock the buffer; and if it is safe to release the pin, do that, too.  It
+  * is safe if the scan is using an MVCC snapshot and the index is WAL-logged.
+  * This will prevent vacuum from stalling in a blocked state trying to read a
+  * page when a cursor is sitting on it -- at least in many important cases.
+  *
+  * Set the buffer to invalid if the pin is released, since the buffer may be
+  * re-used.  If we need to go back to this block (for example, to apply
+  * LP_DEAD hints) we must get a fresh reference to the buffer.  Hopefully it
+  * will remain in shared memory for as long as it takes to scan the index
+  * buffer page.
+  */
+ static void
+ _bt_drop_lock_and_maybe_pin(IndexScanDesc scan, BTScanPos sp)
+ {
+ 	LockBuffer(sp->buf, BUFFER_LOCK_UNLOCK);
+ 
+ 	if (IsMVCCSnapshot(scan->xs_snapshot) &&
+ 		RelationNeedsWAL(scan->indexRelation) &&
+ 		!scan->xs_want_itup)
+ 	{
+ 		ReleaseBuffer(sp->buf);
+ 		sp->buf = InvalidBuffer;
+ 	}
+ }
  
  
  /*
***************
*** 506,511 **** _bt_first(IndexScanDesc scan, ScanDirection dir)
--- 537,544 ----
  	StrategyNumber strat_total;
  	BTScanPosItem *currItem;
  
+ 	Assert(!BTScanPosIsValid(so->currPos));
+ 
  	pgstat_count_index_scan(rel);
  
  	/*
***************
*** 942,950 **** _bt_first(IndexScanDesc scan, ScanDirection dir)
  	/* don't need to keep the stack around... */
  	_bt_freestack(stack);
  
- 	/* remember which buffer we have pinned, if any */
- 	so->currPos.buf = buf;
- 
  	if (!BufferIsValid(buf))
  	{
  		/*
--- 975,980 ----
***************
*** 970,976 **** _bt_first(IndexScanDesc scan, ScanDirection dir)
  		so->currPos.moreRight = false;
  	}
  	so->numKilled = 0;			/* just paranoia */
- 	so->markItemIndex = -1;		/* ditto */
  
  	/* position to the precise item on the page */
  	offnum = _bt_binsrch(rel, buf, keysCount, scankeys, nextkey);
--- 1000,1005 ----
***************
*** 1023,1028 **** _bt_first(IndexScanDesc scan, ScanDirection dir)
--- 1052,1061 ----
  			break;
  	}
  
+ 	/* remember which buffer we have pinned, if any */
+ 	Assert(!BTScanPosIsValid(so->currPos));
+ 	so->currPos.buf = buf;
+ 
  	/*
  	 * Now load data from the first page of the scan.
  	 */
***************
*** 1032,1043 **** _bt_first(IndexScanDesc scan, ScanDirection dir)
  		 * There's no actually-matching data on this page.  Try to advance to
  		 * the next page.  Return false if there's no matching data at all.
  		 */
  		if (!_bt_steppage(scan, dir))
  			return false;
  	}
! 
! 	/* Drop the lock, but not pin, on the current page */
! 	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
  
  	/* OK, itemIndex says what to return */
  	currItem = &so->currPos.items[so->currPos.itemIndex];
--- 1065,1079 ----
  		 * There's no actually-matching data on this page.  Try to advance to
  		 * the next page.  Return false if there's no matching data at all.
  		 */
+ 		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
  		if (!_bt_steppage(scan, dir))
  			return false;
  	}
! 	else
! 	{
! 		/* Drop the lock, and maybe the pin, on the current page */
! 		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
! 	}
  
  	/* OK, itemIndex says what to return */
  	currItem = &so->currPos.items[so->currPos.itemIndex];
***************
*** 1051,1058 **** _bt_first(IndexScanDesc scan, ScanDirection dir)
  /*
   *	_bt_next() -- Get the next item in a scan.
   *
!  *		On entry, so->currPos describes the current page, which is pinned
!  *		but not locked, and so->currPos.itemIndex identifies which item was
   *		previously returned.
   *
   *		On successful exit, scan->xs_ctup.t_self is set to the TID of the
--- 1087,1094 ----
  /*
   *	_bt_next() -- Get the next item in a scan.
   *
!  *		On entry, so->currPos describes the current page, which may be pinned
!  *		but is not locked, and so->currPos.itemIndex identifies which item was
   *		previously returned.
   *
   *		On successful exit, scan->xs_ctup.t_self is set to the TID of the
***************
*** 1076,1101 **** _bt_next(IndexScanDesc scan, ScanDirection dir)
  	{
  		if (++so->currPos.itemIndex > so->currPos.lastItem)
  		{
- 			/* We must acquire lock before applying _bt_steppage */
- 			Assert(BufferIsValid(so->currPos.buf));
- 			LockBuffer(so->currPos.buf, BT_READ);
  			if (!_bt_steppage(scan, dir))
  				return false;
- 			/* Drop the lock, but not pin, on the new page */
- 			LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
  		}
  	}
  	else
  	{
  		if (--so->currPos.itemIndex < so->currPos.firstItem)
  		{
- 			/* We must acquire lock before applying _bt_steppage */
- 			Assert(BufferIsValid(so->currPos.buf));
- 			LockBuffer(so->currPos.buf, BT_READ);
  			if (!_bt_steppage(scan, dir))
  				return false;
- 			/* Drop the lock, but not pin, on the new page */
- 			LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
  		}
  	}
  
--- 1112,1127 ----
***************
*** 1135,1141 **** _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
  	IndexTuple	itup;
  	bool		continuescan;
  
! 	/* we must have the buffer pinned and locked */
  	Assert(BufferIsValid(so->currPos.buf));
  
  	page = BufferGetPage(so->currPos.buf);
--- 1161,1170 ----
  	IndexTuple	itup;
  	bool		continuescan;
  
! 	/*
! 	 * We must have the buffer pinned and locked, but the usual macro can't be
! 	 * used here; this function is what makes it good for currPos.
! 	 */
  	Assert(BufferIsValid(so->currPos.buf));
  
  	page = BufferGetPage(so->currPos.buf);
***************
*** 1144,1149 **** _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
--- 1173,1191 ----
  	maxoff = PageGetMaxOffsetNumber(page);
  
  	/*
+ 	 * We note the buffer's block number so that we can release the pin later.
+ 	 * This allows us to re-read the buffer if it is needed again for hinting.
+ 	 */
+ 	so->currPos.currPage = BufferGetBlockNumber(so->currPos.buf);
+ 
+ 	/*
+ 	 * We save the LSN of the page as we read it, so that we know whether it
+ 	 * safe to apply LP_DEAD hints to the page later.  This allows us to drop
+ 	 * the pin for MVCC scans, which allows vacuum to avoid blocking.
+ 	 */
+ 	so->currPos.lsn = PageGetLSN(page);
+ 
+ 	/*
  	 * we must save the page's right-link while scanning it; this tells us
  	 * where to step right to after we're done with these items.  There is no
  	 * corresponding need for the left-link, since splits always go right.
***************
*** 1153,1158 **** _bt_readpage(IndexScanDesc scan, ScanDirection dir, OffsetNumber offnum)
--- 1195,1206 ----
  	/* initialize tuple workspace to empty */
  	so->currPos.nextTupleOffset = 0;
  
+ 	/*
+ 	 * Now that the current page has been made consistent, the macro should be
+ 	 * good.
+ 	 */
+ 	Assert(BTScanPosIsPinned(so->currPos));
+ 
  	if (ScanDirectionIsForward(dir))
  	{
  		/* load items[] in ascending order */
***************
*** 1241,1251 **** _bt_saveitem(BTScanOpaque so, int itemIndex,
  /*
   *	_bt_steppage() -- Step to next page containing valid data for scan
   *
!  * On entry, so->currPos.buf must be pinned and read-locked.  We'll drop
!  * the lock and pin before moving to next page.
   *
!  * On success exit, we hold pin and read-lock on the next interesting page,
!  * and so->currPos is updated to contain data from that page.
   *
   * If there are no more matching records in the given direction, we drop all
   * locks and pins, set so->currPos.buf to InvalidBuffer, and return FALSE.
--- 1289,1302 ----
  /*
   *	_bt_steppage() -- Step to next page containing valid data for scan
   *
!  * On entry, if so->currPos.buf is valid the buffer is pinned but not locked;
!  * if pinned, we'll drop the pin before moving to next page.  The buffer is
!  * not locked on entry.
   *
!  * On success exit, so->currPos is updated to contain data from the next
!  * interesting page.  For success on a scan using a non-MVCC snapshot we hold
!  * a pin, but not a read lock, on that page.  If we do not hold the pin, we
!  * set so->currPos.buf to InvalidBuffer.  We return TRUE to indicate success.
   *
   * If there are no more matching records in the given direction, we drop all
   * locks and pins, set so->currPos.buf to InvalidBuffer, and return FALSE.
***************
*** 1258,1269 **** _bt_steppage(IndexScanDesc scan, ScanDirection dir)
  	Page		page;
  	BTPageOpaque opaque;
  
! 	/* we must have the buffer pinned and locked */
! 	Assert(BufferIsValid(so->currPos.buf));
  
  	/* Before leaving current page, deal with any killed items */
  	if (so->numKilled > 0)
! 		_bt_killitems(scan, true);
  
  	/*
  	 * Before we modify currPos, make a copy of the page data if there was a
--- 1309,1319 ----
  	Page		page;
  	BTPageOpaque opaque;
  
! 	Assert(BTScanPosIsValid(so->currPos));
  
  	/* Before leaving current page, deal with any killed items */
  	if (so->numKilled > 0)
! 		_bt_killitems(scan);
  
  	/*
  	 * Before we modify currPos, make a copy of the page data if there was a
***************
*** 1271,1278 **** _bt_steppage(IndexScanDesc scan, ScanDirection dir)
  	 */
  	if (so->markItemIndex >= 0)
  	{
- 		/* bump pin on current buffer for assignment to mark buffer */
- 		IncrBufferRefCount(so->currPos.buf);
  		memcpy(&so->markPos, &so->currPos,
  			   offsetof(BTScanPosData, items[1]) +
  			   so->currPos.lastItem * sizeof(BTScanPosItem));
--- 1321,1326 ----
***************
*** 1294,1307 **** _bt_steppage(IndexScanDesc scan, ScanDirection dir)
  		/* Remember we left a page with data */
  		so->currPos.moreLeft = true;
  
  		for (;;)
  		{
- 			/* release the previous buffer */
- 			_bt_relbuf(rel, so->currPos.buf);
- 			so->currPos.buf = InvalidBuffer;
  			/* if we're at end of scan, give up */
  			if (blkno == P_NONE || !so->currPos.moreRight)
  				return false;
  			/* check for interrupts while we're not holding any buffer lock */
  			CHECK_FOR_INTERRUPTS();
  			/* step right one page */
--- 1342,1358 ----
  		/* Remember we left a page with data */
  		so->currPos.moreLeft = true;
  
+ 		/* release the previous buffer, if pinned */
+ 		BTScanPosUnpinIfPinned(so->currPos);
+ 
  		for (;;)
  		{
  			/* if we're at end of scan, give up */
  			if (blkno == P_NONE || !so->currPos.moreRight)
+ 			{
+ 				BTScanPosInvalidate(so->currPos);
  				return false;
+ 			}
  			/* check for interrupts while we're not holding any buffer lock */
  			CHECK_FOR_INTERRUPTS();
  			/* step right one page */
***************
*** 1317,1324 **** _bt_steppage(IndexScanDesc scan, ScanDirection dir)
--- 1368,1377 ----
  				if (_bt_readpage(scan, dir, P_FIRSTDATAKEY(opaque)))
  					break;
  			}
+ 
  			/* nope, keep going */
  			blkno = opaque->btpo_next;
+ 			_bt_relbuf(rel, so->currPos.buf);
  		}
  	}
  	else
***************
*** 1326,1331 **** _bt_steppage(IndexScanDesc scan, ScanDirection dir)
--- 1379,1390 ----
  		/* Remember we left a page with data */
  		so->currPos.moreRight = true;
  
+ 		/* XXX: Can walking left be lighter on the locking and pins? */
+ 		if (BTScanPosIsPinned(so->currPos))
+ 			LockBuffer(so->currPos.buf, BUFFER_LOCK_SHARE);
+ 		else
+ 			so->currPos.buf = _bt_getbuf(rel, so->currPos.currPage, BT_READ);
+ 
  		/*
  		 * Walk left to the next page with data.  This is much more complex
  		 * than the walk-right case because of the possibility that the page
***************
*** 1339,1345 **** _bt_steppage(IndexScanDesc scan, ScanDirection dir)
  			if (!so->currPos.moreLeft)
  			{
  				_bt_relbuf(rel, so->currPos.buf);
! 				so->currPos.buf = InvalidBuffer;
  				return false;
  			}
  
--- 1398,1404 ----
  			if (!so->currPos.moreLeft)
  			{
  				_bt_relbuf(rel, so->currPos.buf);
! 				BTScanPosInvalidate(so->currPos);
  				return false;
  			}
  
***************
*** 1348,1354 **** _bt_steppage(IndexScanDesc scan, ScanDirection dir)
--- 1407,1416 ----
  
  			/* if we're physically at end of index, return failure */
  			if (so->currPos.buf == InvalidBuffer)
+ 			{
+ 				BTScanPosInvalidate(so->currPos);
  				return false;
+ 			}
  
  			/*
  			 * Okay, we managed to move left to a non-deleted page. Done if
***************
*** 1368,1373 **** _bt_steppage(IndexScanDesc scan, ScanDirection dir)
--- 1430,1438 ----
  		}
  	}
  
+ 	/* Drop the lock, and maybe the pin, on the current page */
+ 	_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
+ 
  	return true;
  }
  
***************
*** 1604,1610 **** _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
  		 * exists.
  		 */
  		PredicateLockRelation(rel, scan->xs_snapshot);
! 		so->currPos.buf = InvalidBuffer;
  		return false;
  	}
  
--- 1669,1675 ----
  		 * exists.
  		 */
  		PredicateLockRelation(rel, scan->xs_snapshot);
! 		BTScanPosInvalidate(so->currPos);
  		return false;
  	}
  
***************
*** 1647,1653 **** _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
  		so->currPos.moreRight = false;
  	}
  	so->numKilled = 0;			/* just paranoia */
- 	so->markItemIndex = -1;		/* ditto */
  
  	/*
  	 * Now load data from the first page of the scan.
--- 1712,1717 ----
***************
*** 1658,1669 **** _bt_endpoint(IndexScanDesc scan, ScanDirection dir)
  		 * There's no actually-matching data on this page.  Try to advance to
  		 * the next page.  Return false if there's no matching data at all.
  		 */
  		if (!_bt_steppage(scan, dir))
  			return false;
  	}
! 
! 	/* Drop the lock, but not pin, on the current page */
! 	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
  
  	/* OK, itemIndex says what to return */
  	currItem = &so->currPos.items[so->currPos.itemIndex];
--- 1722,1736 ----
  		 * There's no actually-matching data on this page.  Try to advance to
  		 * the next page.  Return false if there's no matching data at all.
  		 */
+ 		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
  		if (!_bt_steppage(scan, dir))
  			return false;
  	}
! 	else
! 	{
! 		/* Drop the lock, and maybe the pin, on the current page */
! 		_bt_drop_lock_and_maybe_pin(scan, &so->currPos);
! 	}
  
  	/* OK, itemIndex says what to return */
  	currItem = &so->currPos.items[so->currPos.itemIndex];
*** a/src/backend/access/nbtree/nbtutils.c
--- b/src/backend/access/nbtree/nbtutils.c
***************
*** 1724,1732 **** _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc,
   * scan->so contains information about the current page and killed tuples
   * thereon (generally, this should only be called if so->numKilled > 0).
   *
!  * The caller must have pin on so->currPos.buf, but may or may not have
!  * read-lock, as indicated by haveLock.  Note that we assume read-lock
!  * is sufficient for setting LP_DEAD status (which is only a hint).
   *
   * We match items by heap TID before assuming they are the right ones to
   * delete.  We cope with cases where items have moved right due to insertions.
--- 1724,1732 ----
   * scan->so contains information about the current page and killed tuples
   * thereon (generally, this should only be called if so->numKilled > 0).
   *
!  * The caller may or may not have a pin on so->currPos.buf.  Note that we
!  * assume read-lock is sufficient for setting LP_DEAD status (which is only a
!  * hint).
   *
   * We match items by heap TID before assuming they are the right ones to
   * delete.  We cope with cases where items have moved right due to insertions.
***************
*** 1741,1747 **** _bt_check_rowcompare(ScanKey skey, IndexTuple tuple, TupleDesc tupdesc,
   * recycled.)
   */
  void
! _bt_killitems(IndexScanDesc scan, bool haveLock)
  {
  	BTScanOpaque so = (BTScanOpaque) scan->opaque;
  	Page		page;
--- 1741,1747 ----
   * recycled.)
   */
  void
! _bt_killitems(IndexScanDesc scan)
  {
  	BTScanOpaque so = (BTScanOpaque) scan->opaque;
  	Page		page;
***************
*** 1749,1767 **** _bt_killitems(IndexScanDesc scan, bool haveLock)
  	OffsetNumber minoff;
  	OffsetNumber maxoff;
  	int			i;
  	bool		killedsomething = false;
  
! 	Assert(BufferIsValid(so->currPos.buf));
  
! 	if (!haveLock)
  		LockBuffer(so->currPos.buf, BT_READ);
  
! 	page = BufferGetPage(so->currPos.buf);
  	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
  	minoff = P_FIRSTDATAKEY(opaque);
  	maxoff = PageGetMaxOffsetNumber(page);
  
! 	for (i = 0; i < so->numKilled; i++)
  	{
  		int			itemIndex = so->killedItems[i];
  		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
--- 1749,1800 ----
  	OffsetNumber minoff;
  	OffsetNumber maxoff;
  	int			i;
+ 	int			numKilled = so->numKilled;
  	bool		killedsomething = false;
  
! 	Assert(BTScanPosIsValid(so->currPos));
  
! 	/*
! 	 * Always reset the scan state, so we don't look for same items on other
! 	 * pages.
! 	 */
! 	so->numKilled = 0;
! 
! 	if (BTScanPosIsPinned(so->currPos))
! 	{
! 		/* The buffer is still pinned, but not locked.  Lock it now. */
  		LockBuffer(so->currPos.buf, BT_READ);
  
! 		/* Since the else condition needs page, get it here, too. */
! 		page = BufferGetPage(so->currPos.buf);
! 	}
! 	else
! 	{
! 		Buffer		buf;
! 
! 		/* Attempt to re-read the buffer, getting pin and lock. */
! 		buf = _bt_getbuf(scan->indexRelation, so->currPos.currPage, BT_READ);
! 
! 		/* It might not exist anymore; in which case we can't hint it. */
! 		if (!BufferIsValid(buf))
! 			return;
! 
! 		page = BufferGetPage(buf);
! 		if (PageGetLSN(page) == so->currPos.lsn)
! 			so->currPos.buf = buf;
! 		else
! 		{
! 			/* Modified while not pinned means hinting is not safe. */
! 			_bt_relbuf(scan->indexRelation, buf);
! 			return;
! 		}
! 	}
! 
  	opaque = (BTPageOpaque) PageGetSpecialPointer(page);
  	minoff = P_FIRSTDATAKEY(opaque);
  	maxoff = PageGetMaxOffsetNumber(page);
  
! 	for (i = 0; i < numKilled; i++)
  	{
  		int			itemIndex = so->killedItems[i];
  		BTScanPosItem *kitem = &so->currPos.items[itemIndex];
***************
*** 1799,1812 **** _bt_killitems(IndexScanDesc scan, bool haveLock)
  		MarkBufferDirtyHint(so->currPos.buf, true);
  	}
  
! 	if (!haveLock)
! 		LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
! 
! 	/*
! 	 * Always reset the scan state, so we don't look for same items on other
! 	 * pages.
! 	 */
! 	so->numKilled = 0;
  }
  
  
--- 1832,1838 ----
  		MarkBufferDirtyHint(so->currPos.buf, true);
  	}
  
! 	LockBuffer(so->currPos.buf, BUFFER_LOCK_UNLOCK);
  }
  
  
*** a/src/include/access/nbtree.h
--- b/src/include/access/nbtree.h
***************
*** 518,523 **** typedef struct BTScanPosData
--- 518,525 ----
  {
  	Buffer		buf;			/* if valid, the buffer is pinned */
  
+ 	XLogRecPtr	lsn;			/* pos in the WAL stream when page was read */
+ 	BlockNumber currPage;		/* page we've referencd by items array */
  	BlockNumber nextPage;		/* page's right link when we scanned it */
  
  	/*
***************
*** 551,557 **** typedef struct BTScanPosData
  
  typedef BTScanPosData *BTScanPos;
  
! #define BTScanPosIsValid(scanpos) BufferIsValid((scanpos).buf)
  
  /* We need one of these for each equality-type SK_SEARCHARRAY scan key */
  typedef struct BTArrayKeyInfo
--- 553,589 ----
  
  typedef BTScanPosData *BTScanPos;
  
! #define BTScanPosIsPinned(scanpos) \
! ( \
! 	AssertMacro(BlockNumberIsValid((scanpos).currPage) || \
! 				!BufferIsValid((scanpos).buf)), \
! 	BufferIsValid((scanpos).buf) \
! )
! #define BTScanPosUnpin(scanpos) \
! 	do { \
! 		ReleaseBuffer((scanpos).buf); \
! 		(scanpos).buf = InvalidBuffer; \
! 	} while (0)
! #define BTScanPosUnpinIfPinned(scanpos) \
! 	do { \
! 		if (BTScanPosIsPinned(scanpos)) \
! 			BTScanPosUnpin(scanpos); \
! 	} while (0)
! 
! #define BTScanPosIsValid(scanpos) \
! ( \
! 	AssertMacro(BlockNumberIsValid((scanpos).currPage) || \
! 				!BufferIsValid((scanpos).buf)), \
! 	BlockNumberIsValid((scanpos).currPage) \
! )
! #define BTScanPosInvalidate(scanpos) \
! 	do { \
! 		(scanpos).currPage = InvalidBlockNumber; \
! 		(scanpos).nextPage = InvalidBlockNumber; \
! 		(scanpos).buf = InvalidBuffer; \
! 		(scanpos).lsn = InvalidXLogRecPtr; \
! 		(scanpos).nextTupleOffset = 0; \
! 	} while (0);
  
  /* We need one of these for each equality-type SK_SEARCHARRAY scan key */
  typedef struct BTArrayKeyInfo
***************
*** 697,703 **** extern void _bt_preprocess_keys(IndexScanDesc scan);
  extern IndexTuple _bt_checkkeys(IndexScanDesc scan,
  			  Page page, OffsetNumber offnum,
  			  ScanDirection dir, bool *continuescan);
! extern void _bt_killitems(IndexScanDesc scan, bool haveLock);
  extern BTCycleId _bt_vacuum_cycleid(Relation rel);
  extern BTCycleId _bt_start_vacuum(Relation rel);
  extern void _bt_end_vacuum(Relation rel);
--- 729,735 ----
  extern IndexTuple _bt_checkkeys(IndexScanDesc scan,
  			  Page page, OffsetNumber offnum,
  			  ScanDirection dir, bool *continuescan);
! extern void _bt_killitems(IndexScanDesc scan);
  extern BTCycleId _bt_vacuum_cycleid(Relation rel);
  extern BTCycleId _bt_start_vacuum(Relation rel);
  extern void _bt_end_vacuum(Relation rel);
