|
6 | 6 | * Copyright (c) 2000-2005, PostgreSQL Global Development Group
|
7 | 7 | *
|
8 | 8 | * IDENTIFICATION
|
9 |
| - * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.60 2005/01/01 05:43:06 momjian Exp $ |
| 9 | + * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.61 2005/02/20 02:21:28 tgl Exp $ |
10 | 10 | *
|
11 | 11 | *-------------------------------------------------------------------------
|
12 | 12 | */
|
|
16 | 16 | #include "access/clog.h"
|
17 | 17 | #include "access/subtrans.h"
|
18 | 18 | #include "access/transam.h"
|
| 19 | +#include "miscadmin.h" |
19 | 20 | #include "storage/ipc.h"
|
20 | 21 | #include "storage/proc.h"
|
| 22 | +#include "utils/builtins.h" |
21 | 23 |
|
22 | 24 |
|
23 | 25 | /* Number of OIDs to prefetch (preallocate) per XLOG write */
|
@@ -46,6 +48,37 @@ GetNewTransactionId(bool isSubXact)
|
46 | 48 |
|
47 | 49 | xid = ShmemVariableCache->nextXid;
|
48 | 50 |
|
| 51 | + /* |
| 52 | + * Check to see if it's safe to assign another XID. This protects |
| 53 | + * against catastrophic data loss due to XID wraparound. The basic |
| 54 | + * rules are: warn if we're past xidWarnLimit, and refuse to execute |
| 55 | + * transactions if we're past xidStopLimit, unless we are running in |
| 56 | + * a standalone backend (which gives an escape hatch to the DBA who |
| 57 | + * ignored all those warnings). |
| 58 | + * |
| 59 | + * Test is coded to fall out as fast as possible during normal operation, |
| 60 | + * ie, when the warn limit is set and we haven't violated it. |
| 61 | + */ |
| 62 | + if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidWarnLimit) && |
| 63 | + TransactionIdIsValid(ShmemVariableCache->xidWarnLimit)) |
| 64 | + { |
| 65 | + if (IsUnderPostmaster && |
| 66 | + TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidStopLimit)) |
| 67 | + ereport(ERROR, |
| 68 | + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), |
| 69 | + errmsg("database is shut down to avoid wraparound data loss in database \"%s\"", |
| 70 | + NameStr(ShmemVariableCache->limit_datname)), |
| 71 | + errhint("Stop the postmaster and use a standalone backend to VACUUM in \"%s\".", |
| 72 | + NameStr(ShmemVariableCache->limit_datname)))); |
| 73 | + else |
| 74 | + ereport(WARNING, |
| 75 | + (errmsg("database \"%s\" must be vacuumed within %u transactions", |
| 76 | + NameStr(ShmemVariableCache->limit_datname), |
| 77 | + ShmemVariableCache->xidWrapLimit - xid), |
| 78 | + errhint("To avoid a database shutdown, execute a full-database VACUUM in \"%s\".", |
| 79 | + NameStr(ShmemVariableCache->limit_datname)))); |
| 80 | + } |
| 81 | + |
49 | 82 | /*
|
50 | 83 | * If we are allocating the first XID of a new page of the commit log,
|
51 | 84 | * zero out that commit-log page before returning. We must do this
|
@@ -137,6 +170,84 @@ ReadNewTransactionId(void)
|
137 | 170 | return xid;
|
138 | 171 | }
|
139 | 172 |
|
| 173 | +/* |
| 174 | + * Determine the last safe XID to allocate given the currently oldest |
| 175 | + * datfrozenxid (ie, the oldest XID that might exist in any database |
| 176 | + * of our cluster). |
| 177 | + */ |
| 178 | +void |
| 179 | +SetTransactionIdLimit(TransactionId oldest_datfrozenxid, |
| 180 | + Name oldest_datname) |
| 181 | +{ |
| 182 | + TransactionId xidWarnLimit; |
| 183 | + TransactionId xidStopLimit; |
| 184 | + TransactionId xidWrapLimit; |
| 185 | + TransactionId curXid; |
| 186 | + |
| 187 | + Assert(TransactionIdIsValid(oldest_datfrozenxid)); |
| 188 | + |
| 189 | + /* |
| 190 | + * The place where we actually get into deep trouble is halfway around |
| 191 | + * from the oldest potentially-existing XID. (This calculation is |
| 192 | + * probably off by one or two counts, because the special XIDs reduce the |
| 193 | + * size of the loop a little bit. But we throw in plenty of slop below, |
| 194 | + * so it doesn't matter.) |
| 195 | + */ |
| 196 | + xidWrapLimit = oldest_datfrozenxid + (MaxTransactionId >> 1); |
| 197 | + if (xidWrapLimit < FirstNormalTransactionId) |
| 198 | + xidWrapLimit += FirstNormalTransactionId; |
| 199 | + |
| 200 | + /* |
| 201 | + * We'll refuse to continue assigning XIDs in interactive mode once |
| 202 | + * we get within 1M transactions of data loss. This leaves lots |
| 203 | + * of room for the DBA to fool around fixing things in a standalone |
| 204 | + * backend, while not being significant compared to total XID space. |
| 205 | + * (Note that since vacuuming requires one transaction per table |
| 206 | + * cleaned, we had better be sure there's lots of XIDs left...) |
| 207 | + */ |
| 208 | + xidStopLimit = xidWrapLimit - 1000000; |
| 209 | + if (xidStopLimit < FirstNormalTransactionId) |
| 210 | + xidStopLimit -= FirstNormalTransactionId; |
| 211 | + |
| 212 | + /* |
| 213 | + * We'll start complaining loudly when we get within 10M transactions |
| 214 | + * of the stop point. This is kind of arbitrary, but if you let your |
| 215 | + * gas gauge get down to 1% of full, would you be looking for the |
| 216 | + * next gas station? We need to be fairly liberal about this number |
| 217 | + * because there are lots of scenarios where most transactions are |
| 218 | + * done by automatic clients that won't pay attention to warnings. |
| 219 | + * (No, we're not gonna make this configurable. If you know enough to |
| 220 | + * configure it, you know enough to not get in this kind of trouble in |
| 221 | + * the first place.) |
| 222 | + */ |
| 223 | + xidWarnLimit = xidStopLimit - 10000000; |
| 224 | + if (xidWarnLimit < FirstNormalTransactionId) |
| 225 | + xidWarnLimit -= FirstNormalTransactionId; |
| 226 | + |
| 227 | + /* Grab lock for just long enough to set the new limit values */ |
| 228 | + LWLockAcquire(XidGenLock, LW_EXCLUSIVE); |
| 229 | + ShmemVariableCache->xidWarnLimit = xidWarnLimit; |
| 230 | + ShmemVariableCache->xidStopLimit = xidStopLimit; |
| 231 | + ShmemVariableCache->xidWrapLimit = xidWrapLimit; |
| 232 | + namecpy(&ShmemVariableCache->limit_datname, oldest_datname); |
| 233 | + curXid = ShmemVariableCache->nextXid; |
| 234 | + LWLockRelease(XidGenLock); |
| 235 | + |
| 236 | + /* Log the info */ |
| 237 | + ereport(LOG, |
| 238 | + (errmsg("transaction ID wrap limit is %u, limited by database \"%s\"", |
| 239 | + xidWrapLimit, NameStr(*oldest_datname)))); |
| 240 | + /* Give an immediate warning if past the wrap warn point */ |
| 241 | + if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit)) |
| 242 | + ereport(WARNING, |
| 243 | + (errmsg("database \"%s\" must be vacuumed within %u transactions", |
| 244 | + NameStr(*oldest_datname), |
| 245 | + xidWrapLimit - curXid), |
| 246 | + errhint("To avoid a database shutdown, execute a full-database VACUUM in \"%s\".", |
| 247 | + NameStr(*oldest_datname)))); |
| 248 | +} |
| 249 | + |
| 250 | + |
140 | 251 | /* ----------------------------------------------------------------
|
141 | 252 | * object id generation support
|
142 | 253 | * ----------------------------------------------------------------
|
|
0 commit comments