diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index c1128f89ec..c63e1223d8 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1654,6 +1654,78 @@ include_dir 'conf.d' + + max_age_prepared_xacts (integer) + + max_age_prepared_xacts configuration parameter + + + + + Sets maximum age after which a prepared transaction is considered an + orphan. max_age_prepared_xacts only applies when + prepared transactions are enabled (see + ). The age for a + transaction is calculated from the time it was created to current time. + If this value is specified without units, it is taken as milliseconds. + The default value is -1 which allows prepared transactions to live + forever. This parameter can only be set in the + postgresql.conf file or on the server command + line. + + + + If you are planning to use prepared transactions, this parameter + may be set to a value that defines maximum age a prepared + transaction can take in your environment. This parameter must be + used in conjunction with prepared_xacts_vacuum_warn_timeout + (see + ). + + + + + + prepared_xacts_vacuum_warn_timeout (integer) + + prepared_xacts_vacuum_warn_timeout configuration parameter + + + + + Sets timeout after which vacuum starts throwing warnings for every + prepared transactions that has exceeded maximum age defined by + max_age_prepared_xacts (see ). + If this value is specified without units, it is taken as milliseconds. + The default value of -1 will disable this warning mechanism. Setting + a too value could potentially fill up log with orphaned prepared + transaction warnings, so this parameter must be set to a value that + is reasonably large to not fill up log file, but small enough to + notify of long running and potential orphaned prepared transactions. + This parameter can only be set in the + postgresql.conf file or on the server command + line. + + + + This guc along with max_age_prepared_xacts, if + enabled, help you better manage prepared transactions. Warnings are + emitted to log when an autovacuum worker encounters orphaned + prepared transactions, or to a client which has issued a vacuum + command. This parameter defines how frequently a vacuum process + should throw a warning when it encounters a prepared transaction + with an age exceeding max_age_prepared_xacts. + + + + The warning are not thrown when vacuum command is run + with relations, or when vacuumdb command is executed. + + + + + work_mem (integer) diff --git a/src/backend/access/transam/twophase.c b/src/backend/access/transam/twophase.c index 5adf956f41..2bc3ee7d23 100644 --- a/src/backend/access/transam/twophase.c +++ b/src/backend/access/transam/twophase.c @@ -116,6 +116,10 @@ /* GUC variable, can't be changed after startup */ int max_prepared_xacts = 0; +/* GUC variables for checking for orphaned prepared transactions */ +int max_age_prepared_xacts = 0; +int prepared_xacts_vacuum_warn_timeout = 0; + /* * This struct describes one global transaction that is in prepared state * or attempting to become prepared. @@ -184,6 +188,9 @@ typedef struct TwoPhaseStateData /* Number of valid prepXacts entries. */ int numPrepXacts; + /* Flag to tell if we throw a warning for over aged prepared xacts */ + TimestampTz overage_warned_at; + /* There are max_prepared_xacts items in this array */ GlobalTransaction prepXacts[FLEXIBLE_ARRAY_MEMBER]; } TwoPhaseStateData; @@ -264,6 +271,7 @@ TwoPhaseShmemInit(void) Assert(!found); TwoPhaseState->freeGXacts = NULL; TwoPhaseState->numPrepXacts = 0; + TwoPhaseState->overage_warned_at = 0; /* * Initialize the linked list of free GlobalTransactionData structs @@ -428,6 +436,14 @@ MarkAsPreparing(TransactionId xid, const char *gid, Assert(TwoPhaseState->numPrepXacts < max_prepared_xacts); TwoPhaseState->prepXacts[TwoPhaseState->numPrepXacts++] = gxact; + /* + * Is this the first prepared transaction? If so, then let's set + * last warned date at curret timestamp, so that we can warn + * about this if it becomes orphaned. + */ + if (TwoPhaseState->numPrepXacts == 1) + TwoPhaseState->overage_warned_at = GetCurrentTimestamp(); + LWLockRelease(TwoPhaseStateLock); return gxact; @@ -697,6 +713,81 @@ GetPreparedTransactionList(GlobalTransaction *gxacts) return num; } +/* + * To be called from within a vacuum process whether initiated through + * autovacuum or via client initiated vacuum command. + * + * Returns total number of orphaned prepared transactions and throws + * one warning message for every orphaned prepared transactions found. + * Return -1 if timeout period hasn't completed. + * + * force_warning skips the timeout check for throwing warnings. This + * ensures that manually executed vacuum command is notified of any + * orphaned prepared transactions. + */ +int +WarnOverAgedPreparedTransactions(bool force_warning) +{ + bool should_warn = force_warning; + int num = 0; + int num_overage = 0; + int i; + TimestampTz current_time = GetCurrentTimestamp(); + + if (prepared_xacts_vacuum_warn_timeout == -1 || max_age_prepared_xacts == -1) + return -1; + + /* Get exclusive lock so that we can update data in TwoPhaseState + * global structure. + */ + LWLockAcquire(TwoPhaseStateLock, LW_EXCLUSIVE); + + should_warn |= TimestampDifferenceExceeds(TwoPhaseState->overage_warned_at, current_time, prepared_xacts_vacuum_warn_timeout); + + if (should_warn) + { + TwoPhaseState->overage_warned_at = current_time; + } + + LWLockRelease(TwoPhaseStateLock); + + if (!should_warn) + return -1; + + /* Get shared lock to count orphaned transactions */ + LWLockAcquire(TwoPhaseStateLock, LW_SHARED); + + if (TwoPhaseState->numPrepXacts == 0) + { + LWLockRelease(TwoPhaseStateLock); + return 0; + } + + num = TwoPhaseState->numPrepXacts; + + for (i = 0; i < num; i++) + { + if (TimestampDifferenceExceeds(TwoPhaseState->prepXacts[i]->prepared_at, current_time, max_age_prepared_xacts)) + { + num_overage += 1; + + ereport(WARNING, + (errcode(ERRCODE_WARNING), + errmsg("prepared transaction with identifier \"%s\" created on \"%s\" is overage.", + TwoPhaseState->prepXacts[i]->gid, timestamptz_to_str(TwoPhaseState->prepXacts[i]->prepared_at)))); + } + } + + if (num_overage > 0) + ereport(WARNING, + (errcode(ERRCODE_WARNING), + errmsg("%d orphaned prepared transactions found.", num_overage), + errhint("Overage transaction(s) may require manual administrative action(s)."))); + + LWLockRelease(TwoPhaseStateLock); + + return num_overage; +} /* Working status for pg_prepared_xact */ typedef struct @@ -2383,6 +2474,14 @@ PrepareRedoAdd(char *buf, XLogRecPtr start_lsn, Assert(TwoPhaseState->numPrepXacts < max_prepared_xacts); TwoPhaseState->prepXacts[TwoPhaseState->numPrepXacts++] = gxact; + /* + * Is this the first prepared transaction? If so, then let's set + * last warned date at curret timestamp, so that we can warn + * about this if it becomes orphaned. + */ + if (TwoPhaseState->numPrepXacts == 1) + TwoPhaseState->overage_warned_at = GetCurrentTimestamp(); + if (origin_id != InvalidRepOriginId) { /* recover apply progress */ diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index d625d17bf4..cb8bca565f 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -30,6 +30,7 @@ #include "access/multixact.h" #include "access/tableam.h" #include "access/transam.h" +#include "access/twophase.h" #include "access/xact.h" #include "catalog/namespace.h" #include "catalog/pg_database.h" @@ -373,7 +374,17 @@ vacuum(List *relations, VacuumParams *params, relations = newrels; } else + { + /* + * We need to throw a warning to a client running a vacuum process if + * there are any orphaned prepared transactions so that an administrator + * may take requisite action. Since this is a manually initiated commmand, + * we need to force generation of warnings. + */ + WarnOverAgedPreparedTransactions(true); + relations = get_all_vacuum_rels(params->options); + } /* * Decide whether we need to start/commit our own transactions. diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 6d1f28c327..cc9874446c 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -71,6 +71,7 @@ #include "access/reloptions.h" #include "access/tableam.h" #include "access/transam.h" +#include "access/twophase.h" #include "access/xact.h" #include "catalog/dependency.h" #include "catalog/namespace.h" @@ -2004,6 +2005,11 @@ do_autovacuum(void) default_multixact_freeze_table_age = vacuum_multixact_freeze_table_age; } + /* + * Let's throw warnings for any orphaned prepared transactions. + */ + WarnOverAgedPreparedTransactions(false); + ReleaseSysCache(tuple); /* StartTransactionCommand changed elsewhere */ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 8228e1f390..3fd1c96c96 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -2448,6 +2448,28 @@ static struct config_int ConfigureNamesInt[] = NULL, NULL, NULL }, + { + {"max_age_prepared_xacts", PGC_SIGHUP, LOGGING_WHEN, + gettext_noop("Sets the maximum age for a prepared transaciton after which vacuum starts complaining."), + NULL, + GUC_UNIT_MS + }, + &max_age_prepared_xacts, + -1, -1, INT_MAX, + NULL, NULL, NULL + }, + + { + {"prepared_xacts_vacuum_warn_timeout", PGC_SIGHUP, LOGGING_WHEN, + gettext_noop("Timeout before vacuum throws a warning about overage prepared transactions."), + NULL, + GUC_UNIT_MS + }, + &prepared_xacts_vacuum_warn_timeout, + -1, -1, INT_MAX, + NULL, NULL, NULL + }, + #ifdef LOCK_DEBUG { {"trace_lock_oidmin", PGC_SUSET, DEVELOPER_OPTIONS, diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index e1048c0047..b37cd66762 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -184,6 +184,11 @@ # (change requires restart) #backend_flush_after = 0 # measured in pages, 0 disables +# - Prepared Transactions - + +#max_age_prepared_xacts = 6h # 0ms - INT_MAX; default is -1 (disabled) +#prepared_xacts_vacuum_warn_timeout = 6h # 0ms - INT_MAX; default is -1 (disabled) + #------------------------------------------------------------------------------ # WRITE-AHEAD LOG diff --git a/src/include/access/twophase.h b/src/include/access/twophase.h index 2ca71c3445..5c685fdb2a 100644 --- a/src/include/access/twophase.h +++ b/src/include/access/twophase.h @@ -27,6 +27,8 @@ typedef struct GlobalTransactionData *GlobalTransaction; /* GUC variable */ extern PGDLLIMPORT int max_prepared_xacts; +extern PGDLLIMPORT int max_age_prepared_xacts; +extern PGDLLIMPORT int prepared_xacts_vacuum_warn_timeout; extern Size TwoPhaseShmemSize(void); extern void TwoPhaseShmemInit(void); @@ -58,4 +60,6 @@ extern void PrepareRedoAdd(char *buf, XLogRecPtr start_lsn, XLogRecPtr end_lsn, RepOriginId origin_id); extern void PrepareRedoRemove(TransactionId xid, bool giveWarning); extern void restoreTwoPhaseData(void); + +extern int WarnOverAgedPreparedTransactions(bool force_warning); #endif /* TWOPHASE_H */