Treat 2PC commit/abort the same as regular xacts in recovery.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Tue, 29 Jul 2014 07:33:15 +0000 (10:33 +0300)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Tue, 29 Jul 2014 08:59:22 +0000 (11:59 +0300)
There were several oversights in recovery code where COMMIT/ABORT PREPARED
records were ignored:

* pg_last_xact_replay_timestamp() (wasn't updated for 2PC commits)
* recovery_min_apply_delay (2PC commits were applied immediately)
* recovery_target_xid (recovery would not stop if the XID used 2PC)

The first of those was reported by Sergiy Zuban in bug #11032, analyzed by
Tom Lane and Andres Freund. The bug was always there, but was masked before
commit d19bd29f07aef9e508ff047d128a4046cc8bc1e2, because COMMIT PREPARED
always created an extra regular transaction that was WAL-logged.

Backpatch to all supported versions (older versions didn't have all the
features and therefore didn't have all of the above bugs).

src/backend/access/transam/xlog.c
src/include/access/xact.h

index 93f3ca2b2b7fc90336189692741d1d0754092c0e..2bf49a8af8d37867d41c32a3968247171adb1cf5 100644 (file)
@@ -5457,11 +5457,21 @@ getRecordTimestamp(XLogRecord *record, TimestampTz *recordXtime)
        *recordXtime = ((xl_xact_commit *) XLogRecGetData(record))->xact_time;
        return true;
    }
+   if (record->xl_rmid == RM_XACT_ID && record_info == XLOG_XACT_COMMIT_PREPARED)
+   {
+       *recordXtime = ((xl_xact_commit_prepared *) XLogRecGetData(record))->crec.xact_time;
+       return true;
+   }
    if (record->xl_rmid == RM_XACT_ID && record_info == XLOG_XACT_ABORT)
    {
        *recordXtime = ((xl_xact_abort *) XLogRecGetData(record))->xact_time;
        return true;
    }
+   if (record->xl_rmid == RM_XACT_ID && record_info == XLOG_XACT_ABORT_PREPARED)
+   {
+       *recordXtime = ((xl_xact_abort_prepared *) XLogRecGetData(record))->arec.xact_time;
+       return true;
+   }
    return false;
 }
 
@@ -5480,6 +5490,7 @@ recoveryStopsBefore(XLogRecord *record)
    uint8       record_info;
    bool        isCommit;
    TimestampTz recordXtime = 0;
+   TransactionId recordXid;
 
    /* Check if we should stop as soon as reaching consistency */
    if (recoveryTarget == RECOVERY_TARGET_IMMEDIATE && reachedConsistency)
@@ -5498,10 +5509,27 @@ recoveryStopsBefore(XLogRecord *record)
    if (record->xl_rmid != RM_XACT_ID)
        return false;
    record_info = record->xl_info & ~XLR_INFO_MASK;
+
    if (record_info == XLOG_XACT_COMMIT_COMPACT || record_info == XLOG_XACT_COMMIT)
+   {
+       isCommit = true;
+       recordXid = record->xl_xid;
+   }
+   if (record_info == XLOG_XACT_COMMIT_PREPARED)
+   {
        isCommit = true;
+       recordXid = ((xl_xact_commit_prepared *) XLogRecGetData(record))->xid;
+   }
    else if (record_info == XLOG_XACT_ABORT)
+   {
+       isCommit = false;
+       recordXid = record->xl_xid;
+   }
+   else if (record_info == XLOG_XACT_ABORT_PREPARED)
+   {
        isCommit = false;
+       recordXid = ((xl_xact_abort_prepared *) XLogRecGetData(record))->xid;
+   }
    else
        return false;
 
@@ -5516,7 +5544,7 @@ recoveryStopsBefore(XLogRecord *record)
         * they complete. A higher numbered xid will complete before you about
         * 50% of the time...
         */
-       stopsHere = (record->xl_xid == recoveryTargetXid);
+       stopsHere = (recordXid == recoveryTargetXid);
    }
 
    if (recoveryTarget == RECOVERY_TARGET_TIME &&
@@ -5536,7 +5564,7 @@ recoveryStopsBefore(XLogRecord *record)
    if (stopsHere)
    {
        recoveryStopAfter = false;
-       recoveryStopXid = record->xl_xid;
+       recoveryStopXid = recordXid;
        recoveryStopTime = recordXtime;
        recoveryStopName[0] = '\0';
 
@@ -5602,12 +5630,24 @@ recoveryStopsAfter(XLogRecord *record)
    if (record->xl_rmid == RM_XACT_ID &&
        (record_info == XLOG_XACT_COMMIT_COMPACT ||
         record_info == XLOG_XACT_COMMIT ||
-        record_info == XLOG_XACT_ABORT))
+        record_info == XLOG_XACT_COMMIT_PREPARED ||
+        record_info == XLOG_XACT_ABORT ||
+        record_info == XLOG_XACT_ABORT_PREPARED))
    {
+       TransactionId recordXid;
+
        /* Update the last applied transaction timestamp */
        if (getRecordTimestamp(record, &recordXtime))
            SetLatestXTime(recordXtime);
 
+       /* Extract the XID of the committed/aborted transaction */
+       if (record_info == XLOG_XACT_COMMIT_PREPARED)
+           recordXid = ((xl_xact_commit_prepared *) XLogRecGetData(record))->xid;
+       else if (record_info == XLOG_XACT_ABORT_PREPARED)
+           recordXid = ((xl_xact_abort_prepared *) XLogRecGetData(record))->xid;
+       else
+           recordXid = record->xl_xid;
+
        /*
         * There can be only one transaction end record with this exact
         * transactionid
@@ -5618,21 +5658,24 @@ recoveryStopsAfter(XLogRecord *record)
         * 50% of the time...
         */
        if (recoveryTarget == RECOVERY_TARGET_XID && recoveryTargetInclusive &&
-           record->xl_xid == recoveryTargetXid)
+           recordXid == recoveryTargetXid)
        {
            recoveryStopAfter = true;
-           recoveryStopXid = record->xl_xid;
+           recoveryStopXid = recordXid;
            recoveryStopTime = recordXtime;
            recoveryStopName[0] = '\0';
 
-           if (record_info == XLOG_XACT_COMMIT_COMPACT || record_info == XLOG_XACT_COMMIT)
+           if (record_info == XLOG_XACT_COMMIT_COMPACT ||
+               record_info == XLOG_XACT_COMMIT ||
+               record_info == XLOG_XACT_COMMIT_PREPARED)
            {
                ereport(LOG,
                        (errmsg("recovery stopping after commit of transaction %u, time %s",
                                recoveryStopXid,
                                timestamptz_to_str(recoveryStopTime))));
            }
-           else if (record_info == XLOG_XACT_ABORT)
+           else if (record_info == XLOG_XACT_ABORT ||
+                    record_info == XLOG_XACT_ABORT_PREPARED)
            {
                ereport(LOG,
                        (errmsg("recovery stopping after abort of transaction %u, time %s",
@@ -5745,7 +5788,8 @@ recoveryApplyDelay(XLogRecord *record)
    record_info = record->xl_info & ~XLR_INFO_MASK;
    if (!(record->xl_rmid == RM_XACT_ID &&
          (record_info == XLOG_XACT_COMMIT_COMPACT ||
-          record_info == XLOG_XACT_COMMIT)))
+          record_info == XLOG_XACT_COMMIT ||
+          record_info == XLOG_XACT_COMMIT_PREPARED)))
        return false;
 
    if (!getRecordTimestamp(record, &xtime))
index 10ee9433521c7ea81f10de021275ef3a1b62623a..2168dc3cb525525748062701c3bf920d319be92a 100644 (file)
@@ -180,8 +180,7 @@ typedef struct xl_xact_abort
 /*
  * COMMIT_PREPARED and ABORT_PREPARED are identical to COMMIT/ABORT records
  * except that we have to store the XID of the prepared transaction explicitly
- * --- the XID in the record header will be for the transaction doing the
- * COMMIT PREPARED or ABORT PREPARED command.
+ * --- the XID in the record header will be invalid.
  */
 
 typedef struct xl_xact_commit_prepared