Update minimum recovery point on truncation.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 10 Dec 2012 13:54:42 +0000 (15:54 +0200)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Mon, 10 Dec 2012 16:27:30 +0000 (18:27 +0200)
If a file is truncated, we must update minRecoveryPoint. Once a file is
truncated, there's no going back; it would not be safe to stop recovery
at a point earlier than that anymore.

Per report from Kyotaro HORIGUCHI. Backpatch to 8.4. Before that,
minRecoveryPoint was not updated during recovery at all.

src/backend/access/transam/xact.c
src/backend/catalog/storage.c

index e33e5a912c890b9241e6ce3a9a594bf0db6d09e6..89d30f94ec424a9a439114de5a647f409f365707 100644 (file)
@@ -4255,7 +4255,7 @@ xactGetCommittedChildren(TransactionId **ptr)
  */
 
 static void
-xact_redo_commit(xl_xact_commit *xlrec, TransactionId xid)
+xact_redo_commit(XLogRecPtr lsn, xl_xact_commit *xlrec, TransactionId xid)
 {
    TransactionId *sub_xids;
    TransactionId max_xid;
@@ -4280,17 +4280,37 @@ xact_redo_commit(xl_xact_commit *xlrec, TransactionId xid)
    }
 
    /* Make sure files supposed to be dropped are dropped */
-   for (i = 0; i < xlrec->nrels; i++)
+   if (xlrec->nrels > 0)
    {
-       SMgrRelation srel = smgropen(xlrec->xnodes[i]);
-       ForkNumber  fork;
+       /*
+        * First update minimum recovery point to cover this WAL record. Once
+        * a relation is deleted, there's no going back. The buffer manager
+        * enforces the WAL-first rule for normal updates to relation files,
+        * so that the minimum recovery point is always updated before the
+        * corresponding change in the data file is flushed to disk, but we
+        * have to do the same here since we're bypassing the buffer manager.
+        *
+        * Doing this before deleting the files means that if a deletion fails
+        * for some reason, you cannot start up the system even after restart,
+        * until you fix the underlying situation so that the deletion will
+        * succeed. Alternatively, we could update the minimum recovery point
+        * after deletion, but that would leave a small window where the
+        * WAL-first rule would be violated.
+        */
+       XLogFlush(lsn);
 
-       for (fork = 0; fork <= MAX_FORKNUM; fork++)
+       for (i = 0; i < xlrec->nrels; i++)
        {
-           XLogDropRelation(xlrec->xnodes[i], fork);
-           smgrdounlink(srel, fork, false, true);
+           SMgrRelation srel = smgropen(xlrec->xnodes[i]);
+           ForkNumber  fork;
+
+           for (fork = 0; fork <= MAX_FORKNUM; fork++)
+           {
+               XLogDropRelation(xlrec->xnodes[i], fork);
+               smgrdounlink(srel, fork, false, true);
+           }
+           smgrclose(srel);
        }
-       smgrclose(srel);
    }
 }
 
@@ -4346,7 +4366,7 @@ xact_redo(XLogRecPtr lsn, XLogRecord *record)
    {
        xl_xact_commit *xlrec = (xl_xact_commit *) XLogRecGetData(record);
 
-       xact_redo_commit(xlrec, record->xl_xid);
+       xact_redo_commit(lsn, xlrec, record->xl_xid);
    }
    else if (info == XLOG_XACT_ABORT)
    {
@@ -4364,7 +4384,7 @@ xact_redo(XLogRecPtr lsn, XLogRecord *record)
    {
        xl_xact_commit_prepared *xlrec = (xl_xact_commit_prepared *) XLogRecGetData(record);
 
-       xact_redo_commit(&xlrec->crec, xlrec->xid);
+       xact_redo_commit(lsn, &xlrec->crec, xlrec->xid);
        RemoveTwoPhaseFile(xlrec->xid, false);
    }
    else if (info == XLOG_XACT_ABORT_PREPARED)
index 326e76f283e08430e0aa440987b50b2d962b8bf3..2e279c7ed850bb67fbeeaad7c12e9ed75e05dabf 100644 (file)
@@ -426,6 +426,24 @@ smgr_redo(XLogRecPtr lsn, XLogRecord *record)
         */
        smgrcreate(reln, MAIN_FORKNUM, true);
 
+       /*
+        * Before we perform the truncation, update minimum recovery point
+        * to cover this WAL record. Once the relation is truncated, there's
+        * no going back. The buffer manager enforces the WAL-first rule
+        * for normal updates to relation files, so that the minimum recovery
+        * point is always updated before the corresponding change in the
+        * data file is flushed to disk. We have to do the same manually
+        * here.
+        *
+        * Doing this before the truncation means that if the truncation fails
+        * for some reason, you cannot start up the system even after restart,
+        * until you fix the underlying situation so that the truncation will
+        * succeed. Alternatively, we could update the minimum recovery point
+        * after truncation, but that would leave a small window where the
+        * WAL-first rule could be violated.
+        */
+       XLogFlush(lsn);
+
        smgrtruncate(reln, MAIN_FORKNUM, xlrec->blkno, false);
 
        /* Also tell xlogutils.c about it */