summaryrefslogtreecommitdiff
path: root/src/backend/access/nbtree/nbtxlog.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2014-01-14 17:34:57 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2014-01-14 17:34:57 -0500
commitab4bb5c47ac38f35a0dd88ebb7812d6a9c34cb43 (patch)
tree57d56ddd05b999b923a44f37a3c896bed211c1cf /src/backend/access/nbtree/nbtxlog.c
parentfc27b406817f303e38fbc86d857cead2a5116668 (diff)
Fix multiple bugs in index page locking during hot-standby WAL replay.
In ordinary operation, VACUUM must be careful to take a cleanup lock on each leaf page of a btree index; this ensures that no indexscans could still be "in flight" to heap tuples due to be deleted. (Because of possible index-tuple motion due to concurrent page splits, it's not enough to lock only the pages we're deleting index tuples from.) In Hot Standby, the WAL replay process must likewise lock every leaf page. There were several bugs in the code for that: * The replay scan might come across unused, all-zero pages in the index. While btree_xlog_vacuum itself did the right thing (ie, nothing) with such pages, xlogutils.c supposed that such pages must be corrupt and would throw an error. This accounts for various reports of replication failures with "PANIC: WAL contains references to invalid pages". To fix, add a ReadBufferMode value that instructs XLogReadBufferExtended not to complain when we're doing this. * btree_xlog_vacuum performed the extra locking if standbyState == STANDBY_SNAPSHOT_READY, but that's not the correct test: we won't open up for hot standby queries until the database has reached consistency, and we don't want to do the extra locking till then either, for fear of reading corrupted pages (which bufmgr.c would complain about). Fix by exporting a new function from xlog.c that will report whether we're actually in hot standby replay mode. * To ensure full coverage of the index in the replay scan, btvacuumscan would emit a dummy WAL record for the last page of the index, if no vacuuming work had been done on that page. However, if the last page of the index is all-zero, that would result in corruption of said page, since the functions called on it weren't prepared to handle that case. There's no need to lock any such pages, so change the logic to target the last normal leaf page instead. The first two of these bugs were diagnosed by Andres Freund, the other one by me. Fixes based on ideas from Heikki Linnakangas and myself. This has been wrong since Hot Standby was introduced, so back-patch to 9.0.
Diffstat (limited to 'src/backend/access/nbtree/nbtxlog.c')
-rw-r--r--src/backend/access/nbtree/nbtxlog.c41
1 files changed, 30 insertions, 11 deletions
diff --git a/src/backend/access/nbtree/nbtxlog.c b/src/backend/access/nbtree/nbtxlog.c
index 5fbb481a4fe..f0fe8a3892e 100644
--- a/src/backend/access/nbtree/nbtxlog.c
+++ b/src/backend/access/nbtree/nbtxlog.c
@@ -489,28 +489,47 @@ btree_xlog_vacuum(XLogRecPtr lsn, XLogRecord *record)
BTPageOpaque opaque;
/*
- * If queries might be active then we need to ensure every block is
+ * If queries might be active then we need to ensure every leaf page is
* unpinned between the lastBlockVacuumed and the current block, if there
- * are any. This ensures that every block in the index is touched during
- * VACUUM as required to ensure scans work correctly.
+ * are any. This prevents replay of the VACUUM from reaching the stage of
+ * removing heap tuples while there could still be indexscans "in flight"
+ * to those particular tuples (see nbtree/README).
+ *
+ * It might be worth checking if there are actually any backends running;
+ * if not, we could just skip this.
+ *
+ * Since VACUUM can visit leaf pages out-of-order, it might issue records
+ * with lastBlockVacuumed >= block; that's not an error, it just means
+ * nothing to do now.
+ *
+ * Note: since we touch all pages in the range, we will lock non-leaf
+ * pages, and also any empty (all-zero) pages that may be in the index. It
+ * doesn't seem worth the complexity to avoid that. But it's important
+ * that HotStandbyActiveInReplay() will not return true if the database
+ * isn't yet consistent; so we need not fear reading still-corrupt blocks
+ * here during crash recovery.
*/
- if (standbyState == STANDBY_SNAPSHOT_READY &&
- (xlrec->lastBlockVacuumed + 1) != xlrec->block)
+ if (HotStandbyActiveInReplay())
{
- BlockNumber blkno = xlrec->lastBlockVacuumed + 1;
+ BlockNumber blkno;
- for (; blkno < xlrec->block; blkno++)
+ for (blkno = xlrec->lastBlockVacuumed + 1; blkno < xlrec->block; blkno++)
{
/*
+ * We use RBM_NORMAL_NO_LOG mode because it's not an error
+ * condition to see all-zero pages. The original btvacuumpage
+ * scan would have skipped over all-zero pages, noting them in FSM
+ * but not bothering to initialize them just yet; so we mustn't
+ * throw an error here. (We could skip acquiring the cleanup lock
+ * if PageIsNew, but it's probably not worth the cycles to test.)
+ *
* XXX we don't actually need to read the block, we just need to
* confirm it is unpinned. If we had a special call into the
* buffer manager we could optimise this so that if the block is
* not in shared_buffers we confirm it as unpinned.
- *
- * Another simple optimization would be to check if there's any
- * backends running; if not, we could just skip this.
*/
- buffer = XLogReadBufferExtended(xlrec->node, MAIN_FORKNUM, blkno, RBM_NORMAL);
+ buffer = XLogReadBufferExtended(xlrec->node, MAIN_FORKNUM, blkno,
+ RBM_NORMAL_NO_LOG);
if (BufferIsValid(buffer))
{
LockBufferForCleanup(buffer);