From 3b558ac830b2c30b50b9993c505e3061cbda036c Mon Sep 17 00:00:00 2001 From: Heikki Linnakangas Date: Tue, 29 Jul 2014 10:33:15 +0300 Subject: [PATCH] Treat 2PC commit/abort the same as regular xacts in recovery. 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 | 60 ++++++++++++++++++++++++++----- src/include/access/xact.h | 3 +- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 609ad2e07b..040205760f 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -5431,11 +5431,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; } @@ -5454,6 +5464,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) @@ -5472,10 +5483,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; @@ -5490,7 +5518,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 && @@ -5510,7 +5538,7 @@ recoveryStopsBefore(XLogRecord *record) if (stopsHere) { recoveryStopAfter = false; - recoveryStopXid = record->xl_xid; + recoveryStopXid = recordXid; recoveryStopTime = recordXtime; recoveryStopName[0] = '\0'; @@ -5576,12 +5604,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 @@ -5592,21 +5632,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", @@ -5719,7 +5762,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)) diff --git a/src/include/access/xact.h b/src/include/access/xact.h index 634f5b2480..9c2420265c 100644 --- a/src/include/access/xact.h +++ b/src/include/access/xact.h @@ -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 -- 2.40.0