diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index c1128f8..c63e122 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 5adf956..2bc3ee7 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 d625d17..cb8bca5 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 e3a43d3..2417b4f 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 464f264..06410bd 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 e58e478..7f2d71a 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 2ca71c3..5c685fd 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 */