diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index af28a26b1a03edecf422137edf3d42d2ee4f90e8..1ade58644e9863edbde33d5b1efd29619c6e93df 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1,4 +1,4 @@ - + @@ -1241,6 +1241,20 @@ Custom vacuum_cost_limit parameter + + + freeze_min_age + integer + + Custom vacuum_freeze_min_age parameter + + + + freeze_max_age + integer + + Custom autovacuum_freeze_max_age parameter + @@ -1258,6 +1272,17 @@ live tuples currently estimated to be in the relation. + + Also, the autovacuum daemon will perform a VACUUM operation + to prevent transaction ID wraparound if the table's + pg_class.relfrozenxid field attains an age + of more than freeze_max_age transactions, whether the table + has been changed or not. The system will launch autovacuum to perform + such VACUUMs even if autovacuum is otherwise disabled. + See for more about wraparound + prevention. + + Any of the numerical fields can contain -1 (or indeed any negative value) to indicate that the system-wide default should @@ -1266,6 +1291,10 @@ autovacuum_vacuum_cost_delay configuration parameter, or from vacuum_cost_delay if the former is set to a negative value. The same applies to vac_cost_limit. + Also, autovacuum will ignore attempts to set a per-table + freeze_max_age larger than the system-wide setting (it can only be set + smaller), and the freeze_min_age value will be limited to half the + system-wide autovacuum_freeze_max_age setting. @@ -1633,26 +1662,15 @@ - relminxid + relfrozenxid xid - The minimum transaction ID present in all rows in this table. This - value is used to determine the database-global - pg_database.datminxid value. - - - - - relvacuumxid - xid - - - The transaction ID that was used as cleaning point as of the last vacuum - operation. All rows inserted, updated or deleted in this table by - transactions whose IDs are below this one have been marked as known good - or deleted. This is used to determine the database-global - pg_database.datvacuumxid value. + All transaction IDs before this one have been replaced with a permanent + (frozen) transaction ID in this table. This is used to track + whether the table needs to be vacuumed in order to prevent transaction + ID wraparound or to allow pg_clog to be shrunk. Zero + (InvalidTransactionId) if the relation is not a table. @@ -2035,31 +2053,16 @@ - datvacuumxid - xid - - - The transaction ID that was used as cleaning point as of the last vacuum - operation. All rows inserted or deleted by transaction IDs before this one - have been marked as known good or deleted. This - is used to determine when commit-log space can be recycled. - If InvalidTransactionId, then the minimum is unknown and can be - determined by scanning pg_class.relvacuumxid. - - - - - datminxid + datfrozenxid xid - The minimum transaction ID present in all tables in this database. - All rows inserted by transaction IDs before this one have been - relabeled with a permanent (frozen) transaction ID in this - database. This is useful to check whether a database must be - vacuumed soon to avoid transaction ID wrap-around problems. - If InvalidTransactionId, then the minimum is unknown and can be - determined by scanning pg_class.relminxid. + All transaction IDs before this one have been replaced with a permanent + (frozen) transaction ID in this database. This is used to + track whether the database needs to be vacuumed in order to prevent + transaction ID wraparound or to allow pg_clog to be shrunk. + It is the minimum of the per-table + pg_class.relfrozenxid values. diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 8f0058b7ee8c1fc754ba25ec3fb44760a218a590..3f62a2853417c4015bd9d73a6c8a555a06289f2c 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1,4 +1,4 @@ - + Server Configuration @@ -3217,6 +3217,28 @@ SELECT * FROM parent WHERE key = 2400; + + autovacuum_freeze_max_age (integer) + + autovacuum_freeze_max_age configuration parameter + + + + Specifies the maximum age (in transactions) that a table's + pg_class.relfrozenxid field can + attain before a VACUUM operation is forced to prevent + transaction ID wraparound within the table. Note that the system + will launch autovacuum processes to prevent wraparound even when + autovacuum is otherwise disabled. + The default is 200000000 (200 million). + This parameter can only be set at server start, but the setting + can be reduced for individual tables by entries in + pg_autovacuum. + For more information see . + + + + autovacuum_vacuum_cost_delay (integer) @@ -3427,7 +3449,7 @@ SELECT * FROM parent WHERE key = 2400; - + statement_timeout (integer) @@ -3444,6 +3466,26 @@ SELECT * FROM parent WHERE key = 2400; + + + vacuum_freeze_min_age (integer) + + vacuum_freeze_min_age configuration parameter + + + + Specifies the cutoff age (in transactions) that VACUUM + should use to decide whether to replace transaction IDs with + FrozenXID while scanning a table. + The default is 100000000 (100 million). Although users can set this + value anywhere from zero to 1000000000, VACUUM will + silently limit the effective value to half the value of , so that there is not an + unreasonably short time between forced autovacuums. + For more information see . + + + diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml index 6033cf2c5ed114b9ca5ddcff5c37fd1fee0fcf2d..4b321ca31bfc205a7b7f5996abc5099dcc6e7db3 100644 --- a/doc/src/sgml/maintenance.sgml +++ b/doc/src/sgml/maintenance.sgml @@ -1,4 +1,4 @@ - + Routine Database Maintenance Tasks @@ -34,7 +34,7 @@ The other main category of maintenance task is periodic vacuuming of the database. This activity is discussed in - . Closely related to this updating + . Closely related to this is updating the statistics that will be used by the query planner, as discussed in . @@ -95,9 +95,10 @@ will continue to function as normal, though you will not be able to modify the definition of a table with commands such as ALTER TABLE ADD COLUMN while it is being vacuumed. - Beginning in PostgreSQL 8.0, there are - configuration parameters that can be adjusted to further reduce the - performance impact of background vacuuming. See + Also, VACUUM requires a substantial amount of I/O + traffic, which can cause poor performance for other active sessions. + There are configuration parameters that can be adjusted to reduce the + performance impact of background vacuuming — see . @@ -179,9 +180,9 @@ Recommended practice for most sites is to schedule a database-wide VACUUM once a day at a low-usage time of day, supplemented by more frequent vacuuming of heavily-updated tables - if necessary. (Some installations with an extremely high - rate of data modification VACUUM busy tables as - often as once every few minutes.) If you have multiple databases + if necessary. (Some installations with extremely high update rates + vacuum their busiest tables as often as once every few minutes.) + If you have multiple databases in a cluster, don't forget to VACUUM each one; the program may be helpful. @@ -296,29 +297,15 @@ transactions that were in the past appear to be in the future — which means their outputs become invisible. In short, catastrophic data loss. (Actually the data is still there, but that's cold comfort if you can't - get at it.) To avoid this, it is necessary to vacuum every table - in every database at least once every billion transactions. + get at it.) To avoid this, it is necessary to vacuum every table + in every database at least once every two billion transactions. - In practice this isn't an onerous requirement, but since the - consequences of failing to meet it can be complete data loss (not - just wasted disk space or slow performance), some special provisions - have been made to help database administrators avoid disaster. - For each database in the cluster, PostgreSQL - keeps track of the time of the last database-wide VACUUM. - When any database approaches the billion-transaction danger level, - the system begins to emit warning messages. If nothing is done, it - will eventually shut down normal operations until appropriate - manual maintenance is done. The remainder of this - section gives the details. - - - - The new approach to XID comparison distinguishes two special XIDs, - numbers 1 and 2 (BootstrapXID and - FrozenXID). These two XIDs are always considered older - than every normal XID. Normal XIDs (those greater than 2) are + The reason that periodic vacuuming solves the problem is that + PostgreSQL distinguishes a special XID + FrozenXID. This XID is always considered older + than every normal XID. Normal XIDs are compared using modulo-231 arithmetic. This means that for every normal XID, there are two billion XIDs that are older and two billion that are newer; another @@ -333,78 +320,128 @@ two-billion-transactions-old mark. Once they are assigned this special XID, they will appear to be in the past to all normal transactions regardless of wraparound issues, and so such - row versions will be good until deleted, no matter how long that is. This - reassignment of XID is handled by VACUUM. + row versions will be good until deleted, no matter how long that is. + This reassignment of old XIDs is handled by VACUUM. + + + + VACUUM's behavior is controlled by the configuration parameter + : any XID older than + vacuum_freeze_min_age transactions is replaced by + FrozenXID. Larger values of vacuum_freeze_min_age + preserve transactional information longer, while smaller values increase + the number of transactions that can elapse before the table must be + vacuumed again. + + + + The maximum time that a table can go unvacuumed is two billion + transactions minus the vacuum_freeze_min_age that was used + when it was last vacuumed. + If it were to go unvacuumed for longer than that, + data loss could result. To ensure that this does not + happen, the autovacuum facility described in + is invoked on any table + that might contain XIDs older than the age specified by the + configuration parameter + . (This will happen + even if autovacuum is otherwise disabled.) + + + + This implies that if a table is not otherwise vacuumed, + autovacuum will be invoked on it approximately once every + autovacuum_freeze_max_age minus + vacuum_freeze_min_age transactions. + For tables that are regularly vacuumed for space reclamation purposes, + this is of little importance. However, for static tables + (including tables that receive inserts, but no updates or deletes), + there is no need for vacuuming for space reclamation, and so it can + be useful to try to maximize the interval between forced autovacuums + on very large static tables. Obviously one can do this either by + increasing autovacuum_freeze_max_age or by decreasing + vacuum_freeze_min_age. + + + + The sole disadvantage of increasing autovacuum_freeze_max_age + is that the pg_clog subdirectory of the database cluster + will take more space, because it must store the commit status for all + transactions back to the autovacuum_freeze_max_age horizon. + The commit status uses two bits per transaction, so if + autovacuum_freeze_max_age has its maximum allowed value of + a little less than two billion, pg_clog can be expected to + grow to about half a gigabyte. If this is trivial compared to your + total database size, setting autovacuum_freeze_max_age to + its maximum allowed value is recommended. Otherwise, set it depending + on what you are willing to allow for pg_clog storage. + (The default, 200 million transactions, translates to about 50MB of + pg_clog storage.) - VACUUM's normal policy is to reassign FrozenXID - to any row version with a normal XID more than one billion transactions in the - past. This policy preserves the original insertion XID until it is not - likely to be of interest anymore. (In fact, most row versions will probably - live and die without ever being frozen.) With this policy, - the maximum safe interval between VACUUM runs on any table - is exactly one billion transactions: if you wait longer, it's possible - that a row version that was not quite old enough to be reassigned last time - is now more than two billion transactions old and has wrapped around - into the future — i.e., is lost to you. (Of course, it'll reappear - after another two billion transactions, but that's no help.) + One disadvantage of decreasing vacuum_freeze_min_age is that + it may cause VACUUM to do useless work: changing a table row's + XID to FrozenXID is a waste of time if the row is modified + soon thereafter (causing it to acquire a new XID). So the setting should + be large enough that rows are not frozen until they are unlikely to change + any more. Another disadvantage of decreasing this setting is + that details about exactly which transaction inserted or modified a + row will be lost sooner. This information sometimes comes in handy, + particularly when trying to analyze what went wrong after a database + failure. For these two reasons, decreasing this setting is not + recommended except for completely static tables. - Since periodic VACUUM runs are needed anyway for the reasons - described earlier, it's unlikely that any table would not be vacuumed - for as long as a billion transactions. But to help administrators ensure - this constraint is met, VACUUM stores transaction ID - statistics in the system table pg_database. In particular, - the datfrozenxid column of a database's - pg_database row is updated at the completion of any - database-wide VACUUM operation (i.e., - VACUUM that does not - name a specific table). The value stored in this field is the freeze - cutoff XID that was used by that VACUUM command. All normal + To track the age of the oldest XIDs in a database, + VACUUM stores XID + statistics in the system tables pg_class and + pg_database. In particular, + the relfrozenxid column of a table's + pg_class row contains the freeze cutoff XID that was used + by the last VACUUM for that table. All normal XIDs older than this cutoff XID are guaranteed to have been replaced by - FrozenXID within that database. A convenient way to - examine this information is to execute the query + FrozenXID within the table. Similarly, + the datfrozenxid column of a database's + pg_database row is a lower bound on the normal XIDs + appearing in that database — it is just the minimum of the + per-table relfrozenxid values within the database. + A convenient way to + examine this information is to execute queries such as +SELECT relname, age(relfrozenxid) FROM pg_class WHERE relkind = 'r'; SELECT datname, age(datfrozenxid) FROM pg_database; The age column measures the number of transactions from the - cutoff XID to the current transaction's XID. + cutoff XID to the current transaction's XID. Immediately after a + VACUUM, age(relfrozenxid) should be a little + more than the vacuum_freeze_min_age setting that was used + (more by the number of transactions started since the VACUUM + started). If age(relfrozenxid) exceeds + autovacuum_freeze_max_age, an autovacuum will soon be forced + for the table. - With the standard freezing policy, the age column will start - at one billion for a freshly-vacuumed database. When the age - approaches two billion, the database must be vacuumed again to avoid - risk of wraparound failures. Recommended practice is to VACUUM each - database at least once every half-a-billion (500 million) transactions, - so as to provide plenty of safety margin. To help meet this rule, - each database-wide VACUUM automatically delivers a warning - if there are any pg_database entries showing an - age of more than 1.5 billion transactions, for example: + If for some reason autovacuum fails to clear old XIDs from a table, + the system will begin to emit warning messages like this when the + database's oldest XIDs reach ten million transactions from the wraparound + point: -play=# VACUUM; WARNING: database "mydb" must be vacuumed within 177009986 transactions HINT: To avoid a database shutdown, execute a full-database VACUUM in "mydb". -VACUUM - - - If the warnings emitted by VACUUM go ignored, then - PostgreSQL will begin to emit a warning - like the above on every transaction start once there are fewer than 10 - million transactions left until wraparound. If those warnings also are + If these warnings are ignored, the system will shut down and refuse to execute any new transactions once there are fewer than 1 million transactions left until wraparound: -play=# select 2+2; ERROR: database is shut down to avoid wraparound data loss in database "mydb" HINT: Stop the postmaster and use a standalone backend to VACUUM in "mydb". @@ -419,32 +456,6 @@ HINT: Stop the postmaster and use a standalone backend to VACUUM in "mydb". page for details about using a single-user backend. - - VACUUM with the FREEZE option uses a more - aggressive freezing policy: row versions are frozen if they are old enough - to be considered good by all open transactions. In particular, if a - VACUUM FREEZE is performed in an otherwise-idle - database, it is guaranteed that all row versions in that - database will be frozen. Hence, as long as the database is not - modified in any way, it will not need subsequent vacuuming to avoid - transaction ID wraparound problems. This technique is used by - initdb to prepare the template0 database. - It should also be used to prepare any user-created databases that - are to be marked datallowconn = false in - pg_database, since there isn't any convenient way to - VACUUM a database that you can't connect to. - - - - - A database that is marked datallowconn = false - in pg_database is assumed to be properly frozen; the - automatic warnings and wraparound protection shutdown do not take - such databases into account. Therefore it's up to you to ensure - you've correctly frozen a database before you mark it with - datallowconn = false. - - @@ -471,19 +482,17 @@ HINT: Stop the postmaster and use a standalone backend to VACUUM in "mydb". The autovacuum daemon, when enabled, runs every seconds and determines which database - to process. Any database which is close to transaction ID wraparound - is immediately processed. In this case, autovacuum issues a - database-wide VACUUM call, or VACUUM - FREEZE if it's a template database, and then terminates. If - no database fulfills this criterion, the one that was least recently - processed by autovacuum is chosen. In this case each table in - the selected database is checked, and individual VACUUM - or ANALYZE commands are issued as needed. + linkend="guc-autovacuum-naptime"> seconds. On each run, it selects + one database to process and checks each table within that database. + VACUUM or ANALYZE commands are + issued as needed. - For each table, two conditions are used to determine which operation(s) + Tables whose relfrozenxid value is more than + autovacuum_freeze_max_age transactions old are always + vacuumed. Otherwise, + two conditions are used to determine which operation(s) to apply. If the number of obsolete tuples since the last VACUUM exceeds the vacuum threshold, the table is vacuumed. The vacuum threshold is defined as: @@ -521,21 +530,28 @@ analyze threshold = analyze base threshold + analyze scale factor * number of tu - Besides the base threshold values and scale factors, there are three + Besides the base threshold values and scale factors, there are five more parameters that can be set for each table in pg_autovacuum. The first, pg_autovacuum.enabled, can be set to false to instruct the autovacuum daemon to skip that particular table entirely. In this case - autovacuum will only touch the table when it vacuums the entire database + autovacuum will only touch the table if it must do so to prevent transaction ID wraparound. - The other two parameters, the vacuum cost delay + The next two parameters, the vacuum cost delay (pg_autovacuum.vac_cost_delay) and the vacuum cost limit (pg_autovacuum.vac_cost_limit), are used to set table-specific values for the feature. + The last two parameters, + (pg_autovacuum.freeze_min_age) + and + (pg_autovacuum.freeze_max_age), + are used to set table-specific values for + and + respectively. diff --git a/doc/src/sgml/manage-ag.sgml b/doc/src/sgml/manage-ag.sgml index fcc6e81b7b1a7f3da638a7399c85b9308c24f44b..e86cac91e4d1a376acd80f718b82f423bab6f8ca 100644 --- a/doc/src/sgml/manage-ag.sgml +++ b/doc/src/sgml/manage-ag.sgml @@ -1,4 +1,4 @@ - + Managing Databases @@ -249,19 +249,6 @@ createdb -T template0 dbname should always be marked with datistemplate = true. - - After preparing a template database, or making any changes to one, - it is a good idea to perform VACUUM FREEZE in that - database. If this is done when there are no other open transactions - in the same database, then it is guaranteed that all rows in the - database are frozen and will not be subject to transaction - ID wraparound problems. This is particularly important for a database - that will have datallowconn set to false, since it - will be impossible to do routine maintenance VACUUM in - such a database. - See for more information. - - template1 and template0 do not have any special diff --git a/doc/src/sgml/ref/vacuum.sgml b/doc/src/sgml/ref/vacuum.sgml index 0764aa6807972d12aba1d8cdc1c6ecd57fd6c2f2..cf039113f644938e492fd8a176367fdb83036ab0 100644 --- a/doc/src/sgml/ref/vacuum.sgml +++ b/doc/src/sgml/ref/vacuum.sgml @@ -1,5 +1,5 @@ @@ -20,8 +20,8 @@ PostgreSQL documentation -VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ table ] -VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ] +VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ table ] +VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ table [ (column [, ...] ) ] ] @@ -62,21 +62,6 @@ VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ ta blocks. This form is much slower and requires an exclusive lock on each table while it is being processed. - - - FREEZE is a special-purpose option that - causes tuples to be marked frozen as soon as possible, - rather than waiting until they are quite old. If this is done when there - are no other open transactions in the same database, then it is guaranteed - that all tuples in the database are frozen and will not be - subject to transaction ID wraparound problems, no matter how long the - database is left unvacuumed. - FREEZE is not recommended for routine use. Its only - intended usage is in connection with preparation of user-defined template - databases, or other databases that are completely read-only and will not - receive routine maintenance VACUUM operations. - See for details. - @@ -98,6 +83,11 @@ VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ ta Selects aggressive freezing of tuples. + Specifying FREEZE is equivalent to performing + VACUUM with the + parameter + set to zero. The FREEZE option is deprecated and + will be removed in a future release; set the parameter instead. @@ -185,6 +175,13 @@ VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ ta it is sometimes advisable to use the cost-based vacuum delay feature. See for details. + + + PostgreSQL includes an autovacuum + facility which can automate routine vacuum maintenance. For more + information about automatic and manual vacuuming, see + . + diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 57acaf2bb8c896b8b69ac4d95c6baf88bf26bfd0..12775cc2db73fe89f020ab39cae6c1c9ec307bf4 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.220 2006/10/04 00:29:48 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.221 2006/11/05 22:42:07 tgl Exp $ * * * INTERFACE ROUTINES @@ -2809,6 +2809,166 @@ heap_inplace_update(Relation relation, HeapTuple tuple) } +/* + * heap_freeze_tuple + * + * Check to see whether any of the XID fields of a tuple (xmin, xmax, xvac) + * are older than the specified cutoff XID. If so, replace them with + * FrozenTransactionId or InvalidTransactionId as appropriate, and return + * TRUE. Return FALSE if nothing was changed. + * + * It is assumed that the caller has checked the tuple with + * HeapTupleSatisfiesVacuum() and determined that it is not HEAPTUPLE_DEAD + * (else we should be removing the tuple, not freezing it). + * + * NB: cutoff_xid *must* be <= the current global xmin, to ensure that any + * XID older than it could neither be running nor seen as running by any + * open transaction. This ensures that the replacement will not change + * anyone's idea of the tuple state. Also, since we assume the tuple is + * not HEAPTUPLE_DEAD, the fact that an XID is not still running allows us + * to assume that it is either committed good or aborted, as appropriate; + * so we need no external state checks to decide what to do. (This is good + * because this function is applied during WAL recovery, when we don't have + * access to any such state, and can't depend on the hint bits to be set.) + * + * In lazy VACUUM, we call this while initially holding only a shared lock + * on the tuple's buffer. If any change is needed, we trade that in for an + * exclusive lock before making the change. Caller should pass the buffer ID + * if shared lock is held, InvalidBuffer if exclusive lock is already held. + * + * Note: it might seem we could make the changes without exclusive lock, since + * TransactionId read/write is assumed atomic anyway. However there is a race + * condition: someone who just fetched an old XID that we overwrite here could + * conceivably not finish checking the XID against pg_clog before we finish + * the VACUUM and perhaps truncate off the part of pg_clog he needs. Getting + * exclusive lock ensures no other backend is in process of checking the + * tuple status. Also, getting exclusive lock makes it safe to adjust the + * infomask bits. + */ +bool +heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid, + Buffer buf) +{ + bool changed = false; + TransactionId xid; + + xid = HeapTupleHeaderGetXmin(tuple); + if (TransactionIdIsNormal(xid) && + TransactionIdPrecedes(xid, cutoff_xid)) + { + if (buf != InvalidBuffer) + { + /* trade in share lock for exclusive lock */ + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + buf = InvalidBuffer; + } + HeapTupleHeaderSetXmin(tuple, FrozenTransactionId); + /* + * Might as well fix the hint bits too; usually XMIN_COMMITTED will + * already be set here, but there's a small chance not. + */ + Assert(!(tuple->t_infomask & HEAP_XMIN_INVALID)); + tuple->t_infomask |= HEAP_XMIN_COMMITTED; + changed = true; + } + + /* + * When we release shared lock, it's possible for someone else to change + * xmax before we get the lock back, so repeat the check after acquiring + * exclusive lock. (We don't need this pushup for xmin, because only + * VACUUM could be interested in changing an existing tuple's xmin, + * and there's only one VACUUM allowed on a table at a time.) + */ +recheck_xmax: + if (!(tuple->t_infomask & HEAP_XMAX_IS_MULTI)) + { + xid = HeapTupleHeaderGetXmax(tuple); + if (TransactionIdIsNormal(xid) && + TransactionIdPrecedes(xid, cutoff_xid)) + { + if (buf != InvalidBuffer) + { + /* trade in share lock for exclusive lock */ + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + buf = InvalidBuffer; + goto recheck_xmax; /* see comment above */ + } + HeapTupleHeaderSetXmax(tuple, InvalidTransactionId); + /* + * The tuple might be marked either XMAX_INVALID or + * XMAX_COMMITTED + LOCKED. Normalize to INVALID just to be + * sure no one gets confused. + */ + tuple->t_infomask &= ~HEAP_XMAX_COMMITTED; + tuple->t_infomask |= HEAP_XMAX_INVALID; + changed = true; + } + } + else + { + /*---------- + * XXX perhaps someday we should zero out very old MultiXactIds here? + * + * The only way a stale MultiXactId could pose a problem is if a + * tuple, having once been multiply-share-locked, is not touched by + * any vacuum or attempted lock or deletion for just over 4G MultiXact + * creations, and then in the probably-narrow window where its xmax + * is again a live MultiXactId, someone tries to lock or delete it. + * Even then, another share-lock attempt would work fine. An + * exclusive-lock or delete attempt would face unexpected delay, or + * in the very worst case get a deadlock error. This seems an + * extremely low-probability scenario with minimal downside even if + * it does happen, so for now we don't do the extra bookkeeping that + * would be needed to clean out MultiXactIds. + *---------- + */ + } + + /* + * Although xvac per se could only be set by VACUUM, it shares physical + * storage space with cmax, and so could be wiped out by someone setting + * xmax. Hence recheck after changing lock, same as for xmax itself. + */ +recheck_xvac: + if (tuple->t_infomask & HEAP_MOVED) + { + xid = HeapTupleHeaderGetXvac(tuple); + if (TransactionIdIsNormal(xid) && + TransactionIdPrecedes(xid, cutoff_xid)) + { + if (buf != InvalidBuffer) + { + /* trade in share lock for exclusive lock */ + LockBuffer(buf, BUFFER_LOCK_UNLOCK); + LockBuffer(buf, BUFFER_LOCK_EXCLUSIVE); + buf = InvalidBuffer; + goto recheck_xvac; /* see comment above */ + } + /* + * If a MOVED_OFF tuple is not dead, the xvac transaction must + * have failed; whereas a non-dead MOVED_IN tuple must mean the + * xvac transaction succeeded. + */ + if (tuple->t_infomask & HEAP_MOVED_OFF) + HeapTupleHeaderSetXvac(tuple, InvalidTransactionId); + else + HeapTupleHeaderSetXvac(tuple, FrozenTransactionId); + /* + * Might as well fix the hint bits too; usually XMIN_COMMITTED will + * already be set here, but there's a small chance not. + */ + Assert(!(tuple->t_infomask & HEAP_XMIN_INVALID)); + tuple->t_infomask |= HEAP_XMIN_COMMITTED; + changed = true; + } + } + + return changed; +} + + /* ---------------- * heap_markpos - mark scan position * ---------------- @@ -2877,6 +3037,9 @@ heap_restrpos(HeapScanDesc scan) /* * Perform XLogInsert for a heap-clean operation. Caller must already * have modified the buffer and marked it dirty. + * + * Note: for historical reasons, the entries in the unused[] array should + * be zero-based tuple indexes, not one-based. */ XLogRecPtr log_heap_clean(Relation reln, Buffer buffer, OffsetNumber *unused, int uncnt) @@ -2920,6 +3083,57 @@ log_heap_clean(Relation reln, Buffer buffer, OffsetNumber *unused, int uncnt) return recptr; } +/* + * Perform XLogInsert for a heap-freeze operation. Caller must already + * have modified the buffer and marked it dirty. + * + * Unlike log_heap_clean(), the offsets[] entries are one-based. + */ +XLogRecPtr +log_heap_freeze(Relation reln, Buffer buffer, + TransactionId cutoff_xid, + OffsetNumber *offsets, int offcnt) +{ + xl_heap_freeze xlrec; + XLogRecPtr recptr; + XLogRecData rdata[2]; + + /* Caller should not call me on a temp relation */ + Assert(!reln->rd_istemp); + + xlrec.node = reln->rd_node; + xlrec.block = BufferGetBlockNumber(buffer); + xlrec.cutoff_xid = cutoff_xid; + + rdata[0].data = (char *) &xlrec; + rdata[0].len = SizeOfHeapFreeze; + rdata[0].buffer = InvalidBuffer; + rdata[0].next = &(rdata[1]); + + /* + * The tuple-offsets array is not actually in the buffer, but pretend + * that it is. When XLogInsert stores the whole buffer, the offsets array + * need not be stored too. + */ + if (offcnt > 0) + { + rdata[1].data = (char *) offsets; + rdata[1].len = offcnt * sizeof(OffsetNumber); + } + else + { + rdata[1].data = NULL; + rdata[1].len = 0; + } + rdata[1].buffer = buffer; + rdata[1].buffer_std = true; + rdata[1].next = NULL; + + recptr = XLogInsert(RM_HEAP2_ID, XLOG_HEAP2_FREEZE, rdata); + + return recptr; +} + /* * Perform XLogInsert for a heap-update operation. Caller must already * have modified the buffer(s) and marked them dirty. @@ -3057,6 +3271,7 @@ heap_xlog_clean(XLogRecPtr lsn, XLogRecord *record) while (unused < unend) { + /* unused[] entries are zero-based */ lp = PageGetItemId(page, *unused + 1); lp->lp_flags &= ~LP_USED; unused++; @@ -3071,6 +3286,55 @@ heap_xlog_clean(XLogRecPtr lsn, XLogRecord *record) UnlockReleaseBuffer(buffer); } +static void +heap_xlog_freeze(XLogRecPtr lsn, XLogRecord *record) +{ + xl_heap_freeze *xlrec = (xl_heap_freeze *) XLogRecGetData(record); + TransactionId cutoff_xid = xlrec->cutoff_xid; + Relation reln; + Buffer buffer; + Page page; + + if (record->xl_info & XLR_BKP_BLOCK_1) + return; + + reln = XLogOpenRelation(xlrec->node); + buffer = XLogReadBuffer(reln, xlrec->block, false); + if (!BufferIsValid(buffer)) + return; + page = (Page) BufferGetPage(buffer); + + if (XLByteLE(lsn, PageGetLSN(page))) + { + UnlockReleaseBuffer(buffer); + return; + } + + if (record->xl_len > SizeOfHeapFreeze) + { + OffsetNumber *offsets; + OffsetNumber *offsets_end; + + offsets = (OffsetNumber *) ((char *) xlrec + SizeOfHeapFreeze); + offsets_end = (OffsetNumber *) ((char *) xlrec + record->xl_len); + + while (offsets < offsets_end) + { + /* offsets[] entries are one-based */ + ItemId lp = PageGetItemId(page, *offsets); + HeapTupleHeader tuple = (HeapTupleHeader) PageGetItem(page, lp); + + (void) heap_freeze_tuple(tuple, cutoff_xid, InvalidBuffer); + offsets++; + } + } + + PageSetLSN(page, lsn); + PageSetTLI(page, ThisTimeLineID); + MarkBufferDirty(buffer); + UnlockReleaseBuffer(buffer); +} + static void heap_xlog_newpage(XLogRecPtr lsn, XLogRecord *record) { @@ -3546,6 +3810,18 @@ heap_redo(XLogRecPtr lsn, XLogRecord *record) elog(PANIC, "heap_redo: unknown op code %u", info); } +void +heap2_redo(XLogRecPtr lsn, XLogRecord *record) +{ + uint8 info = record->xl_info & ~XLR_INFO_MASK; + + info &= XLOG_HEAP_OPMASK; + if (info == XLOG_HEAP2_FREEZE) + heap_xlog_freeze(lsn, record); + else + elog(PANIC, "heap2_redo: unknown op code %u", info); +} + static void out_target(StringInfo buf, xl_heaptid *target) { @@ -3645,3 +3921,22 @@ heap_desc(StringInfo buf, uint8 xl_info, char *rec) else appendStringInfo(buf, "UNKNOWN"); } + +void +heap2_desc(StringInfo buf, uint8 xl_info, char *rec) +{ + uint8 info = xl_info & ~XLR_INFO_MASK; + + info &= XLOG_HEAP_OPMASK; + if (info == XLOG_HEAP2_FREEZE) + { + xl_heap_freeze *xlrec = (xl_heap_freeze *) rec; + + appendStringInfo(buf, "freeze: rel %u/%u/%u; blk %u; cutoff %u", + xlrec->node.spcNode, xlrec->node.dbNode, + xlrec->node.relNode, xlrec->block, + xlrec->cutoff_xid); + } + else + appendStringInfo(buf, "UNKNOWN"); +} diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c index f57bdefa3a0d3a97bea89ffc5993a0dad355dce2..5817239a374481cbea5848c5a85ae7d027aabb7d 100644 --- a/src/backend/access/transam/clog.c +++ b/src/backend/access/transam/clog.c @@ -24,7 +24,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/access/transam/clog.c,v 1.40 2006/10/04 00:29:49 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/clog.c,v 1.41 2006/11/05 22:42:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -69,6 +69,7 @@ static SlruCtlData ClogCtlData; static int ZeroCLOGPage(int pageno, bool writeXlog); static bool CLOGPagePrecedes(int page1, int page2); static void WriteZeroPageXlogRec(int pageno); +static void WriteTruncateXlogRec(int pageno); /* @@ -309,16 +310,17 @@ ExtendCLOG(TransactionId newestXact) /* * Remove all CLOG segments before the one holding the passed transaction ID * - * When this is called, we know that the database logically contains no - * reference to transaction IDs older than oldestXact. However, we must - * not truncate the CLOG until we have performed a checkpoint, to ensure - * that no such references remain on disk either; else a crash just after - * the truncation might leave us with a problem. Since CLOG segments hold - * a large number of transactions, the opportunity to actually remove a - * segment is fairly rare, and so it seems best not to do the checkpoint - * unless we have confirmed that there is a removable segment. Therefore - * we issue the checkpoint command here, not in higher-level code as might - * seem cleaner. + * Before removing any CLOG data, we must flush XLOG to disk, to ensure + * that any recently-emitted HEAP_FREEZE records have reached disk; otherwise + * a crash and restart might leave us with some unfrozen tuples referencing + * removed CLOG data. We choose to emit a special TRUNCATE XLOG record too. + * Replaying the deletion from XLOG is not critical, since the files could + * just as well be removed later, but doing so prevents a long-running hot + * standby server from acquiring an unreasonably bloated CLOG directory. + * + * Since CLOG segments hold a large number of transactions, the opportunity to + * actually remove a segment is fairly rare, and so it seems best not to do + * the XLOG flush unless we have confirmed that there is a removable segment. */ void TruncateCLOG(TransactionId oldestXact) @@ -335,8 +337,8 @@ TruncateCLOG(TransactionId oldestXact) if (!SlruScanDirectory(ClogCtl, cutoffPage, false)) return; /* nothing to remove */ - /* Perform a CHECKPOINT */ - RequestCheckpoint(true, false); + /* Write XLOG record and flush XLOG to disk */ + WriteTruncateXlogRec(cutoffPage); /* Now we can remove the old CLOG segment(s) */ SimpleLruTruncate(ClogCtl, cutoffPage); @@ -386,6 +388,29 @@ WriteZeroPageXlogRec(int pageno) (void) XLogInsert(RM_CLOG_ID, CLOG_ZEROPAGE | XLOG_NO_TRAN, &rdata); } +/* + * Write a TRUNCATE xlog record + * + * We must flush the xlog record to disk before returning --- see notes + * in TruncateCLOG(). + * + * Note: xlog record is marked as outside transaction control, since we + * want it to be redone whether the invoking transaction commits or not. + */ +static void +WriteTruncateXlogRec(int pageno) +{ + XLogRecData rdata; + XLogRecPtr recptr; + + rdata.data = (char *) (&pageno); + rdata.len = sizeof(int); + rdata.buffer = InvalidBuffer; + rdata.next = NULL; + recptr = XLogInsert(RM_CLOG_ID, CLOG_TRUNCATE | XLOG_NO_TRAN, &rdata); + XLogFlush(recptr); +} + /* * CLOG resource manager's routines */ @@ -409,6 +434,22 @@ clog_redo(XLogRecPtr lsn, XLogRecord *record) LWLockRelease(CLogControlLock); } + else if (info == CLOG_TRUNCATE) + { + int pageno; + + memcpy(&pageno, XLogRecGetData(record), sizeof(int)); + + /* + * During XLOG replay, latest_page_number isn't set up yet; insert + * a suitable value to bypass the sanity test in SimpleLruTruncate. + */ + ClogCtl->shared->latest_page_number = pageno; + + SimpleLruTruncate(ClogCtl, pageno); + } + else + elog(PANIC, "clog_redo: unknown op code %u", info); } void @@ -423,6 +464,13 @@ clog_desc(StringInfo buf, uint8 xl_info, char *rec) memcpy(&pageno, rec, sizeof(int)); appendStringInfo(buf, "zeropage: %d", pageno); } + else if (info == CLOG_TRUNCATE) + { + int pageno; + + memcpy(&pageno, rec, sizeof(int)); + appendStringInfo(buf, "truncate before: %d", pageno); + } else appendStringInfo(buf, "UNKNOWN"); } diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c index 7db05c9c482bac4906fed9a2686b325a2e1b6015..08de22eaa4abdfa1365af2ce6c71d15d2c01f766 100644 --- a/src/backend/access/transam/rmgr.c +++ b/src/backend/access/transam/rmgr.c @@ -3,7 +3,7 @@ * * Resource managers definition * - * $PostgreSQL: pgsql/src/backend/access/transam/rmgr.c,v 1.24 2006/08/07 16:57:56 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/rmgr.c,v 1.25 2006/11/05 22:42:07 tgl Exp $ */ #include "postgres.h" @@ -32,7 +32,7 @@ const RmgrData RmgrTable[RM_MAX_ID + 1] = { {"MultiXact", multixact_redo, multixact_desc, NULL, NULL, NULL}, {"Reserved 7", NULL, NULL, NULL, NULL, NULL}, {"Reserved 8", NULL, NULL, NULL, NULL, NULL}, - {"Reserved 9", NULL, NULL, NULL, NULL, NULL}, + {"Heap2", heap2_redo, heap2_desc, NULL, NULL, NULL}, {"Heap", heap_redo, heap_desc, NULL, NULL, NULL}, {"Btree", btree_redo, btree_desc, btree_xlog_startup, btree_xlog_cleanup, btree_safe_restartpoint}, {"Hash", hash_redo, hash_desc, NULL, NULL, NULL}, diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index 04e9840cb5d9060e3db444c6939ca01514b98756..a8abaaea35cf0721bc4f0594f79333dae388b38f 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -6,7 +6,7 @@ * Copyright (c) 2000-2006, PostgreSQL Global Development Group * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.75 2006/10/04 00:29:49 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.76 2006/11/05 22:42:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -17,6 +17,8 @@ #include "access/subtrans.h" #include "access/transam.h" #include "miscadmin.h" +#include "postmaster/autovacuum.h" +#include "storage/pmsignal.h" #include "storage/proc.h" #include "utils/builtins.h" @@ -47,20 +49,31 @@ GetNewTransactionId(bool isSubXact) xid = ShmemVariableCache->nextXid; - /* + /*---------- * Check to see if it's safe to assign another XID. This protects against * catastrophic data loss due to XID wraparound. The basic rules are: - * warn if we're past xidWarnLimit, and refuse to execute transactions if - * we're past xidStopLimit, unless we are running in a standalone backend - * (which gives an escape hatch to the DBA who ignored all those - * warnings). + * + * If we're past xidVacLimit, start trying to force autovacuum cycles. + * If we're past xidWarnLimit, start issuing warnings. + * If we're past xidStopLimit, refuse to execute transactions, unless + * we are running in a standalone backend (which gives an escape hatch + * to the DBA who somehow got past the earlier defenses). * * Test is coded to fall out as fast as possible during normal operation, - * ie, when the warn limit is set and we haven't violated it. + * ie, when the vac limit is set and we haven't violated it. + *---------- */ - if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidWarnLimit) && - TransactionIdIsValid(ShmemVariableCache->xidWarnLimit)) + if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidVacLimit) && + TransactionIdIsValid(ShmemVariableCache->xidVacLimit)) { + /* + * To avoid swamping the postmaster with signals, we issue the + * autovac request only once per 64K transaction starts. This + * still gives plenty of chances before we get into real trouble. + */ + if (IsUnderPostmaster && (xid % 65536) == 0) + SendPostmasterSignal(PMSIGNAL_START_AUTOVAC); + if (IsUnderPostmaster && TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidStopLimit)) ereport(ERROR, @@ -69,7 +82,7 @@ GetNewTransactionId(bool isSubXact) NameStr(ShmemVariableCache->limit_datname)), errhint("Stop the postmaster and use a standalone backend to vacuum database \"%s\".", NameStr(ShmemVariableCache->limit_datname)))); - else + else if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidWarnLimit)) ereport(WARNING, (errmsg("database \"%s\" must be vacuumed within %u transactions", NameStr(ShmemVariableCache->limit_datname), @@ -178,28 +191,29 @@ ReadNewTransactionId(void) /* * Determine the last safe XID to allocate given the currently oldest - * datminxid (ie, the oldest XID that might exist in any database + * datfrozenxid (ie, the oldest XID that might exist in any database * of our cluster). */ void -SetTransactionIdLimit(TransactionId oldest_datminxid, +SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Name oldest_datname) { + TransactionId xidVacLimit; TransactionId xidWarnLimit; TransactionId xidStopLimit; TransactionId xidWrapLimit; TransactionId curXid; - Assert(TransactionIdIsValid(oldest_datminxid)); + Assert(TransactionIdIsNormal(oldest_datfrozenxid)); /* * The place where we actually get into deep trouble is halfway around - * from the oldest existing XID. (This calculation is probably off by one - * or two counts, because the special XIDs reduce the size of the loop a - * little bit. But we throw in plenty of slop below, so it doesn't - * matter.) + * from the oldest potentially-existing XID. (This calculation is + * probably off by one or two counts, because the special XIDs reduce the + * size of the loop a little bit. But we throw in plenty of slop below, + * so it doesn't matter.) */ - xidWrapLimit = oldest_datminxid + (MaxTransactionId >> 1); + xidWrapLimit = oldest_datfrozenxid + (MaxTransactionId >> 1); if (xidWrapLimit < FirstNormalTransactionId) xidWrapLimit += FirstNormalTransactionId; @@ -229,8 +243,28 @@ SetTransactionIdLimit(TransactionId oldest_datminxid, if (xidWarnLimit < FirstNormalTransactionId) xidWarnLimit -= FirstNormalTransactionId; + /* + * We'll start trying to force autovacuums when oldest_datfrozenxid + * gets to be more than autovacuum_freeze_max_age transactions old. + * + * Note: guc.c ensures that autovacuum_freeze_max_age is in a sane + * range, so that xidVacLimit will be well before xidWarnLimit. + * + * Note: autovacuum_freeze_max_age is a PGC_POSTMASTER parameter so that + * we don't have to worry about dealing with on-the-fly changes in its + * value. It doesn't look practical to update shared state from a GUC + * assign hook (too many processes would try to execute the hook, + * resulting in race conditions as well as crashes of those not + * connected to shared memory). Perhaps this can be improved someday. + */ + xidVacLimit = oldest_datfrozenxid + autovacuum_freeze_max_age; + if (xidVacLimit < FirstNormalTransactionId) + xidVacLimit += FirstNormalTransactionId; + /* Grab lock for just long enough to set the new limit values */ LWLockAcquire(XidGenLock, LW_EXCLUSIVE); + ShmemVariableCache->oldestXid = oldest_datfrozenxid; + ShmemVariableCache->xidVacLimit = xidVacLimit; ShmemVariableCache->xidWarnLimit = xidWarnLimit; ShmemVariableCache->xidStopLimit = xidStopLimit; ShmemVariableCache->xidWrapLimit = xidWrapLimit; @@ -242,6 +276,18 @@ SetTransactionIdLimit(TransactionId oldest_datminxid, ereport(DEBUG1, (errmsg("transaction ID wrap limit is %u, limited by database \"%s\"", xidWrapLimit, NameStr(*oldest_datname)))); + + /* + * If past the autovacuum force point, immediately signal an autovac + * request. The reason for this is that autovac only processes one + * database per invocation. Once it's finished cleaning up the oldest + * database, it'll call here, and we'll signal the postmaster to start + * another iteration immediately if there are still any old databases. + */ + if (TransactionIdFollowsOrEquals(curXid, xidVacLimit) && + IsUnderPostmaster) + SendPostmasterSignal(PMSIGNAL_START_AUTOVAC); + /* Give an immediate warning if past the wrap warn point */ if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit)) ereport(WARNING, diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 8e1724989cb4d2038c4bea29aba638637f6d4b23..3c6e2ebf5cdc9bff3bb8dd49583130d9ee65d2bf 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.227 2006/10/04 00:29:49 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.228 2006/11/05 22:42:07 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -468,8 +468,12 @@ TransactionIdIsCurrentTransactionId(TransactionId xid) * is what we need during bootstrap. (Bootstrap mode only inserts tuples, * it never updates or deletes them, so all tuples can be presumed good * immediately.) + * + * Likewise, InvalidTransactionId and FrozenTransactionId are certainly + * not my transaction ID, so we can just return "false" immediately for + * any non-normal XID. */ - if (xid == BootstrapTransactionId) + if (!TransactionIdIsNormal(xid)) return false; /* diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index ba029397f871b4ca4508d94825e71daf9ea0f0a8..03440cbf48ba92b240b2a61587859c8a9db7c1c8 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/access/transam/xlog.c,v 1.252 2006/10/18 22:44:11 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/xlog.c,v 1.253 2006/11/05 22:42:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -5343,36 +5343,6 @@ GetLastSegSwitchTime(void) return result; } -/* - * GetRecentNextXid - get the nextXid value saved by the most recent checkpoint - * - * This is currently used only by the autovacuum daemon. To check for - * impending XID wraparound, autovac needs an approximate idea of the current - * XID counter, and it needs it before choosing which DB to attach to, hence - * before it sets up a PGPROC, hence before it can take any LWLocks. But it - * has attached to shared memory, and so we can let it reach into the shared - * ControlFile structure and pull out the last checkpoint nextXID. - * - * Since we don't take any sort of lock, we have to assume that reading a - * TransactionId is atomic ... but that assumption is made elsewhere, too, - * and in any case the worst possible consequence of a bogus result is that - * autovac issues an unnecessary database-wide VACUUM. - * - * Note: we could also choose to read ShmemVariableCache->nextXid in an - * unlocked fashion, thus getting a more up-to-date result; but since that - * changes far more frequently than the controlfile checkpoint copy, it would - * pose a far higher risk of bogus result if we did have a nonatomic-read - * problem. - * - * A (theoretically) completely safe answer is to read the actual pg_control - * file into local process memory, but that certainly seems like overkill. - */ -TransactionId -GetRecentNextXid(void) -{ - return ControlFile->checkPointCopy.nextXid; -} - /* * GetNextXidAndEpoch - get the current nextXid value and associated epoch * diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index d30556d48c15d0b71fc2a4aca70a1836a263ed63..d6822c73c698fc4af7c529f6e0188247e36c50c4 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.313 2006/10/04 00:29:50 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/catalog/heap.c,v 1.314 2006/11/05 22:42:08 tgl Exp $ * * * INTERFACE ROUTINES @@ -595,8 +595,7 @@ InsertPgClassTuple(Relation pg_class_desc, values[Anum_pg_class_relhaspkey - 1] = BoolGetDatum(rd_rel->relhaspkey); values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules); values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass); - values[Anum_pg_class_relminxid - 1] = TransactionIdGetDatum(rd_rel->relminxid); - values[Anum_pg_class_relvacuumxid - 1] = TransactionIdGetDatum(rd_rel->relvacuumxid); + values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid); /* start out with empty permissions */ nulls[Anum_pg_class_relacl - 1] = 'n'; if (reloptions != (Datum) 0) @@ -644,35 +643,6 @@ AddNewRelationTuple(Relation pg_class_desc, */ new_rel_reltup = new_rel_desc->rd_rel; - /* Initialize relminxid and relvacuumxid */ - if (relkind == RELKIND_RELATION || - relkind == RELKIND_TOASTVALUE) - { - /* - * Only real tables have Xids stored in them; initialize our known - * value to the minimum Xid that could put tuples in the new table. - */ - if (!IsBootstrapProcessingMode()) - { - new_rel_reltup->relminxid = RecentXmin; - new_rel_reltup->relvacuumxid = RecentXmin; - } - else - { - new_rel_reltup->relminxid = FirstNormalTransactionId; - new_rel_reltup->relvacuumxid = FirstNormalTransactionId; - } - } - else - { - /* - * Other relations will not have Xids in them, so set the initial - * value to InvalidTransactionId. - */ - new_rel_reltup->relminxid = InvalidTransactionId; - new_rel_reltup->relvacuumxid = InvalidTransactionId; - } - switch (relkind) { case RELKIND_RELATION: @@ -694,6 +664,31 @@ AddNewRelationTuple(Relation pg_class_desc, break; } + /* Initialize relfrozenxid */ + if (relkind == RELKIND_RELATION || + relkind == RELKIND_TOASTVALUE) + { + /* + * Initialize to the minimum XID that could put tuples in the table. + * We know that no xacts older than RecentXmin are still running, + * so that will do. + */ + if (!IsBootstrapProcessingMode()) + new_rel_reltup->relfrozenxid = RecentXmin; + else + new_rel_reltup->relfrozenxid = FirstNormalTransactionId; + } + else + { + /* + * Other relation types will not contain XIDs, so set relfrozenxid + * to InvalidTransactionId. (Note: a sequence does contain a tuple, + * but we force its xmin to be FrozenTransactionId always; see + * commands/sequence.c.) + */ + new_rel_reltup->relfrozenxid = InvalidTransactionId; + } + new_rel_reltup->relowner = relowner; new_rel_reltup->reltype = new_type_oid; new_rel_reltup->relkind = relkind; diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index cb538bdd42b52d62bfa320c1c6ca2f54791ad934..95410258d34ec07b85afb15c385c348b03690fe6 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.100 2006/10/05 17:57:40 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/analyze.c,v 1.101 2006/11/05 22:42:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -421,7 +421,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt) vac_update_relstats(RelationGetRelid(onerel), RelationGetNumberOfBlocks(onerel), totalrows, hasindex, - InvalidTransactionId, InvalidTransactionId); + InvalidTransactionId); for (ind = 0; ind < nindexes; ind++) { @@ -432,7 +432,7 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt) vac_update_relstats(RelationGetRelid(Irel[ind]), RelationGetNumberOfBlocks(Irel[ind]), totalindexrows, false, - InvalidTransactionId, InvalidTransactionId); + InvalidTransactionId); } /* report results to the stats collector, too */ diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c index cd327bb8d15a6120cdb3839efd1fd1e7096f05c5..566d559c78a95a6b8e564bd05b3b51772c28d4d2 100644 --- a/src/backend/commands/dbcommands.c +++ b/src/backend/commands/dbcommands.c @@ -13,7 +13,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.186 2006/10/18 22:44:12 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/commands/dbcommands.c,v 1.187 2006/11/05 22:42:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,8 +53,7 @@ static bool get_db_info(const char *name, LOCKMODE lockmode, Oid *dbIdP, Oid *ownerIdP, int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP, - Oid *dbLastSysOidP, - TransactionId *dbVacuumXidP, TransactionId *dbMinXidP, + Oid *dbLastSysOidP, TransactionId *dbFrozenXidP, Oid *dbTablespace); static bool have_createdb_privilege(void); static void remove_dbtablespaces(Oid db_id); @@ -75,8 +74,7 @@ createdb(const CreatedbStmt *stmt) bool src_istemplate; bool src_allowconn; Oid src_lastsysoid; - TransactionId src_vacuumxid; - TransactionId src_minxid; + TransactionId src_frozenxid; Oid src_deftablespace; volatile Oid dst_deftablespace; Relation pg_database_rel; @@ -228,7 +226,7 @@ createdb(const CreatedbStmt *stmt) if (!get_db_info(dbtemplate, ShareLock, &src_dboid, &src_owner, &src_encoding, &src_istemplate, &src_allowconn, &src_lastsysoid, - &src_vacuumxid, &src_minxid, &src_deftablespace)) + &src_frozenxid, &src_deftablespace)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("template database \"%s\" does not exist", @@ -366,8 +364,7 @@ createdb(const CreatedbStmt *stmt) new_record[Anum_pg_database_datallowconn - 1] = BoolGetDatum(true); new_record[Anum_pg_database_datconnlimit - 1] = Int32GetDatum(dbconnlimit); new_record[Anum_pg_database_datlastsysoid - 1] = ObjectIdGetDatum(src_lastsysoid); - new_record[Anum_pg_database_datvacuumxid - 1] = TransactionIdGetDatum(src_vacuumxid); - new_record[Anum_pg_database_datminxid - 1] = TransactionIdGetDatum(src_minxid); + new_record[Anum_pg_database_datfrozenxid - 1] = TransactionIdGetDatum(src_frozenxid); new_record[Anum_pg_database_dattablespace - 1] = ObjectIdGetDatum(dst_deftablespace); /* @@ -565,7 +562,7 @@ dropdb(const char *dbname, bool missing_ok) pgdbrel = heap_open(DatabaseRelationId, RowExclusiveLock); if (!get_db_info(dbname, AccessExclusiveLock, &db_id, NULL, NULL, - &db_istemplate, NULL, NULL, NULL, NULL, NULL)) + &db_istemplate, NULL, NULL, NULL, NULL)) { if (!missing_ok) { @@ -689,7 +686,7 @@ RenameDatabase(const char *oldname, const char *newname) rel = heap_open(DatabaseRelationId, RowExclusiveLock); if (!get_db_info(oldname, AccessExclusiveLock, &db_id, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL)) + NULL, NULL, NULL, NULL, NULL)) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%s\" does not exist", oldname))); @@ -1067,8 +1064,7 @@ static bool get_db_info(const char *name, LOCKMODE lockmode, Oid *dbIdP, Oid *ownerIdP, int *encodingP, bool *dbIsTemplateP, bool *dbAllowConnP, - Oid *dbLastSysOidP, - TransactionId *dbVacuumXidP, TransactionId *dbMinXidP, + Oid *dbLastSysOidP, TransactionId *dbFrozenXidP, Oid *dbTablespace) { bool result = false; @@ -1154,12 +1150,9 @@ get_db_info(const char *name, LOCKMODE lockmode, /* last system OID used in database */ if (dbLastSysOidP) *dbLastSysOidP = dbform->datlastsysoid; - /* limit of vacuumed XIDs */ - if (dbVacuumXidP) - *dbVacuumXidP = dbform->datvacuumxid; - /* limit of min XIDs */ - if (dbMinXidP) - *dbMinXidP = dbform->datminxid; + /* limit of frozen XIDs */ + if (dbFrozenXidP) + *dbFrozenXidP = dbform->datfrozenxid; /* default tablespace for this database */ if (dbTablespace) *dbTablespace = dbform->dattablespace; diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index e9f0bf363ea9109ae9fb2db3d8b5b4a3c708fe0d..b15fcc1059f8c21692282abfadc004a4ed749bad 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -13,7 +13,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.341 2006/10/04 00:29:51 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/vacuum.c,v 1.342 2006/11/05 22:42:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -25,7 +25,6 @@ #include "access/clog.h" #include "access/genam.h" #include "access/heapam.h" -#include "access/multixact.h" #include "access/transam.h" #include "access/xact.h" #include "catalog/namespace.h" @@ -36,7 +35,6 @@ #include "miscadmin.h" #include "postmaster/autovacuum.h" #include "storage/freespace.h" -#include "storage/pmsignal.h" #include "storage/proc.h" #include "storage/procarray.h" #include "utils/acl.h" @@ -52,6 +50,11 @@ #include "pgstat.h" +/* + * GUC parameters + */ +int vacuum_freeze_min_age; + /* * VacPage structures keep track of each page on which we find useful * amounts of free space. @@ -125,7 +128,6 @@ typedef struct VRelStats Size min_tlen; Size max_tlen; bool hasindex; - TransactionId minxid; /* Minimum Xid present anywhere on table */ /* vtlinks array for tuple chain following - sorted by new_tid */ int num_vtlinks; VTupleLink vtlinks; @@ -193,22 +195,21 @@ static MemoryContext vac_context = NULL; static int elevel = -1; +static TransactionId OldestXmin; +static TransactionId FreezeLimit; + /* non-export function prototypes */ static List *get_rel_oids(List *relids, const RangeVar *vacrel, const char *stmttype); -static void vac_update_dbminxid(Oid dbid, - TransactionId *minxid, - TransactionId *vacuumxid); -static void vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid); +static void vac_truncate_clog(TransactionId frozenXID); static void vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind); static void full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt); static void scan_heap(VRelStats *vacrelstats, Relation onerel, - VacPageList vacuum_pages, VacPageList fraged_pages, - TransactionId FreezeLimit, TransactionId OldestXmin); + VacPageList vacuum_pages, VacPageList fraged_pages); static void repair_frag(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages, VacPageList fraged_pages, - int nindexes, Relation *Irel, TransactionId OldestXmin); + int nindexes, Relation *Irel); static void move_chain_tuple(Relation rel, Buffer old_buf, Page old_page, HeapTuple old_tup, Buffer dst_buf, Page dst_page, VacPage dst_vacpage, @@ -298,27 +299,6 @@ vacuum(VacuumStmt *vacstmt, List *relids) else in_outer_xact = IsInTransactionChain((void *) vacstmt); - /* - * Disallow the combination VACUUM FULL FREEZE; although it would mostly - * work, VACUUM FULL's ability to move tuples around means that it is - * injecting its own XID into tuple visibility checks. We'd have to - * guarantee that every moved tuple is properly marked XMIN_COMMITTED or - * XMIN_INVALID before the end of the operation. There are corner cases - * where this does not happen, and getting rid of them all seems hard (not - * to mention fragile to maintain). On the whole it's not worth it - * compared to telling people to use two operations. See pgsql-hackers - * discussion of 27-Nov-2004, and comments below for update_hint_bits(). - * - * Note: this is enforced here, and not in the grammar, since (a) we can - * give a better error message, and (b) we might want to allow it again - * someday. - */ - if (vacstmt->vacuum && vacstmt->full && vacstmt->freeze) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("VACUUM FULL FREEZE is not supported"), - errhint("Use VACUUM FULL, then VACUUM FREEZE."))); - /* * Send info about dead objects to the statistics collector, unless we are * in autovacuum --- autovacuum.c does this for itself. @@ -492,23 +472,21 @@ vacuum(VacuumStmt *vacstmt, List *relids) ActiveSnapshot = CopySnapshot(GetTransactionSnapshot()); } - if (vacstmt->vacuum) + if (vacstmt->vacuum && !IsAutoVacuumProcess()) { - TransactionId minxid, - vacuumxid; + /* + * Update pg_database.datfrozenxid, and truncate pg_clog if possible. + * (autovacuum.c does this for itself.) + */ + vac_update_datfrozenxid(); /* * If it was a database-wide VACUUM, print FSM usage statistics (we - * don't make you be superuser to see these). + * don't make you be superuser to see these). We suppress this in + * autovacuum, too. */ if (all_rels) PrintFreeSpaceMapStatistics(elevel); - - /* Update pg_database.datminxid and datvacuumxid */ - vac_update_dbminxid(MyDatabaseId, &minxid, &vacuumxid); - - /* Try to truncate pg_clog. */ - vac_truncate_clog(minxid, vacuumxid); } /* @@ -591,12 +569,14 @@ vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel, TransactionId *oldestXmin, TransactionId *freezeLimit) { + int freezemin; TransactionId limit; + TransactionId safeLimit; /* * We can always ignore processes running lazy vacuum. This is because we * use these values only for deciding which tuples we must keep in the - * tables. Since lazy vacuum doesn't write its xid to the table, it's + * tables. Since lazy vacuum doesn't write its XID anywhere, it's * safe to ignore it. In theory it could be problematic to ignore lazy * vacuums on a full vacuum, but keep in mind that only one vacuum process * can be working on a particular table at any time, and that each vacuum @@ -606,30 +586,35 @@ vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel, Assert(TransactionIdIsNormal(*oldestXmin)); - if (vacstmt->freeze) - { - /* FREEZE option: use oldest Xmin as freeze cutoff too */ - limit = *oldestXmin; - } - else - { - /* - * Normal case: freeze cutoff is well in the past, to wit, about - * halfway to the wrap horizon - */ - limit = GetCurrentTransactionId() - (MaxTransactionId >> 2); - } + /* + * Determine the minimum freeze age to use: as specified in the vacstmt, + * or vacuum_freeze_min_age, but in any case not more than half + * autovacuum_freeze_max_age, so that autovacuums to prevent XID + * wraparound won't occur too frequently. + */ + freezemin = vacstmt->freeze_min_age; + if (freezemin < 0) + freezemin = vacuum_freeze_min_age; + freezemin = Min(freezemin, autovacuum_freeze_max_age / 2); + Assert(freezemin >= 0); /* - * Be careful not to generate a "permanent" XID + * Compute the cutoff XID, being careful not to generate a "permanent" XID */ + limit = *oldestXmin - freezemin; if (!TransactionIdIsNormal(limit)) limit = FirstNormalTransactionId; /* - * Ensure sane relationship of limits + * If oldestXmin is very far back (in practice, more than + * autovacuum_freeze_max_age / 2 XIDs old), complain and force a + * minimum freeze age of zero. */ - if (TransactionIdFollows(limit, *oldestXmin)) + safeLimit = ReadNewTransactionId() - autovacuum_freeze_max_age; + if (!TransactionIdIsNormal(safeLimit)) + safeLimit = FirstNormalTransactionId; + + if (TransactionIdPrecedes(limit, safeLimit)) { ereport(WARNING, (errmsg("oldest xmin is far in the past"), @@ -668,8 +653,7 @@ vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel, */ void vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples, - bool hasindex, TransactionId minxid, - TransactionId vacuumxid) + bool hasindex, TransactionId frozenxid) { Relation rd; HeapTuple ctup; @@ -718,14 +702,15 @@ vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples, dirty = true; } } - if (TransactionIdIsValid(minxid) && pgcform->relminxid != minxid) - { - pgcform->relminxid = minxid; - dirty = true; - } - if (TransactionIdIsValid(vacuumxid) && pgcform->relvacuumxid != vacuumxid) + + /* + * relfrozenxid should never go backward. Caller can pass + * InvalidTransactionId if it has no new data. + */ + if (TransactionIdIsNormal(frozenxid) && + TransactionIdPrecedes(pgcform->relfrozenxid, frozenxid)) { - pgcform->relvacuumxid = vacuumxid; + pgcform->relfrozenxid = frozenxid; dirty = true; } @@ -740,34 +725,41 @@ vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples, /* - * vac_update_dbminxid() -- update the minimum Xid present in one database + * vac_update_datfrozenxid() -- update pg_database.datfrozenxid for our DB * - * Update pg_database's datminxid and datvacuumxid, and the flat-file copy - * of it. datminxid is updated to the minimum of all relminxid found in - * pg_class. datvacuumxid is updated to the minimum of all relvacuumxid - * found in pg_class. The values are also returned in minxid and - * vacuumxid, respectively. + * Update pg_database's datfrozenxid entry for our database to be the + * minimum of the pg_class.relfrozenxid values. If we are able to + * advance pg_database.datfrozenxid, also try to truncate pg_clog. * * We violate transaction semantics here by overwriting the database's - * existing pg_database tuple with the new values. This is reasonably - * safe since the new values are correct whether or not this transaction + * existing pg_database tuple with the new value. This is reasonably + * safe since the new value is correct whether or not this transaction * commits. As with vac_update_relstats, this avoids leaving dead tuples * behind after a VACUUM. * * This routine is shared by full and lazy VACUUM. */ -static void -vac_update_dbminxid(Oid dbid, TransactionId *minxid, TransactionId *vacuumxid) +void +vac_update_datfrozenxid(void) { HeapTuple tuple; Form_pg_database dbform; Relation relation; SysScanDesc scan; HeapTuple classTup; - TransactionId newMinXid = InvalidTransactionId; - TransactionId newVacXid = InvalidTransactionId; + TransactionId newFrozenXid; bool dirty = false; + /* + * Initialize the "min" calculation with RecentGlobalXmin. Any + * not-yet-committed pg_class entries for new tables must have + * relfrozenxid at least this high, because any other open xact must have + * RecentXmin >= its PGPROC.xmin >= our RecentGlobalXmin; see + * AddNewRelationTuple(). So we cannot produce a wrong minimum by + * starting with this. + */ + newFrozenXid = RecentGlobalXmin; + /* * We must seqscan pg_class to find the minimum Xid, because there is no * index that can help us here. @@ -779,60 +771,46 @@ vac_update_dbminxid(Oid dbid, TransactionId *minxid, TransactionId *vacuumxid) while ((classTup = systable_getnext(scan)) != NULL) { - Form_pg_class classForm; - - classForm = (Form_pg_class) GETSTRUCT(classTup); + Form_pg_class classForm = (Form_pg_class) GETSTRUCT(classTup); /* * Only consider heap and TOAST tables (anything else should have - * InvalidTransactionId in both fields anyway.) + * InvalidTransactionId in relfrozenxid anyway.) */ if (classForm->relkind != RELKIND_RELATION && classForm->relkind != RELKIND_TOASTVALUE) continue; - Assert(TransactionIdIsNormal(classForm->relminxid)); - Assert(TransactionIdIsNormal(classForm->relvacuumxid)); + Assert(TransactionIdIsNormal(classForm->relfrozenxid)); - /* - * Compute the minimum relminxid in all the tables in the database. - */ - if ((!TransactionIdIsValid(newMinXid) || - TransactionIdPrecedes(classForm->relminxid, newMinXid))) - newMinXid = classForm->relminxid; - - /* ditto, for relvacuumxid */ - if ((!TransactionIdIsValid(newVacXid) || - TransactionIdPrecedes(classForm->relvacuumxid, newVacXid))) - newVacXid = classForm->relvacuumxid; + if (TransactionIdPrecedes(classForm->relfrozenxid, newFrozenXid)) + newFrozenXid = classForm->relfrozenxid; } /* we're done with pg_class */ systable_endscan(scan); heap_close(relation, AccessShareLock); - Assert(TransactionIdIsNormal(newMinXid)); - Assert(TransactionIdIsNormal(newVacXid)); + Assert(TransactionIdIsNormal(newFrozenXid)); /* Now fetch the pg_database tuple we need to update. */ relation = heap_open(DatabaseRelationId, RowExclusiveLock); /* Fetch a copy of the tuple to scribble on */ tuple = SearchSysCacheCopy(DATABASEOID, - ObjectIdGetDatum(dbid), + ObjectIdGetDatum(MyDatabaseId), 0, 0, 0); if (!HeapTupleIsValid(tuple)) - elog(ERROR, "could not find tuple for database %u", dbid); + elog(ERROR, "could not find tuple for database %u", MyDatabaseId); dbform = (Form_pg_database) GETSTRUCT(tuple); - if (TransactionIdPrecedes(dbform->datminxid, newMinXid)) - { - dbform->datminxid = newMinXid; - dirty = true; - } - if (TransactionIdPrecedes(dbform->datvacuumxid, newVacXid)) + /* + * Don't allow datfrozenxid to go backward (probably can't happen anyway); + * and detect the common case where it doesn't go forward either. + */ + if (TransactionIdPrecedes(dbform->datfrozenxid, newFrozenXid)) { - dbform->datvacuumxid = newVacXid; + dbform->datfrozenxid = newFrozenXid; dirty = true; } @@ -842,56 +820,57 @@ vac_update_dbminxid(Oid dbid, TransactionId *minxid, TransactionId *vacuumxid) heap_freetuple(tuple); heap_close(relation, RowExclusiveLock); - /* set return values */ - *minxid = newMinXid; - *vacuumxid = newVacXid; - - /* Mark the flat-file copy of pg_database for update at commit */ - database_file_update_needed(); + /* + * If we were able to advance datfrozenxid, mark the flat-file copy of + * pg_database for update at commit, and see if we can truncate + * pg_clog. + */ + if (dirty) + { + database_file_update_needed(); + vac_truncate_clog(newFrozenXid); + } } /* * vac_truncate_clog() -- attempt to truncate the commit log * - * Scan pg_database to determine the system-wide oldest datvacuumxid, + * Scan pg_database to determine the system-wide oldest datfrozenxid, * and use it to truncate the transaction commit log (pg_clog). - * Also update the XID wrap limit point maintained by varsup.c. - * - * We also generate a warning if the system-wide oldest datfrozenxid - * seems to be in danger of wrapping around. This is a long-in-advance - * warning; if we start getting uncomfortably close, GetNewTransactionId - * will generate more-annoying warnings, and ultimately refuse to issue - * any more new XIDs. + * Also update the XID wrap limit info maintained by varsup.c. * - * The passed XIDs are simply the ones I just wrote into my pg_database - * entry. They're used to initialize the "min" calculations. + * The passed XID is simply the one I just wrote into my pg_database + * entry. It's used to initialize the "min" calculation. * - * This routine is shared by full and lazy VACUUM. Note that it is only - * applied after a database-wide VACUUM operation. + * This routine is shared by full and lazy VACUUM. Note that it's + * only invoked when we've managed to change our DB's datfrozenxid + * entry. */ static void -vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid) +vac_truncate_clog(TransactionId frozenXID) { TransactionId myXID = GetCurrentTransactionId(); - TransactionId minXID; - TransactionId vacuumXID; Relation relation; HeapScanDesc scan; HeapTuple tuple; - int32 age; NameData oldest_datname; - bool vacuumAlreadyWrapped = false; - bool minAlreadyWrapped = false; + bool frozenAlreadyWrapped = false; - /* Initialize the minimum values. */ - minXID = myminxid; - vacuumXID = myvacxid; + /* init oldest_datname to sync with my frozenXID */ namestrcpy(&oldest_datname, get_database_name(MyDatabaseId)); /* - * Note: the "already wrapped" cases should now be impossible due to the - * defenses in GetNewTransactionId, but we keep them anyway. + * Scan pg_database to compute the minimum datfrozenxid + * + * Note: we need not worry about a race condition with new entries being + * inserted by CREATE DATABASE. Any such entry will have a copy of some + * existing DB's datfrozenxid, and that source DB cannot be ours because + * of the interlock against copying a DB containing an active backend. + * Hence the new entry will not reduce the minimum. Also, if two + * VACUUMs concurrently modify the datfrozenxid's of different databases, + * the worst possible outcome is that pg_clog is not truncated as + * aggressively as it could be. */ relation = heap_open(DatabaseRelationId, AccessShareLock); @@ -901,19 +880,13 @@ vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid) { Form_pg_database dbform = (Form_pg_database) GETSTRUCT(tuple); - Assert(TransactionIdIsNormal(dbform->datvacuumxid)); - Assert(TransactionIdIsNormal(dbform->datminxid)); - - if (TransactionIdPrecedes(myXID, dbform->datvacuumxid)) - vacuumAlreadyWrapped = true; - else if (TransactionIdPrecedes(dbform->datvacuumxid, vacuumXID)) - vacuumXID = dbform->datvacuumxid; + Assert(TransactionIdIsNormal(dbform->datfrozenxid)); - if (TransactionIdPrecedes(myXID, dbform->datminxid)) - minAlreadyWrapped = true; - else if (TransactionIdPrecedes(dbform->datminxid, minXID)) + if (TransactionIdPrecedes(myXID, dbform->datfrozenxid)) + frozenAlreadyWrapped = true; + else if (TransactionIdPrecedes(dbform->datfrozenxid, frozenXID)) { - minXID = dbform->datminxid; + frozenXID = dbform->datfrozenxid; namecpy(&oldest_datname, &dbform->datname); } } @@ -924,9 +897,11 @@ vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid) /* * Do not truncate CLOG if we seem to have suffered wraparound already; - * the computed minimum XID might be bogus. + * the computed minimum XID might be bogus. This case should now be + * impossible due to the defenses in GetNewTransactionId, but we keep the + * test anyway. */ - if (vacuumAlreadyWrapped) + if (frozenAlreadyWrapped) { ereport(WARNING, (errmsg("some databases have not been vacuumed in over 2 billion transactions"), @@ -934,55 +909,14 @@ vac_truncate_clog(TransactionId myminxid, TransactionId myvacxid) return; } - /* Truncate CLOG to the oldest vacuumxid */ - TruncateCLOG(vacuumXID); + /* Truncate CLOG to the oldest frozenxid */ + TruncateCLOG(frozenXID); /* - * Do not update varsup.c if we seem to have suffered wraparound already; - * the computed XID might be bogus. + * Update the wrap limit for GetNewTransactionId. Note: this function + * will also signal the postmaster for an(other) autovac cycle if needed. */ - if (minAlreadyWrapped) - { - ereport(WARNING, - (errmsg("some databases have not been vacuumed in over 1 billion transactions"), - errhint("Better vacuum them soon, or you may have a wraparound failure."))); - return; - } - - /* Update the wrap limit for GetNewTransactionId */ - SetTransactionIdLimit(minXID, &oldest_datname); - - /* Give warning about impending wraparound problems */ - age = (int32) (myXID - minXID); - if (age > (int32) ((MaxTransactionId >> 3) * 3)) - ereport(WARNING, - (errmsg("database \"%s\" must be vacuumed within %u transactions", - NameStr(oldest_datname), - (MaxTransactionId >> 1) - age), - errhint("To avoid a database shutdown, execute a full-database VACUUM in \"%s\".", - NameStr(oldest_datname)))); - - /* - * Have the postmaster start an autovacuum iteration. If the user has - * autovacuum configured, this is not needed; otherwise, we need to make - * sure we have some mechanism to cope with transaction Id wraparound. - * Ideally this would only be needed for template databases, because all - * other databases should be kept nicely pruned by regular vacuuming. - * - * XXX -- the test we use here is fairly arbitrary. Note that in the - * autovacuum database-wide code, a template database is always processed - * with VACUUM FREEZE, so we can be sure that it will be truly frozen so - * it won't be need to be processed here again soon. - * - * FIXME -- here we could get into a kind of loop if the database being - * chosen is not actually a template database, because we'll not freeze - * it, so its age may not really decrease if there are any live - * non-freezable tuples. Consider forcing a vacuum freeze if autovacuum - * is invoked by a backend. On the other hand, forcing a vacuum freeze on - * a user database may not a be a very polite thing to do. - */ - if (!AutoVacuumingActive() && age > (int32) ((MaxTransactionId >> 3) * 3)) - SendPostmasterSignal(PMSIGNAL_START_AUTOVAC); + SetTransactionIdLimit(frozenXID, &oldest_datname); } @@ -1118,7 +1052,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, char expected_relkind) { relation_close(onerel, lmode); CommitTransactionCommand(); - return; /* assume no long-lived data in temp tables */ + return; } /* @@ -1208,8 +1142,6 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) int nindexes, i; VRelStats *vacrelstats; - TransactionId FreezeLimit, - OldestXmin; vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared, &OldestXmin, &FreezeLimit); @@ -1222,21 +1154,9 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) vacrelstats->rel_tuples = 0; vacrelstats->hasindex = false; - /* - * Set initial minimum Xid, which will be updated if a smaller Xid is - * found in the relation by scan_heap. - * - * We use RecentXmin here (the minimum Xid that belongs to a transaction - * that is still open according to our snapshot), because it is the - * earliest transaction that could insert new tuples in the table after - * our VACUUM is done. - */ - vacrelstats->minxid = RecentXmin; - /* scan the heap */ vacuum_pages.num_pages = fraged_pages.num_pages = 0; - scan_heap(vacrelstats, onerel, &vacuum_pages, &fraged_pages, FreezeLimit, - OldestXmin); + scan_heap(vacrelstats, onerel, &vacuum_pages, &fraged_pages); /* Now open all indexes of the relation */ vac_open_indexes(onerel, AccessExclusiveLock, &nindexes, &Irel); @@ -1264,7 +1184,7 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) { /* Try to shrink heap */ repair_frag(vacrelstats, onerel, &vacuum_pages, &fraged_pages, - nindexes, Irel, OldestXmin); + nindexes, Irel); vac_close_indexes(nindexes, Irel, NoLock); } else @@ -1283,7 +1203,7 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) /* update statistics in pg_class */ vac_update_relstats(RelationGetRelid(onerel), vacrelstats->rel_pages, vacrelstats->rel_tuples, vacrelstats->hasindex, - vacrelstats->minxid, OldestXmin); + FreezeLimit); /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, @@ -1299,14 +1219,10 @@ full_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) * deleted tuples), constructs fraged_pages (list of pages with free * space that tuples could be moved into), and calculates statistics * on the number of live tuples in the heap. - * - * It also updates the minimum Xid found anywhere on the table in - * vacrelstats->minxid, for later storing it in pg_class.relminxid. */ static void scan_heap(VRelStats *vacrelstats, Relation onerel, - VacPageList vacuum_pages, VacPageList fraged_pages, - TransactionId FreezeLimit, TransactionId OldestXmin) + VacPageList vacuum_pages, VacPageList fraged_pages) { BlockNumber nblocks, blkno; @@ -1357,8 +1273,9 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, Buffer buf; OffsetNumber offnum, maxoff; - bool pgchanged, - notup; + bool notup; + OffsetNumber frozen[MaxOffsetNumber]; + int nfrozen; vacuum_delay_point(); @@ -1414,7 +1331,7 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, continue; } - pgchanged = false; + nfrozen = 0; notup = true; maxoff = PageGetMaxOffsetNumber(page); for (offnum = FirstOffsetNumber; @@ -1446,24 +1363,7 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, tupgone = true; /* we can delete the tuple */ break; case HEAPTUPLE_LIVE: - - /* - * Tuple is good. Consider whether to replace its xmin - * value with FrozenTransactionId. - */ - if (TransactionIdIsNormal(HeapTupleHeaderGetXmin(tuple.t_data)) && - TransactionIdPrecedes(HeapTupleHeaderGetXmin(tuple.t_data), - FreezeLimit)) - { - HeapTupleHeaderSetXmin(tuple.t_data, FrozenTransactionId); - /* infomask should be okay already */ - Assert(tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED); - pgchanged = true; - } - - /* - * Other checks... - */ + /* Tuple is good --- but let's do some validity checks */ if (onerel->rd_rel->relhasoids && !OidIsValid(HeapTupleGetOid(&tuple))) elog(WARNING, "relation \"%s\" TID %u/%u: OID is invalid", @@ -1559,8 +1459,6 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, } else { - TransactionId min; - num_tuples += 1; notup = false; if (tuple.t_len < min_tlen) @@ -1569,13 +1467,12 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, max_tlen = tuple.t_len; /* - * If the tuple is alive, we consider it for the "minxid" - * calculations. + * Each non-removable tuple must be checked to see if it + * needs freezing. */ - min = vactuple_get_minxid(&tuple); - if (TransactionIdIsValid(min) && - TransactionIdPrecedes(min, vacrelstats->minxid)) - vacrelstats->minxid = min; + if (heap_freeze_tuple(tuple.t_data, FreezeLimit, + InvalidBuffer)) + frozen[nfrozen++] = offnum; } } /* scan along page */ @@ -1627,8 +1524,26 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, else empty_end_pages = 0; - if (pgchanged) + /* + * If we froze any tuples, mark the buffer dirty, and write a WAL + * record recording the changes. We must log the changes to be + * crash-safe against future truncation of CLOG. + */ + if (nfrozen > 0) + { MarkBufferDirty(buf); + /* no XLOG for temp tables, though */ + if (!onerel->rd_istemp) + { + XLogRecPtr recptr; + + recptr = log_heap_freeze(onerel, buf, FreezeLimit, + frozen, nfrozen); + PageSetLSN(page, recptr); + PageSetTLI(page, ThisTimeLineID); + } + } + UnlockReleaseBuffer(buf); } @@ -1701,63 +1616,6 @@ scan_heap(VRelStats *vacrelstats, Relation onerel, pg_rusage_show(&ru0)))); } -/* - * vactuple_get_minxid - * - * Get the minimum relevant Xid for a tuple, not considering FrozenXid. - * Return InvalidXid if none (i.e., xmin=FrozenXid, xmax=InvalidXid). - * This is for the purpose of calculating pg_class.relminxid for a table - * we're vacuuming. - */ -TransactionId -vactuple_get_minxid(HeapTuple tuple) -{ - TransactionId min = InvalidTransactionId; - - /* - * Initialize calculations with Xmin. NB -- may be FrozenXid and we don't - * want that one. - */ - if (TransactionIdIsNormal(HeapTupleHeaderGetXmin(tuple->t_data))) - min = HeapTupleHeaderGetXmin(tuple->t_data); - - /* - * If Xmax is not marked INVALID, we assume it's valid without making - * further checks on it --- it must be recently obsoleted or still - * running, else HeapTupleSatisfiesVacuum would have deemed it removable. - */ - if (!(tuple->t_data->t_infomask | HEAP_XMAX_INVALID)) - { - TransactionId xmax = HeapTupleHeaderGetXmax(tuple->t_data); - - /* If xmax is a plain Xid, consider it by itself */ - if (!(tuple->t_data->t_infomask | HEAP_XMAX_IS_MULTI)) - { - if (!TransactionIdIsValid(min) || - (TransactionIdIsNormal(xmax) && - TransactionIdPrecedes(xmax, min))) - min = xmax; - } - else - { - /* If it's a MultiXactId, consider each of its members */ - TransactionId *members; - int nmembers, - membno; - - nmembers = GetMultiXactIdMembers(xmax, &members); - - for (membno = 0; membno < nmembers; membno++) - { - if (!TransactionIdIsValid(min) || - TransactionIdPrecedes(members[membno], min)) - min = members[membno]; - } - } - } - - return min; -} /* * repair_frag() -- try to repair relation's fragmentation @@ -1772,7 +1630,7 @@ vactuple_get_minxid(HeapTuple tuple) static void repair_frag(VRelStats *vacrelstats, Relation onerel, VacPageList vacuum_pages, VacPageList fraged_pages, - int nindexes, Relation *Irel, TransactionId OldestXmin) + int nindexes, Relation *Irel) { TransactionId myXID = GetCurrentTransactionId(); Buffer dst_buffer = InvalidBuffer; @@ -2903,8 +2761,8 @@ move_plain_tuple(Relation rel, * update_hint_bits() -- update hint bits in destination pages * * Scan all the pages that we moved tuples onto and update tuple status bits. - * This is normally not really necessary, but it will save time for future - * transactions examining these tuples. + * This is not really necessary, but it will save time for future transactions + * examining these tuples. * * This pass guarantees that all HEAP_MOVED_IN tuples are marked as * XMIN_COMMITTED, so that future tqual tests won't need to check their XVAC. @@ -2919,13 +2777,9 @@ move_plain_tuple(Relation rel, * To completely ensure that no MOVED_OFF tuples remain unmarked, we'd have * to remember and revisit those pages too. * - * Because of this omission, VACUUM FULL FREEZE is not a safe combination; - * it's possible that the VACUUM's own XID remains exposed as something that - * tqual tests would need to check. - * - * For the non-freeze case, one wonders whether it wouldn't be better to skip - * this work entirely, and let the tuple status updates happen someplace - * that's not holding an exclusive lock on the relation. + * One wonders whether it wouldn't be better to skip this work entirely, + * and let the tuple status updates happen someplace that's not holding an + * exclusive lock on the relation. */ static void update_hint_bits(Relation rel, VacPageList fraged_pages, int num_fraged_pages, @@ -3114,7 +2968,7 @@ scan_index(Relation indrel, double num_tuples) /* now update statistics in pg_class */ vac_update_relstats(RelationGetRelid(indrel), stats->num_pages, stats->num_index_tuples, - false, InvalidTransactionId, InvalidTransactionId); + false, InvalidTransactionId); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", @@ -3183,7 +3037,7 @@ vacuum_index(VacPageList vacpagelist, Relation indrel, /* now update statistics in pg_class */ vac_update_relstats(RelationGetRelid(indrel), stats->num_pages, stats->num_index_tuples, - false, InvalidTransactionId, InvalidTransactionId); + false, InvalidTransactionId); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index c89dc20404c97b2675743367c1c0c28a1862fe05..b57ce14a70069d512d8d5b41174e79da5eefbe0a 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -36,7 +36,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/commands/vacuumlazy.c,v 1.80 2006/10/04 00:29:52 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/commands/vacuumlazy.c,v 1.81 2006/11/05 22:42:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -78,7 +78,6 @@ typedef struct LVRelStats double tuples_deleted; BlockNumber nonempty_pages; /* actually, last nonempty page + 1 */ Size threshold; /* minimum interesting free space */ - TransactionId minxid; /* minimum Xid present anywhere in table */ /* List of TIDs of tuples we intend to delete */ /* NB: this list is ordered by TID address */ int num_dead_tuples; /* current # of entries */ @@ -96,11 +95,13 @@ typedef struct LVRelStats static int elevel = -1; +static TransactionId OldestXmin; +static TransactionId FreezeLimit; + /* non-export function prototypes */ static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, - Relation *Irel, int nindexes, TransactionId FreezeLimit, - TransactionId OldestXmin); + Relation *Irel, int nindexes); static void lazy_vacuum_heap(Relation onerel, LVRelStats *vacrelstats); static void lazy_vacuum_index(Relation indrel, IndexBulkDeleteResult **stats, @@ -110,10 +111,9 @@ static void lazy_cleanup_index(Relation indrel, LVRelStats *vacrelstats); static int lazy_vacuum_page(Relation onerel, BlockNumber blkno, Buffer buffer, int tupindex, LVRelStats *vacrelstats); -static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats, - TransactionId OldestXmin); +static void lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats); static BlockNumber count_nondeletable_pages(Relation onerel, - LVRelStats *vacrelstats, TransactionId OldestXmin); + LVRelStats *vacrelstats); static void lazy_space_alloc(LVRelStats *vacrelstats, BlockNumber relblocks); static void lazy_record_dead_tuple(LVRelStats *vacrelstats, ItemPointer itemptr); @@ -129,8 +129,7 @@ static int vac_cmp_page_spaces(const void *left, const void *right); * lazy_vacuum_rel() -- perform LAZY VACUUM for one heap relation * * This routine vacuums a single heap, cleans out its indexes, and - * updates its relpages and reltuples statistics, as well as the - * relminxid and relvacuumxid information. + * updates its relpages and reltuples statistics. * * At entry, we have already established a transaction and opened * and locked the relation. @@ -142,8 +141,6 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) Relation *Irel; int nindexes; BlockNumber possibly_freeable; - TransactionId OldestXmin, - FreezeLimit; if (vacstmt->verbose) elevel = INFO; @@ -159,23 +156,12 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) /* XXX should we scale it up or down? Adjust vacuum.c too, if so */ vacrelstats->threshold = GetAvgFSMRequestSize(&onerel->rd_node); - /* - * Set initial minimum Xid, which will be updated if a smaller Xid is - * found in the relation by lazy_scan_heap. - * - * We use RecentXmin here (the minimum Xid that belongs to a transaction - * that is still open according to our snapshot), because it is the - * earliest transaction that could concurrently insert new tuples in the - * table. - */ - vacrelstats->minxid = RecentXmin; - /* Open all indexes of the relation */ vac_open_indexes(onerel, RowExclusiveLock, &nindexes, &Irel); vacrelstats->hasindex = (nindexes > 0); /* Do the vacuuming */ - lazy_scan_heap(onerel, vacrelstats, Irel, nindexes, FreezeLimit, OldestXmin); + lazy_scan_heap(onerel, vacrelstats, Irel, nindexes); /* Done with indexes */ vac_close_indexes(nindexes, Irel, NoLock); @@ -189,7 +175,7 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages; if (possibly_freeable >= REL_TRUNCATE_MINIMUM || possibly_freeable >= vacrelstats->rel_pages / REL_TRUNCATE_FRACTION) - lazy_truncate_heap(onerel, vacrelstats, OldestXmin); + lazy_truncate_heap(onerel, vacrelstats); /* Update shared free space map with final free space info */ lazy_update_fsm(onerel, vacrelstats); @@ -199,7 +185,7 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) vacrelstats->rel_pages, vacrelstats->rel_tuples, vacrelstats->hasindex, - vacrelstats->minxid, OldestXmin); + FreezeLimit); /* report results to the stats collector, too */ pgstat_report_vacuum(RelationGetRelid(onerel), onerel->rd_rel->relisshared, @@ -215,16 +201,12 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt) * of live tuples in the heap. When done, or when we run low on space * for dead-tuple TIDs, invoke vacuuming of indexes and heap. * - * It also updates the minimum Xid found anywhere on the table in - * vacrelstats->minxid, for later storing it in pg_class.relminxid. - * * If there are no indexes then we just vacuum each dirty page as we * process it, since there's no point in gathering many tuples. */ static void lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, - Relation *Irel, int nindexes, TransactionId FreezeLimit, - TransactionId OldestXmin) + Relation *Irel, int nindexes) { BlockNumber nblocks, blkno; @@ -266,10 +248,11 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, Page page; OffsetNumber offnum, maxoff; - bool pgchanged, - tupgone, + bool tupgone, hastup; int prev_dead_count; + OffsetNumber frozen[MaxOffsetNumber]; + int nfrozen; vacuum_delay_point(); @@ -293,7 +276,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, buf = ReadBuffer(onerel, blkno); - /* In this phase we only need shared access to the buffer */ + /* Initially, we only need shared access to the buffer */ LockBuffer(buf, BUFFER_LOCK_SHARE); page = BufferGetPage(buf); @@ -349,7 +332,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, continue; } - pgchanged = false; + nfrozen = 0; hastup = false; prev_dead_count = vacrelstats->num_dead_tuples; maxoff = PageGetMaxOffsetNumber(page); @@ -379,31 +362,7 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, tupgone = true; /* we can delete the tuple */ break; case HEAPTUPLE_LIVE: - - /* - * Tuple is good. Consider whether to replace its xmin - * value with FrozenTransactionId. - * - * NB: Since we hold only a shared buffer lock here, we - * are assuming that TransactionId read/write is atomic. - * This is not the only place that makes such an - * assumption. It'd be possible to avoid the assumption by - * momentarily acquiring exclusive lock, but for the - * moment I see no need to. - */ - if (TransactionIdIsNormal(HeapTupleHeaderGetXmin(tuple.t_data)) && - TransactionIdPrecedes(HeapTupleHeaderGetXmin(tuple.t_data), - FreezeLimit)) - { - HeapTupleHeaderSetXmin(tuple.t_data, FrozenTransactionId); - /* infomask should be okay already */ - Assert(tuple.t_data->t_infomask & HEAP_XMIN_COMMITTED); - pgchanged = true; - } - - /* - * Other checks... - */ + /* Tuple is good --- but let's do some validity checks */ if (onerel->rd_rel->relhasoids && !OidIsValid(HeapTupleGetOid(&tuple))) elog(WARNING, "relation \"%s\" TID %u/%u: OID is invalid", @@ -435,22 +394,40 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, } else { - TransactionId min; - num_tuples += 1; hastup = true; /* - * If the tuple is alive, we consider it for the "minxid" - * calculations. + * Each non-removable tuple must be checked to see if it + * needs freezing. If we already froze anything, then + * we've already switched the buffer lock to exclusive. */ - min = vactuple_get_minxid(&tuple); - if (TransactionIdIsValid(min) && - TransactionIdPrecedes(min, vacrelstats->minxid)) - vacrelstats->minxid = min; + if (heap_freeze_tuple(tuple.t_data, FreezeLimit, + (nfrozen > 0) ? InvalidBuffer : buf)) + frozen[nfrozen++] = offnum; } } /* scan along page */ + /* + * If we froze any tuples, mark the buffer dirty, and write a WAL + * record recording the changes. We must log the changes to be + * crash-safe against future truncation of CLOG. + */ + if (nfrozen > 0) + { + MarkBufferDirty(buf); + /* no XLOG for temp tables, though */ + if (!onerel->rd_istemp) + { + XLogRecPtr recptr; + + recptr = log_heap_freeze(onerel, buf, FreezeLimit, + frozen, nfrozen); + PageSetLSN(page, recptr); + PageSetTLI(page, ThisTimeLineID); + } + } + /* * If there are no indexes then we can vacuum the page right now * instead of doing a second scan. @@ -485,8 +462,6 @@ lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, if (hastup) vacrelstats->nonempty_pages = blkno + 1; - if (pgchanged) - MarkBufferDirty(buf); UnlockReleaseBuffer(buf); } @@ -710,7 +685,7 @@ lazy_cleanup_index(Relation indrel, vac_update_relstats(RelationGetRelid(indrel), stats->num_pages, stats->num_index_tuples, - false, InvalidTransactionId, InvalidTransactionId); + false, InvalidTransactionId); ereport(elevel, (errmsg("index \"%s\" now contains %.0f row versions in %u pages", @@ -731,8 +706,7 @@ lazy_cleanup_index(Relation indrel, * lazy_truncate_heap - try to truncate off any empty pages at the end */ static void -lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats, - TransactionId OldestXmin) +lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats) { BlockNumber old_rel_pages = vacrelstats->rel_pages; BlockNumber new_rel_pages; @@ -773,7 +747,7 @@ lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats, * because other backends could have added tuples to these pages whilst we * were vacuuming. */ - new_rel_pages = count_nondeletable_pages(onerel, vacrelstats, OldestXmin); + new_rel_pages = count_nondeletable_pages(onerel, vacrelstats); if (new_rel_pages >= old_rel_pages) { @@ -837,8 +811,7 @@ lazy_truncate_heap(Relation onerel, LVRelStats *vacrelstats, * Returns number of nondeletable pages (last nonempty page + 1). */ static BlockNumber -count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats, - TransactionId OldestXmin) +count_nondeletable_pages(Relation onerel, LVRelStats *vacrelstats) { BlockNumber blkno; HeapTupleData tuple; diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index 776d167ff2e66d789aaadab789ca501f37f44ab1..cfacd208c6b29cf78cdd8253e7aaa1a99b9061b3 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.156 2006/10/04 00:29:53 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/libpq/hba.c,v 1.157 2006/11/05 22:42:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1004,16 +1004,14 @@ load_hba(void) * dbname: gets database name (must be of size NAMEDATALEN bytes) * dboid: gets database OID * dbtablespace: gets database's default tablespace's OID - * dbminxid: gets database's minimum XID - * dbvacuumxid: gets database's vacuum XID + * dbfrozenxid: gets database's frozen XID * * This is not much related to the other functions in hba.c, but we put it * here because it uses the next_token() infrastructure. */ bool read_pg_database_line(FILE *fp, char *dbname, Oid *dboid, - Oid *dbtablespace, TransactionId *dbminxid, - TransactionId *dbvacuumxid) + Oid *dbtablespace, TransactionId *dbfrozenxid) { char buf[MAX_TOKEN]; @@ -1035,11 +1033,7 @@ read_pg_database_line(FILE *fp, char *dbname, Oid *dboid, next_token(fp, buf, sizeof(buf)); if (!isdigit((unsigned char) buf[0])) elog(FATAL, "bad data in flat pg_database file"); - *dbminxid = atoxid(buf); - next_token(fp, buf, sizeof(buf)); - if (!isdigit((unsigned char) buf[0])) - elog(FATAL, "bad data in flat pg_database file"); - *dbvacuumxid = atoxid(buf); + *dbfrozenxid = atoxid(buf); /* expect EOL next */ if (next_token(fp, buf, sizeof(buf))) elog(FATAL, "bad data in flat pg_database file"); diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c index 8efe73904c0fe3d00df76e0d4f7532a2556498bf..c1858e6746d101f47547a1ab4e67c32898c1f372 100644 --- a/src/backend/nodes/copyfuncs.c +++ b/src/backend/nodes/copyfuncs.c @@ -15,7 +15,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.352 2006/10/13 21:43:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/copyfuncs.c,v 1.353 2006/11/05 22:42:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -2350,8 +2350,8 @@ _copyVacuumStmt(VacuumStmt *from) COPY_SCALAR_FIELD(vacuum); COPY_SCALAR_FIELD(full); COPY_SCALAR_FIELD(analyze); - COPY_SCALAR_FIELD(freeze); COPY_SCALAR_FIELD(verbose); + COPY_SCALAR_FIELD(freeze_min_age); COPY_NODE_FIELD(relation); COPY_NODE_FIELD(va_cols); diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c index 0ac818f8a292334371070f9da6180b6f2810c7b4..a42afb77a3da11e9c124becb7da6a81fb90e00de 100644 --- a/src/backend/nodes/equalfuncs.c +++ b/src/backend/nodes/equalfuncs.c @@ -18,7 +18,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.286 2006/10/13 21:43:18 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/nodes/equalfuncs.c,v 1.287 2006/11/05 22:42:08 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1215,8 +1215,8 @@ _equalVacuumStmt(VacuumStmt *a, VacuumStmt *b) COMPARE_SCALAR_FIELD(vacuum); COMPARE_SCALAR_FIELD(full); COMPARE_SCALAR_FIELD(analyze); - COMPARE_SCALAR_FIELD(freeze); COMPARE_SCALAR_FIELD(verbose); + COMPARE_SCALAR_FIELD(freeze_min_age); COMPARE_NODE_FIELD(relation); COMPARE_NODE_FIELD(va_cols); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index 1d1e105c74ecebe3a1dfd7be91cf44730de73df2..c90743a1017b7d33b31c02c782dac3f3791fb2da 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -11,7 +11,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.567 2006/10/13 21:43:19 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/parser/gram.y,v 2.568 2006/11/05 22:42:09 tgl Exp $ * * HISTORY * AUTHOR DATE MAJOR EVENT @@ -5158,7 +5158,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose n->vacuum = true; n->analyze = false; n->full = $2; - n->freeze = $3; + n->freeze_min_age = $3 ? 0 : -1; n->verbose = $4; n->relation = NULL; n->va_cols = NIL; @@ -5170,7 +5170,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose n->vacuum = true; n->analyze = false; n->full = $2; - n->freeze = $3; + n->freeze_min_age = $3 ? 0 : -1; n->verbose = $4; n->relation = $5; n->va_cols = NIL; @@ -5181,7 +5181,7 @@ VacuumStmt: VACUUM opt_full opt_freeze opt_verbose VacuumStmt *n = (VacuumStmt *) $5; n->vacuum = true; n->full = $2; - n->freeze = $3; + n->freeze_min_age = $3 ? 0 : -1; n->verbose |= $4; $$ = (Node *)n; } @@ -5194,7 +5194,7 @@ AnalyzeStmt: n->vacuum = false; n->analyze = true; n->full = false; - n->freeze = false; + n->freeze_min_age = -1; n->verbose = $2; n->relation = NULL; n->va_cols = NIL; @@ -5206,7 +5206,7 @@ AnalyzeStmt: n->vacuum = false; n->analyze = true; n->full = false; - n->freeze = false; + n->freeze_min_age = -1; n->verbose = $2; n->relation = $3; n->va_cols = $4; diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 2ba12c2f9e66e71eba65e0f2ece30a9ef3ecd331..11552c9464428dc9bdff57d3099dfc3559081e65 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.27 2006/10/04 00:29:56 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/autovacuum.c,v 1.28 2006/11/05 22:42:09 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -59,6 +59,7 @@ int autovacuum_vac_thresh; double autovacuum_vac_scale; int autovacuum_anl_thresh; double autovacuum_anl_scale; +int autovacuum_freeze_max_age; int autovacuum_vac_cost_delay; int autovacuum_vac_cost_limit; @@ -70,6 +71,12 @@ static bool am_autovacuum = false; static time_t last_autovac_start_time = 0; static time_t last_autovac_stop_time = 0; +/* Comparison point for determining whether freeze_max_age is exceeded */ +static TransactionId recentXid; + +/* Default freeze_min_age to use for autovacuum (varies by database) */ +static int default_freeze_min_age; + /* Memory context for long-lived data */ static MemoryContext AutovacMemCxt; @@ -78,10 +85,8 @@ typedef struct autovac_dbase { Oid oid; char *name; - TransactionId minxid; - TransactionId vacuumxid; + TransactionId frozenxid; PgStat_StatDBEntry *entry; - int32 age; } autovac_dbase; /* struct to keep track of tables to vacuum and/or analyze */ @@ -91,6 +96,7 @@ typedef struct autovac_table Oid toastrelid; bool dovacuum; bool doanalyze; + int freeze_min_age; int vacuum_cost_delay; int vacuum_cost_limit; } autovac_table; @@ -100,7 +106,6 @@ typedef struct autovac_table static pid_t autovac_forkexec(void); #endif NON_EXEC_STATIC void AutoVacMain(int argc, char *argv[]); -static void process_whole_db(void); static void do_autovacuum(PgStat_StatDBEntry *dbentry); static List *autovac_get_database_list(void); static void test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry, @@ -108,10 +113,9 @@ static void test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry, Form_pg_autovacuum avForm, List **vacuum_tables, List **toast_table_ids); -static void autovacuum_do_vac_analyze(List *relids, bool dovacuum, - bool doanalyze, bool freeze); -static void autovac_report_activity(VacuumStmt *vacstmt, - List *relids); +static void autovacuum_do_vac_analyze(Oid relid, bool dovacuum, + bool doanalyze, int freeze_min_age); +static void autovac_report_activity(VacuumStmt *vacstmt, Oid relid); /* @@ -222,9 +226,9 @@ AutoVacMain(int argc, char *argv[]) { ListCell *cell; List *dblist; - TransactionId nextXid; autovac_dbase *db; - bool whole_db; + TransactionId xidForceLimit; + bool for_xid_wrap; sigjmp_buf local_sigjmp_buf; /* we are a postmaster subprocess now */ @@ -315,29 +319,28 @@ AutoVacMain(int argc, char *argv[]) dblist = autovac_get_database_list(); /* - * Get the next Xid that was current as of the last checkpoint. We need it - * to determine whether databases are about to need database-wide vacuums. + * Determine the oldest datfrozenxid/relfrozenxid that we will allow + * to pass without forcing a vacuum. (This limit can be tightened for + * particular tables, but not loosened.) */ - nextXid = GetRecentNextXid(); + recentXid = ReadNewTransactionId(); + xidForceLimit = recentXid - autovacuum_freeze_max_age; + /* ensure it's a "normal" XID, else TransactionIdPrecedes misbehaves */ + if (xidForceLimit < FirstNormalTransactionId) + xidForceLimit -= FirstNormalTransactionId; /* * Choose a database to connect to. We pick the database that was least - * recently auto-vacuumed, or one that needs database-wide vacuum (to - * prevent Xid wraparound-related data loss). + * recently auto-vacuumed, or one that needs vacuuming to prevent Xid + * wraparound-related data loss. If any db at risk of wraparound is + * found, we pick the one with oldest datfrozenxid, + * independently of autovacuum times. * * Note that a database with no stats entry is not considered, except for * Xid wraparound purposes. The theory is that if no one has ever * connected to it since the stats were last initialized, it doesn't need * vacuuming. * - * Note that if we are called when autovacuum is nominally disabled in - * postgresql.conf, we assume the postmaster has invoked us because a - * database is in danger of Xid wraparound. In that case, we only - * consider vacuuming whole databases, not individual tables; and we pick - * the oldest one, regardless of it's true age. So the criteria for - * deciding that a database needs a database-wide vacuum is elsewhere - * (currently in vac_truncate_clog). - * * XXX This could be improved if we had more info about whether it needs * vacuuming before connecting to it. Perhaps look through the pgstats * data for the database's tables? One idea is to keep track of the @@ -346,84 +349,40 @@ AutoVacMain(int argc, char *argv[]) * starvation for less busy databases. */ db = NULL; - whole_db = false; - - if (AutoVacuumingActive()) + for_xid_wrap = false; + foreach(cell, dblist) { - /* - * We look for the database that most urgently needs a database-wide - * vacuum. We decide that a database-wide vacuum is needed 100000 - * transactions sooner than vacuum.c's vac_truncate_clog() would - * decide to start giving warnings. If any such db is found, we - * ignore all other dbs. - * - * Unlike vacuum.c, we also look at vacuumxid. This is so that - * pg_clog can be kept trimmed to a reasonable size. - */ - foreach(cell, dblist) + autovac_dbase *tmp = lfirst(cell); + + /* Find pgstat entry if any */ + tmp->entry = pgstat_fetch_stat_dbentry(tmp->oid); + + /* Check to see if this one is at risk of wraparound */ + if (TransactionIdPrecedes(tmp->frozenxid, xidForceLimit)) { - autovac_dbase *tmp = lfirst(cell); - bool this_whole_db; - int32 true_age, - vacuum_age; - - true_age = (int32) (nextXid - tmp->minxid); - vacuum_age = (int32) (nextXid - tmp->vacuumxid); - tmp->age = Max(true_age, vacuum_age); - - this_whole_db = (tmp->age > - (int32) ((MaxTransactionId >> 3) * 3 - 100000)); - - if (whole_db || this_whole_db) - { - if (!this_whole_db) - continue; - if (db == NULL || tmp->age > db->age) - { - db = tmp; - whole_db = true; - } - continue; - } - - /* - * Otherwise, skip a database with no pgstat entry; it means it - * hasn't seen any activity. - */ - tmp->entry = pgstat_fetch_stat_dbentry(tmp->oid); - if (!tmp->entry) - continue; - - /* - * Remember the db with oldest autovac time. - */ if (db == NULL || - tmp->entry->last_autovac_time < db->entry->last_autovac_time) + TransactionIdPrecedes(tmp->frozenxid, db->frozenxid)) db = tmp; + for_xid_wrap = true; + continue; } - } - else - { + else if (for_xid_wrap) + continue; /* ignore not-at-risk DBs */ + /* - * If autovacuuming is not active, we must have gotten here because a - * backend signalled the postmaster. Pick up the database with the - * greatest age, and apply a database-wide vacuum on it. + * Otherwise, skip a database with no pgstat entry; it means it + * hasn't seen any activity. */ - int32 oldest = 0; - - whole_db = true; - foreach(cell, dblist) - { - autovac_dbase *tmp = lfirst(cell); - int32 age = (int32) (nextXid - tmp->minxid); + if (!tmp->entry) + continue; - if (age > oldest) - { - oldest = age; - db = tmp; - } - } - Assert(db); + /* + * Remember the db with oldest autovac time. (If we are here, + * both tmp->entry and db->entry must be non-null.) + */ + if (db == NULL || + tmp->entry->last_autovac_time < db->entry->last_autovac_time) + db = tmp; } if (db) @@ -460,10 +419,7 @@ AutoVacMain(int argc, char *argv[]) /* * And do an appropriate amount of work */ - if (whole_db) - process_whole_db(); - else - do_autovacuum(db->entry); + do_autovacuum(db->entry); } /* One iteration done, go away */ @@ -485,8 +441,7 @@ autovac_get_database_list(void) FILE *db_file; Oid db_id; Oid db_tablespace; - TransactionId db_minxid; - TransactionId db_vacuumxid; + TransactionId db_frozenxid; filename = database_getflatfilename(); db_file = AllocateFile(filename, "r"); @@ -496,8 +451,7 @@ autovac_get_database_list(void) errmsg("could not open file \"%s\": %m", filename))); while (read_pg_database_line(db_file, thisname, &db_id, - &db_tablespace, &db_minxid, - &db_vacuumxid)) + &db_tablespace, &db_frozenxid)) { autovac_dbase *db; @@ -505,11 +459,9 @@ autovac_get_database_list(void) db->oid = db_id; db->name = pstrdup(thisname); - db->minxid = db_minxid; - db->vacuumxid = db_vacuumxid; - /* these get set later: */ + db->frozenxid = db_frozenxid; + /* this gets set later: */ db->entry = NULL; - db->age = 0; dblist = lappend(dblist, db); } @@ -520,60 +472,12 @@ autovac_get_database_list(void) return dblist; } -/* - * Process a whole database. If it's a template database or is disallowing - * connection by means of datallowconn=false, then issue a VACUUM FREEZE. - * Else use a plain VACUUM. - */ -static void -process_whole_db(void) -{ - HeapTuple tup; - Form_pg_database dbForm; - bool freeze; - - /* Start a transaction so our commands have one to play into. */ - StartTransactionCommand(); - - /* functions in indexes may want a snapshot set */ - ActiveSnapshot = CopySnapshot(GetTransactionSnapshot()); - - /* - * Clean up any dead statistics collector entries for this DB. - */ - pgstat_vacuum_tabstat(); - - /* Look up the pg_database entry and decide whether to FREEZE */ - tup = SearchSysCache(DATABASEOID, - ObjectIdGetDatum(MyDatabaseId), - 0, 0, 0); - if (!HeapTupleIsValid(tup)) - elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); - - dbForm = (Form_pg_database) GETSTRUCT(tup); - - if (!dbForm->datallowconn || dbForm->datistemplate) - freeze = true; - else - freeze = false; - - ReleaseSysCache(tup); - - elog(DEBUG2, "autovacuum: VACUUM%s whole database", - (freeze) ? " FREEZE" : ""); - - autovacuum_do_vac_analyze(NIL, true, false, freeze); - - /* Finally close out the last transaction. */ - CommitTransactionCommand(); -} - /* * Process a database table-by-table * - * dbentry must be a valid pointer to the database entry in the stats - * databases' hash table, and it will be used to determine whether vacuum or - * analyze is needed on a per-table basis. + * dbentry is either a pointer to the database entry in the stats databases + * hash table, or NULL if we couldn't find any entry (the latter case occurs + * only if we are forcing a vacuum for anti-wrap purposes). * * Note that CHECK_FOR_INTERRUPTS is supposed to be used in certain spots in * order not to ignore shutdown commands for too long. @@ -585,6 +489,7 @@ do_autovacuum(PgStat_StatDBEntry *dbentry) avRel; HeapTuple tuple; HeapScanDesc relScan; + Form_pg_database dbForm; List *vacuum_tables = NIL; List *toast_table_ids = NIL; ListCell *cell; @@ -603,6 +508,25 @@ do_autovacuum(PgStat_StatDBEntry *dbentry) */ pgstat_vacuum_tabstat(); + /* + * Find the pg_database entry and select the default freeze_min_age. + * We use zero in template and nonconnectable databases, + * else the system-wide default. + */ + tuple = SearchSysCache(DATABASEOID, + ObjectIdGetDatum(MyDatabaseId), + 0, 0, 0); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for database %u", MyDatabaseId); + dbForm = (Form_pg_database) GETSTRUCT(tuple); + + if (dbForm->datistemplate || !dbForm->datallowconn) + default_freeze_min_age = 0; + else + default_freeze_min_age = vacuum_freeze_min_age; + + ReleaseSysCache(tuple); + /* * StartTransactionCommand and CommitTransactionCommand will automatically * switch to other contexts. We need this one to keep the list of @@ -676,9 +600,11 @@ do_autovacuum(PgStat_StatDBEntry *dbentry) if (classForm->relisshared && PointerIsValid(shared)) tabentry = hash_search(shared->tables, &relid, HASH_FIND, NULL); - else + else if (PointerIsValid(dbentry)) tabentry = hash_search(dbentry->tables, &relid, HASH_FIND, NULL); + else + tabentry = NULL; test_rel_for_autovac(relid, tabentry, classForm, avForm, &vacuum_tables, &toast_table_ids); @@ -719,12 +645,18 @@ do_autovacuum(PgStat_StatDBEntry *dbentry) VacuumCostDelay = tab->vacuum_cost_delay; VacuumCostLimit = tab->vacuum_cost_limit; - autovacuum_do_vac_analyze(list_make1_oid(tab->relid), + autovacuum_do_vac_analyze(tab->relid, tab->dovacuum, tab->doanalyze, - false); + tab->freeze_min_age); } + /* + * Update pg_database.datfrozenxid, and truncate pg_clog if possible. + * We only need to do this once, not after each table. + */ + vac_update_datfrozenxid(); + /* Finally close out the last transaction. */ CommitTransactionCommand(); } @@ -746,10 +678,13 @@ do_autovacuum(PgStat_StatDBEntry *dbentry) * the number of tuples (both live and dead) that there were as of the last * analyze. This is asymmetric to the VACUUM case. * + * We also force vacuum if the table's relfrozenxid is more than freeze_max_age + * transactions back. + * * A table whose pg_autovacuum.enabled value is false, is automatically - * skipped. Thus autovacuum can be disabled for specific tables. Also, - * when the stats collector does not have data about a table, it will be - * skipped. + * skipped (unless we have to vacuum it due to freeze_max_age). Thus + * autovacuum can be disabled for specific tables. Also, when the stats + * collector does not have data about a table, it will be skipped. * * A table whose vac_base_thresh value is <0 takes the base value from the * autovacuum_vacuum_threshold GUC variable. Similarly, a vac_scale_factor @@ -763,44 +698,28 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry, List **vacuum_tables, List **toast_table_ids) { + bool force_vacuum; + bool dovacuum; + bool doanalyze; float4 reltuples; /* pg_class.reltuples */ - /* constants from pg_autovacuum or GUC variables */ int vac_base_thresh, anl_base_thresh; float4 vac_scale_factor, anl_scale_factor; - /* thresholds calculated from above constants */ float4 vacthresh, anlthresh; - /* number of vacuum (resp. analyze) tuples at this time */ float4 vactuples, anltuples; - + /* freeze parameters */ + int freeze_min_age; + int freeze_max_age; + TransactionId xidForceLimit; /* cost-based vacuum delay parameters */ int vac_cost_limit; int vac_cost_delay; - bool dovacuum; - bool doanalyze; - - /* User disabled it in pg_autovacuum? */ - if (avForm && !avForm->enabled) - return; - - /* - * Skip a table not found in stat hash. If it's not acted upon, there's - * no need to vacuum it. (Note that database-level check will take care - * of Xid wraparound.) - */ - if (!PointerIsValid(tabentry)) - return; - - reltuples = classForm->reltuples; - vactuples = tabentry->n_dead_tuples; - anltuples = tabentry->n_live_tuples + tabentry->n_dead_tuples - - tabentry->last_anl_tuples; /* * If there is a tuple in pg_autovacuum, use it; else, use the GUC @@ -819,6 +738,12 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry, anl_base_thresh = (avForm->anl_base_thresh >= 0) ? avForm->anl_base_thresh : autovacuum_anl_thresh; + freeze_min_age = (avForm->freeze_min_age >= 0) ? + avForm->freeze_min_age : default_freeze_min_age; + freeze_max_age = (avForm->freeze_max_age >= 0) ? + Min(avForm->freeze_max_age, autovacuum_freeze_max_age) : + autovacuum_freeze_max_age; + vac_cost_limit = (avForm->vac_cost_limit >= 0) ? avForm->vac_cost_limit : ((autovacuum_vac_cost_limit >= 0) ? @@ -837,6 +762,9 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry, anl_scale_factor = autovacuum_anl_scale; anl_base_thresh = autovacuum_anl_thresh; + freeze_min_age = default_freeze_min_age; + freeze_max_age = autovacuum_freeze_max_age; + vac_cost_limit = (autovacuum_vac_cost_limit >= 0) ? autovacuum_vac_cost_limit : VacuumCostLimit; @@ -844,22 +772,51 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry, autovacuum_vac_cost_delay : VacuumCostDelay; } - vacthresh = (float4) vac_base_thresh + vac_scale_factor * reltuples; - anlthresh = (float4) anl_base_thresh + anl_scale_factor * reltuples; + /* Force vacuum if table is at risk of wraparound */ + xidForceLimit = recentXid - freeze_max_age; + if (xidForceLimit < FirstNormalTransactionId) + xidForceLimit -= FirstNormalTransactionId; + force_vacuum = (TransactionIdIsNormal(classForm->relfrozenxid) && + TransactionIdPrecedes(classForm->relfrozenxid, + xidForceLimit)); - /* - * Note that we don't need to take special consideration for stat reset, - * because if that happens, the last vacuum and analyze counts will be - * reset too. - */ + /* User disabled it in pg_autovacuum? (But ignore if at risk) */ + if (avForm && !avForm->enabled && !force_vacuum) + return; - elog(DEBUG3, "%s: vac: %.0f (threshold %.0f), anl: %.0f (threshold %.0f)", - NameStr(classForm->relname), - vactuples, vacthresh, anltuples, anlthresh); + if (PointerIsValid(tabentry)) + { + reltuples = classForm->reltuples; + vactuples = tabentry->n_dead_tuples; + anltuples = tabentry->n_live_tuples + tabentry->n_dead_tuples - + tabentry->last_anl_tuples; - /* Determine if this table needs vacuum or analyze. */ - dovacuum = (vactuples > vacthresh); - doanalyze = (anltuples > anlthresh); + vacthresh = (float4) vac_base_thresh + vac_scale_factor * reltuples; + anlthresh = (float4) anl_base_thresh + anl_scale_factor * reltuples; + + /* + * Note that we don't need to take special consideration for stat + * reset, because if that happens, the last vacuum and analyze counts + * will be reset too. + */ + elog(DEBUG3, "%s: vac: %.0f (threshold %.0f), anl: %.0f (threshold %.0f)", + NameStr(classForm->relname), + vactuples, vacthresh, anltuples, anlthresh); + + /* Determine if this table needs vacuum or analyze. */ + dovacuum = force_vacuum || (vactuples > vacthresh); + doanalyze = (anltuples > anlthresh); + } + else + { + /* + * Skip a table not found in stat hash, unless we have to force + * vacuum for anti-wrap purposes. If it's not acted upon, there's + * no need to vacuum it. + */ + dovacuum = force_vacuum; + doanalyze = false; + } /* ANALYZE refuses to work with pg_statistics */ if (relid == StatisticRelationId) @@ -888,6 +845,7 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry, tab->toastrelid = classForm->reltoastrelid; tab->dovacuum = dovacuum; tab->doanalyze = doanalyze; + tab->freeze_min_age = freeze_min_age; tab->vacuum_cost_limit = vac_cost_limit; tab->vacuum_cost_delay = vac_cost_delay; @@ -904,11 +862,11 @@ test_rel_for_autovac(Oid relid, PgStat_StatTabEntry *tabentry, /* * autovacuum_do_vac_analyze - * Vacuum and/or analyze a list of tables; or all tables if relids = NIL + * Vacuum and/or analyze the specified table */ static void -autovacuum_do_vac_analyze(List *relids, bool dovacuum, bool doanalyze, - bool freeze) +autovacuum_do_vac_analyze(Oid relid, bool dovacuum, bool doanalyze, + int freeze_min_age) { VacuumStmt *vacstmt; MemoryContext old_cxt; @@ -932,15 +890,15 @@ autovacuum_do_vac_analyze(List *relids, bool dovacuum, bool doanalyze, vacstmt->vacuum = dovacuum; vacstmt->full = false; vacstmt->analyze = doanalyze; - vacstmt->freeze = freeze; + vacstmt->freeze_min_age = freeze_min_age; vacstmt->verbose = false; - vacstmt->relation = NULL; /* all tables, or not used if relids != NIL */ + vacstmt->relation = NULL; /* not used since we pass relids list */ vacstmt->va_cols = NIL; /* Let pgstat know what we're doing */ - autovac_report_activity(vacstmt, relids); + autovac_report_activity(vacstmt, relid); - vacuum(vacstmt, relids); + vacuum(vacstmt, list_make1_oid(relid)); pfree(vacstmt); MemoryContextSwitchTo(old_cxt); @@ -958,48 +916,35 @@ autovacuum_do_vac_analyze(List *relids, bool dovacuum, bool doanalyze, * bother to report "" or some such. */ static void -autovac_report_activity(VacuumStmt *vacstmt, List *relids) +autovac_report_activity(VacuumStmt *vacstmt, Oid relid) { + char *relname = get_rel_name(relid); + char *nspname = get_namespace_name(get_rel_namespace(relid)); #define MAX_AUTOVAC_ACTIV_LEN (NAMEDATALEN * 2 + 32) char activity[MAX_AUTOVAC_ACTIV_LEN]; - /* - * This case is not currently exercised by the autovac code. Fill it in - * if needed. - */ - if (list_length(relids) > 1) - elog(WARNING, "vacuuming >1 rel unsupported"); - /* Report the command and possible options */ if (vacstmt->vacuum) snprintf(activity, MAX_AUTOVAC_ACTIV_LEN, - "VACUUM%s%s%s", - vacstmt->full ? " FULL" : "", - vacstmt->freeze ? " FREEZE" : "", + "VACUUM%s", vacstmt->analyze ? " ANALYZE" : ""); - else if (vacstmt->analyze) + else snprintf(activity, MAX_AUTOVAC_ACTIV_LEN, "ANALYZE"); - /* Report the qualified name of the first relation, if any */ - if (relids) + /* + * Report the qualified name of the relation. + * + * Paranoia is appropriate here in case relation was recently dropped + * --- the lsyscache routines we just invoked will return NULL rather + * than failing. + */ + if (relname && nspname) { - Oid relid = linitial_oid(relids); - char *relname = get_rel_name(relid); - char *nspname = get_namespace_name(get_rel_namespace(relid)); - - /* - * Paranoia is appropriate here in case relation was recently dropped - * --- the lsyscache routines we just invoked will return NULL rather - * than failing. - */ - if (relname && nspname) - { - int len = strlen(activity); + int len = strlen(activity); - snprintf(activity + len, MAX_AUTOVAC_ACTIV_LEN - len, - " %s.%s", nspname, relname); - } + snprintf(activity + len, MAX_AUTOVAC_ACTIV_LEN - len, + " %s.%s", nspname, relname); } pgstat_report_activity(activity); diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c index ed4de7269806cbac8c76352871b4106a9db30392..caf7d9a82d47663adc3d42e8e525466108804d88 100644 --- a/src/backend/postmaster/postmaster.c +++ b/src/backend/postmaster/postmaster.c @@ -37,7 +37,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.500 2006/10/04 00:29:56 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/postmaster/postmaster.c,v 1.501 2006/11/05 22:42:09 tgl Exp $ * * NOTES * @@ -217,6 +217,8 @@ static bool FatalError = false; /* T if recovering from backend crash */ bool ClientAuthInProgress = false; /* T during new-client * authentication */ +static bool force_autovac = false; /* received START_AUTOVAC signal */ + /* * State for assigning random salts and cancel keys. * Also, the global MyCancelKey passes the cancel key assigned to a given @@ -1231,9 +1233,13 @@ ServerLoop(void) * (It'll die relatively quickly.) We check that it's not started too * frequently in autovac_start. */ - if (AutoVacuumingActive() && AutoVacPID == 0 && + if ((AutoVacuumingActive() || force_autovac) && AutoVacPID == 0 && StartupPID == 0 && !FatalError && Shutdown == NoShutdown) + { AutoVacPID = autovac_start(); + if (AutoVacPID != 0) + force_autovac = false; /* signal successfully processed */ + } /* If we have lost the archiver, try to start a new one */ if (XLogArchivingActive() && PgArchPID == 0 && @@ -2100,9 +2106,7 @@ reaper(SIGNAL_ARGS) /* * Was it the autovacuum process? Normal exit can be ignored; we'll * start a new one at the next iteration of the postmaster's main - * loop, if necessary. - * - * An unexpected exit must crash the system. + * loop, if necessary. An unexpected exit is treated as a crash. */ if (AutoVacPID != 0 && pid == AutoVacPID) { @@ -3424,12 +3428,16 @@ sigusr1_handler(SIGNAL_ARGS) if (CheckPostmasterSignal(PMSIGNAL_START_AUTOVAC)) { - /* start one iteration of the autovacuum daemon */ - if (Shutdown == NoShutdown) - { - Assert(!AutoVacuumingActive()); - AutoVacPID = autovac_start(); - } + /* + * Start one iteration of the autovacuum daemon, even if autovacuuming + * is nominally not enabled. This is so we can have an active defense + * against transaction ID wraparound. We set a flag for the main loop + * to do it rather than trying to do it here --- this is because the + * autovac process itself may send the signal, and we want to handle + * that by launching another iteration as soon as the current one + * completes. + */ + force_autovac = true; } PG_SETMASK(&UnBlockSig); diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 39de167fe561d510f99ca5e349dd86bb6cb8c9b0..b037e594941d22d97c9fecd002af9f0cd93a1d3e 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -23,7 +23,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/storage/ipc/procarray.c,v 1.18 2006/10/04 00:29:57 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/storage/ipc/procarray.c,v 1.19 2006/11/05 22:42:09 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -213,7 +213,9 @@ TransactionIdIsInProgress(TransactionId xid) /* * Don't bother checking a transaction older than RecentXmin; it could not - * possibly still be running. + * possibly still be running. (Note: in particular, this guarantees + * that we reject InvalidTransactionId, FrozenTransactionId, etc as + * not running.) */ if (TransactionIdPrecedes(xid, RecentXmin)) { diff --git a/src/backend/utils/init/flatfiles.c b/src/backend/utils/init/flatfiles.c index 8867ec3e015fc520582de52978bc42821fc99b3d..dc117145fc0182479803bf906e2f1f16b311e9bf 100644 --- a/src/backend/utils/init/flatfiles.c +++ b/src/backend/utils/init/flatfiles.c @@ -23,7 +23,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.21 2006/07/14 14:52:25 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/init/flatfiles.c,v 1.22 2006/11/05 22:42:09 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -163,7 +163,7 @@ name_okay(const char *str) /* * write_database_file: update the flat database file * - * A side effect is to determine the oldest database's datminxid + * A side effect is to determine the oldest database's datfrozenxid * so we can set or update the XID wrap limit. */ static void @@ -177,7 +177,7 @@ write_database_file(Relation drel) HeapScanDesc scan; HeapTuple tuple; NameData oldest_datname; - TransactionId oldest_datminxid = InvalidTransactionId; + TransactionId oldest_datfrozenxid = InvalidTransactionId; /* * Create a temporary filename to be renamed later. This prevents the @@ -208,27 +208,23 @@ write_database_file(Relation drel) char *datname; Oid datoid; Oid dattablespace; - TransactionId datminxid, - datvacuumxid; + TransactionId datfrozenxid; datname = NameStr(dbform->datname); datoid = HeapTupleGetOid(tuple); dattablespace = dbform->dattablespace; - datminxid = dbform->datminxid; - datvacuumxid = dbform->datvacuumxid; + datfrozenxid = dbform->datfrozenxid; /* - * Identify the oldest datminxid, ignoring databases that are not - * connectable (we assume they are safely frozen). This must match + * Identify the oldest datfrozenxid. This must match * the logic in vac_truncate_clog() in vacuum.c. */ - if (dbform->datallowconn && - TransactionIdIsNormal(datminxid)) + if (TransactionIdIsNormal(datfrozenxid)) { - if (oldest_datminxid == InvalidTransactionId || - TransactionIdPrecedes(datminxid, oldest_datminxid)) + if (oldest_datfrozenxid == InvalidTransactionId || + TransactionIdPrecedes(datfrozenxid, oldest_datfrozenxid)) { - oldest_datminxid = datminxid; + oldest_datfrozenxid = datfrozenxid; namestrcpy(&oldest_datname, datname); } } @@ -244,14 +240,14 @@ write_database_file(Relation drel) } /* - * The file format is: "dbname" oid tablespace minxid vacuumxid + * The file format is: "dbname" oid tablespace frozenxid * * The xids are not needed for backend startup, but are of use to * autovacuum, and might also be helpful for forensic purposes. */ fputs_quote(datname, fp); - fprintf(fp, " %u %u %u %u\n", - datoid, dattablespace, datminxid, datvacuumxid); + fprintf(fp, " %u %u %u\n", + datoid, dattablespace, datfrozenxid); } heap_endscan(scan); @@ -272,10 +268,10 @@ write_database_file(Relation drel) tempname, filename))); /* - * Set the transaction ID wrap limit using the oldest datminxid + * Set the transaction ID wrap limit using the oldest datfrozenxid */ - if (oldest_datminxid != InvalidTransactionId) - SetTransactionIdLimit(oldest_datminxid, &oldest_datname); + if (oldest_datfrozenxid != InvalidTransactionId) + SetTransactionIdLimit(oldest_datfrozenxid, &oldest_datname); } diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index 9ab8c9ba97f31659ca6e37b6a84fd05fe4d14187..82532a196b00136a2e380815c392b9b8a08a2064 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.171 2006/10/04 00:30:02 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/init/postinit.c,v 1.172 2006/11/05 22:42:09 tgl Exp $ * * *------------------------------------------------------------------------- @@ -77,7 +77,7 @@ FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace) char *filename; FILE *db_file; char thisname[NAMEDATALEN]; - TransactionId dummyxid; + TransactionId db_frozenxid; filename = database_getflatfilename(); db_file = AllocateFile(filename, "r"); @@ -87,8 +87,7 @@ FindMyDatabase(const char *name, Oid *db_id, Oid *db_tablespace) errmsg("could not open file \"%s\": %m", filename))); while (read_pg_database_line(db_file, thisname, db_id, - db_tablespace, &dummyxid, - &dummyxid)) + db_tablespace, &db_frozenxid)) { if (strcmp(thisname, name) == 0) { diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index f0ff66fa66a1f01a818ddbd8c1eba476154c4524..5bbf89da5bd7420d2efd0a313256b547d43b30a2 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -10,7 +10,7 @@ * Written by Peter Eisentraut . * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.357 2006/10/19 18:32:47 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/misc/guc.c,v 1.358 2006/11/05 22:42:09 tgl Exp $ * *-------------------------------------------------------------------- */ @@ -1330,6 +1330,15 @@ static struct config_int ConfigureNamesInt[] = 0, 0, INT_MAX, NULL, NULL }, + { + {"vacuum_freeze_min_age", PGC_USERSET, CLIENT_CONN_STATEMENT, + gettext_noop("Minimum age at which VACUUM should freeze a table row."), + NULL + }, + &vacuum_freeze_min_age, + 100000000, 0, 1000000000, NULL, NULL + }, + { {"max_fsm_relations", PGC_POSTMASTER, RESOURCES_FSM, gettext_noop("Sets the maximum number of tables and indexes for which free space is tracked."), @@ -1576,6 +1585,15 @@ static struct config_int ConfigureNamesInt[] = &autovacuum_anl_thresh, 250, 0, INT_MAX, NULL, NULL }, + { + /* see varsup.c for why this is PGC_POSTMASTER not PGC_SIGHUP */ + {"autovacuum_freeze_max_age", PGC_POSTMASTER, AUTOVACUUM, + gettext_noop("Age at which to autovacuum a table to prevent transacion ID wraparound."), + NULL + }, + &autovacuum_freeze_max_age, + 200000000, 100000000, 2000000000, NULL, NULL + }, { {"tcp_keepalives_idle", PGC_USERSET, CLIENT_CONN_OTHER, diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 1d0e842bba07c4e6a260953790c8f7186f9fe728..3a0dedba672082cd9d1b29514164b53a12413e15 100644 --- a/src/backend/utils/misc/postgresql.conf.sample +++ b/src/backend/utils/misc/postgresql.conf.sample @@ -373,6 +373,8 @@ # vacuum #autovacuum_analyze_scale_factor = 0.1 # fraction of rel size before # analyze +#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum + # (change requires restart) #autovacuum_vacuum_cost_delay = -1 # default vacuum cost delay for # autovacuum, -1 means use # vacuum_cost_delay @@ -394,6 +396,7 @@ #default_transaction_isolation = 'read committed' #default_transaction_read_only = off #statement_timeout = 0 # 0 is disabled +#vacuum_freeze_min_age = 100000000 # - Locale and Formatting - diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c index eedc6222be841a8d26f90e8a9fceb415c0c46f26..2bdb6d9e7141ee558e373fd6d3383a2d8cece73e 100644 --- a/src/backend/utils/time/tqual.c +++ b/src/backend/utils/time/tqual.c @@ -32,7 +32,7 @@ * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/utils/time/tqual.c,v 1.98 2006/10/04 00:30:04 momjian Exp $ + * $PostgreSQL: pgsql/src/backend/utils/time/tqual.c,v 1.99 2006/11/05 22:42:09 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1099,9 +1099,11 @@ HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin, { /* * "Deleting" xact really only locked it, so the tuple is live in any - * case. However, we must make sure that either XMAX_COMMITTED or - * XMAX_INVALID gets set once the xact is gone; otherwise it is unsafe - * to recycle CLOG status after vacuuming. + * case. However, we should make sure that either XMAX_COMMITTED or + * XMAX_INVALID gets set once the xact is gone, to reduce the costs + * of examining the tuple for future xacts. Also, marking dead + * MultiXacts as invalid here provides defense against MultiXactId + * wraparound (see also comments in heap_freeze_tuple()). */ if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) { diff --git a/src/include/access/clog.h b/src/include/access/clog.h index 999f15bdf7f37de828d695cfaf1a627a4e2ddece..0cb76000a29b8ef556d7c7edc08e96e65b1c26fa 100644 --- a/src/include/access/clog.h +++ b/src/include/access/clog.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/access/clog.h,v 1.17 2006/03/24 04:32:13 tgl Exp $ + * $PostgreSQL: pgsql/src/include/access/clog.h,v 1.18 2006/11/05 22:42:09 tgl Exp $ */ #ifndef CLOG_H #define CLOG_H @@ -46,6 +46,7 @@ extern void TruncateCLOG(TransactionId oldestXact); /* XLOG stuff */ #define CLOG_ZEROPAGE 0x00 +#define CLOG_TRUNCATE 0x10 extern void clog_redo(XLogRecPtr lsn, XLogRecord *record); extern void clog_desc(StringInfo buf, uint8 xl_info, char *rec); diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index b2dd0f3390d3412413f0f70c92403a08ed855a22..4b3dd57534c21a75f7465291b19c47a860bfb20d 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.116 2006/10/04 00:30:07 momjian Exp $ + * $PostgreSQL: pgsql/src/include/access/heapam.h,v 1.117 2006/11/05 22:42:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -170,6 +170,8 @@ extern HTSU_Result heap_lock_tuple(Relation relation, HeapTuple tuple, TransactionId *update_xmax, CommandId cid, LockTupleMode mode, bool nowait); extern void heap_inplace_update(Relation relation, HeapTuple tuple); +extern bool heap_freeze_tuple(HeapTupleHeader tuple, TransactionId cutoff_xid, + Buffer buf); extern Oid simple_heap_insert(Relation relation, HeapTuple tup); extern void simple_heap_delete(Relation relation, ItemPointer tid); @@ -181,11 +183,17 @@ extern void heap_restrpos(HeapScanDesc scan); extern void heap_redo(XLogRecPtr lsn, XLogRecord *rptr); extern void heap_desc(StringInfo buf, uint8 xl_info, char *rec); -extern XLogRecPtr log_heap_clean(Relation reln, Buffer buffer, - OffsetNumber *unused, int uncnt); +extern void heap2_redo(XLogRecPtr lsn, XLogRecord *rptr); +extern void heap2_desc(StringInfo buf, uint8 xl_info, char *rec); + extern XLogRecPtr log_heap_move(Relation reln, Buffer oldbuf, ItemPointerData from, Buffer newbuf, HeapTuple newtup); +extern XLogRecPtr log_heap_clean(Relation reln, Buffer buffer, + OffsetNumber *unused, int uncnt); +extern XLogRecPtr log_heap_freeze(Relation reln, Buffer buffer, + TransactionId cutoff_xid, + OffsetNumber *offsets, int offcnt); /* in common/heaptuple.c */ extern Size heap_compute_data_size(TupleDesc tupleDesc, diff --git a/src/include/access/htup.h b/src/include/access/htup.h index edfce82bc044dfd1bb65cbfee2959293c09a4589..ed1f082de05b2f4ace750579471abcd8044413c7 100644 --- a/src/include/access/htup.h +++ b/src/include/access/htup.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/access/htup.h,v 1.86 2006/10/04 00:30:07 momjian Exp $ + * $PostgreSQL: pgsql/src/include/access/htup.h,v 1.87 2006/11/05 22:42:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -510,6 +510,13 @@ typedef HeapTupleData *HeapTuple; * we can (and we do) restore entire page in redo */ #define XLOG_HEAP_INIT_PAGE 0x80 +/* + * We ran out of opcodes, so heapam.c now has a second RmgrId. These opcodes + * are associated with RM_HEAP2_ID, but are not logically different from + * the ones above associated with RM_HEAP_ID. We apply XLOG_HEAP_OPMASK, + * although currently XLOG_HEAP_INIT_PAGE is not used for any of these. + */ +#define XLOG_HEAP2_FREEZE 0x00 /* * All what we need to find changed tuple @@ -613,4 +620,15 @@ typedef struct xl_heap_inplace #define SizeOfHeapInplace (offsetof(xl_heap_inplace, target) + SizeOfHeapTid) +/* This is what we need to know about tuple freezing during vacuum */ +typedef struct xl_heap_freeze +{ + RelFileNode node; + BlockNumber block; + TransactionId cutoff_xid; + /* TUPLE OFFSET NUMBERS FOLLOW AT THE END */ +} xl_heap_freeze; + +#define SizeOfHeapFreeze (offsetof(xl_heap_freeze, cutoff_xid) + sizeof(TransactionId)) + #endif /* HTUP_H */ diff --git a/src/include/access/rmgr.h b/src/include/access/rmgr.h index 471b0cfb76475a6e9ffdba9e4de988bd88de55dc..7be2dfc9f657528fe2bb7dfa122525373b05395e 100644 --- a/src/include/access/rmgr.h +++ b/src/include/access/rmgr.h @@ -3,7 +3,7 @@ * * Resource managers definition * - * $PostgreSQL: pgsql/src/include/access/rmgr.h,v 1.16 2006/05/02 11:28:55 teodor Exp $ + * $PostgreSQL: pgsql/src/include/access/rmgr.h,v 1.17 2006/11/05 22:42:10 tgl Exp $ */ #ifndef RMGR_H #define RMGR_H @@ -12,6 +12,9 @@ typedef uint8 RmgrId; /* * Built-in resource managers + * + * Note: RM_MAX_ID could be as much as 255 without breaking the XLOG file + * format, but we keep it small to minimize the size of RmgrTable[]. */ #define RM_XLOG_ID 0 #define RM_XACT_ID 1 @@ -20,6 +23,7 @@ typedef uint8 RmgrId; #define RM_DBASE_ID 4 #define RM_TBLSPC_ID 5 #define RM_MULTIXACT_ID 6 +#define RM_HEAP2_ID 9 #define RM_HEAP_ID 10 #define RM_BTREE_ID 11 #define RM_HASH_ID 12 diff --git a/src/include/access/transam.h b/src/include/access/transam.h index f1b91145f6755cdfd181190a975f415f5f756a9c..96cc65f94d53089dd66551e575e9978bcad3ecc0 100644 --- a/src/include/access/transam.h +++ b/src/include/access/transam.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/access/transam.h,v 1.58 2006/07/10 16:20:51 alvherre Exp $ + * $PostgreSQL: pgsql/src/include/access/transam.h,v 1.59 2006/11/05 22:42:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -23,7 +23,7 @@ * always be considered valid. * * FirstNormalTransactionId is the first "normal" transaction id. - * Note: if you need to change it, you must change it in pg_class.h as well. + * Note: if you need to change it, you must change pg_class.h as well. * ---------------- */ #define InvalidTransactionId ((TransactionId) 0) @@ -88,6 +88,9 @@ typedef struct VariableCacheData Oid nextOid; /* next OID to assign */ uint32 oidCount; /* OIDs available before must do XLOG work */ TransactionId nextXid; /* next XID to assign */ + + TransactionId oldestXid; /* cluster-wide minimum datfrozenxid */ + TransactionId xidVacLimit; /* start forcing autovacuums here */ TransactionId xidWarnLimit; /* start complaining here */ TransactionId xidStopLimit; /* refuse to advance nextXid beyond here */ TransactionId xidWrapLimit; /* where the world ends */ @@ -124,7 +127,7 @@ extern bool TransactionIdFollowsOrEquals(TransactionId id1, TransactionId id2); /* in transam/varsup.c */ extern TransactionId GetNewTransactionId(bool isSubXact); extern TransactionId ReadNewTransactionId(void); -extern void SetTransactionIdLimit(TransactionId oldest_datminxid, +extern void SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Name oldest_datname); extern Oid GetNewObjectId(void); diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h index a5ae94b91aa517ef292fb56ef3465150cc301ec7..93c95aa462728fda850f6d3e48864751e4dfa583 100644 --- a/src/include/access/xlog.h +++ b/src/include/access/xlog.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/access/xlog.h,v 1.74 2006/08/21 16:16:31 tgl Exp $ + * $PostgreSQL: pgsql/src/include/access/xlog.h,v 1.75 2006/11/05 22:42:10 tgl Exp $ */ #ifndef XLOG_H #define XLOG_H @@ -165,7 +165,6 @@ extern void InitXLOGAccess(void); extern void CreateCheckPoint(bool shutdown, bool force); extern void XLogPutNextOid(Oid nextOid); extern XLogRecPtr GetRedoRecPtr(void); -extern TransactionId GetRecentNextXid(void); extern void GetNextXidAndEpoch(TransactionId *xid, uint32 *epoch); #endif /* XLOG_H */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index f5da840d2d022e1a718f34a3dc8ab8c1c19041b8..2f61b94675826298761b312fef2e3d086ca31ae8 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.358 2006/09/18 22:40:38 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.359 2006/11/05 22:42:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200609181 +#define CATALOG_VERSION_NO 200611051 #endif diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index b96a5b83404dcf9ecd5a0d827c59989caec6b317..3fcc5ed67469ab9da61894b77fe94313b75f4b98 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.125 2006/10/04 00:30:07 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_attribute.h,v 1.126 2006/11/05 22:42:10 tgl Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -404,10 +404,9 @@ DATA(insert ( 1249 tableoid 26 0 4 -7 0 -1 -1 t p i t f f t 0)); { 1259, {"relhaspkey"}, 16, -1, 1, 22, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \ { 1259, {"relhasrules"}, 16, -1, 1, 23, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \ { 1259, {"relhassubclass"},16, -1, 1, 24, 0, -1, -1, true, 'p', 'c', true, false, false, true, 0 }, \ -{ 1259, {"relminxid"}, 28, -1, 4, 25, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \ -{ 1259, {"relvacuumxid"}, 28, -1, 4, 26, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \ -{ 1259, {"relacl"}, 1034, -1, -1, 27, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \ -{ 1259, {"reloptions"}, 1009, -1, -1, 28, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 } +{ 1259, {"relfrozenxid"}, 28, -1, 4, 25, 0, -1, -1, true, 'p', 'i', true, false, false, true, 0 }, \ +{ 1259, {"relacl"}, 1034, -1, -1, 26, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 }, \ +{ 1259, {"reloptions"}, 1009, -1, -1, 27, 1, -1, -1, false, 'x', 'i', false, false, false, true, 0 } DATA(insert ( 1259 relname 19 -1 NAMEDATALEN 1 0 -1 -1 f p i t f f t 0)); DATA(insert ( 1259 relnamespace 26 -1 4 2 0 -1 -1 t p i t f f t 0)); @@ -433,10 +432,9 @@ DATA(insert ( 1259 relhasoids 16 -1 1 21 0 -1 -1 t p c t f f t 0)); DATA(insert ( 1259 relhaspkey 16 -1 1 22 0 -1 -1 t p c t f f t 0)); DATA(insert ( 1259 relhasrules 16 -1 1 23 0 -1 -1 t p c t f f t 0)); DATA(insert ( 1259 relhassubclass 16 -1 1 24 0 -1 -1 t p c t f f t 0)); -DATA(insert ( 1259 relminxid 28 -1 4 25 0 -1 -1 t p i t f f t 0)); -DATA(insert ( 1259 relvacuumxid 28 -1 4 26 0 -1 -1 t p i t f f t 0)); -DATA(insert ( 1259 relacl 1034 -1 -1 27 1 -1 -1 f x i f f f t 0)); -DATA(insert ( 1259 reloptions 1009 -1 -1 28 1 -1 -1 f x i f f f t 0)); +DATA(insert ( 1259 relfrozenxid 28 -1 4 25 0 -1 -1 t p i t f f t 0)); +DATA(insert ( 1259 relacl 1034 -1 -1 26 1 -1 -1 f x i f f f t 0)); +DATA(insert ( 1259 reloptions 1009 -1 -1 27 1 -1 -1 f x i f f f t 0)); DATA(insert ( 1259 ctid 27 0 6 -1 0 -1 -1 f p s t f f t 0)); DATA(insert ( 1259 oid 26 0 4 -2 0 -1 -1 t p i t f f t 0)); DATA(insert ( 1259 xmin 28 0 4 -3 0 -1 -1 t p i t f f t 0)); diff --git a/src/include/catalog/pg_autovacuum.h b/src/include/catalog/pg_autovacuum.h index 5e3db86a43b38d6de321a56c8f050ea68c6dc6dd..e46da9c649d2a818e3fae8eec258696ecba6a6ee 100644 --- a/src/include/catalog/pg_autovacuum.h +++ b/src/include/catalog/pg_autovacuum.h @@ -6,7 +6,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_autovacuum.h,v 1.4 2006/03/05 15:58:54 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_autovacuum.h,v 1.5 2006/11/05 22:42:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -28,14 +28,16 @@ #define AutovacuumRelationId 1248 CATALOG(pg_autovacuum,1248) BKI_WITHOUT_OIDS { - Oid vacrelid; /* OID of table */ - bool enabled; /* enabled for this table? */ + Oid vacrelid; /* OID of table */ + bool enabled; /* enabled for this table? */ int4 vac_base_thresh; /* base threshold value */ - float4 vac_scale_factor; /* reltuples scaling factor */ + float4 vac_scale_factor; /* reltuples scaling factor */ int4 anl_base_thresh; /* base threshold value */ - float4 anl_scale_factor; /* reltuples scaling factor */ - int4 vac_cost_delay; /* vacuum cost-based delay */ - int4 vac_cost_limit; /* vacuum cost limit */ + float4 anl_scale_factor; /* reltuples scaling factor */ + int4 vac_cost_delay; /* vacuum cost-based delay */ + int4 vac_cost_limit; /* vacuum cost limit */ + int4 freeze_min_age; /* vacuum min freeze age */ + int4 freeze_max_age; /* max age before forcing vacuum */ } FormData_pg_autovacuum; /* ---------------- @@ -49,7 +51,7 @@ typedef FormData_pg_autovacuum *Form_pg_autovacuum; * compiler constants for pg_autovacuum * ---------------- */ -#define Natts_pg_autovacuum 8 +#define Natts_pg_autovacuum 10 #define Anum_pg_autovacuum_vacrelid 1 #define Anum_pg_autovacuum_enabled 2 #define Anum_pg_autovacuum_vac_base_thresh 3 @@ -58,6 +60,8 @@ typedef FormData_pg_autovacuum *Form_pg_autovacuum; #define Anum_pg_autovacuum_anl_scale_factor 6 #define Anum_pg_autovacuum_vac_cost_delay 7 #define Anum_pg_autovacuum_vac_cost_limit 8 +#define Anum_pg_autovacuum_freeze_min_age 9 +#define Anum_pg_autovacuum_freeze_max_age 10 /* There are no preloaded tuples in pg_autovacuum.h */ diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index aa0c02ca1d9ae88fc8efce1503b5e8a6549cec14..75aee92e51261acce0f6a69e6f3d05f00e9f390d 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_class.h,v 1.96 2006/10/04 00:30:07 momjian Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_class.h,v 1.97 2006/11/05 22:42:10 tgl Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -65,8 +65,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP bool relhaspkey; /* has PRIMARY KEY index */ bool relhasrules; /* has associated rules */ bool relhassubclass; /* has derived classes */ - TransactionId relminxid; /* minimum Xid present in table */ - TransactionId relvacuumxid; /* Xid used as last vacuum OldestXmin */ + TransactionId relfrozenxid; /* all Xids < this are frozen in this rel */ /* * VARIABLE LENGTH FIELDS start here. These fields may be NULL, too. @@ -80,7 +79,7 @@ CATALOG(pg_class,1259) BKI_BOOTSTRAP /* Size of fixed part of pg_class tuples, not counting var-length fields */ #define CLASS_TUPLE_SIZE \ - (offsetof(FormData_pg_class,relvacuumxid) + sizeof(TransactionId)) + (offsetof(FormData_pg_class,relfrozenxid) + sizeof(TransactionId)) /* ---------------- * Form_pg_class corresponds to a pointer to a tuple with @@ -94,7 +93,7 @@ typedef FormData_pg_class *Form_pg_class; * ---------------- */ -#define Natts_pg_class 28 +#define Natts_pg_class 27 #define Anum_pg_class_relname 1 #define Anum_pg_class_relnamespace 2 #define Anum_pg_class_reltype 3 @@ -119,27 +118,27 @@ typedef FormData_pg_class *Form_pg_class; #define Anum_pg_class_relhaspkey 22 #define Anum_pg_class_relhasrules 23 #define Anum_pg_class_relhassubclass 24 -#define Anum_pg_class_relminxid 25 -#define Anum_pg_class_relvacuumxid 26 -#define Anum_pg_class_relacl 27 -#define Anum_pg_class_reloptions 28 +#define Anum_pg_class_relfrozenxid 25 +#define Anum_pg_class_relacl 26 +#define Anum_pg_class_reloptions 27 /* ---------------- * initial contents of pg_class * * NOTE: only "bootstrapped" relations need to be declared here. Be sure that - * the OIDs listed here match those given in their CATALOG macros. + * the OIDs listed here match those given in their CATALOG macros, and that + * the relnatts values are correct. * ---------------- */ -/* Note: the "3" here stands for FirstNormalTransactionId */ -DATA(insert OID = 1247 ( pg_type PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f r 23 0 0 0 0 0 t f f f 3 3 _null_ _null_ )); +/* Note: "3" in the relfrozenxid column stands for FirstNormalTransactionId */ +DATA(insert OID = 1247 ( pg_type PGNSP 71 PGUID 0 1247 0 0 0 0 0 f f r 23 0 0 0 0 0 t f f f 3 _null_ _null_ )); DESCR(""); -DATA(insert OID = 1249 ( pg_attribute PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f r 17 0 0 0 0 0 f f f f 3 3 _null_ _null_ )); +DATA(insert OID = 1249 ( pg_attribute PGNSP 75 PGUID 0 1249 0 0 0 0 0 f f r 17 0 0 0 0 0 f f f f 3 _null_ _null_ )); DESCR(""); -DATA(insert OID = 1255 ( pg_proc PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f r 18 0 0 0 0 0 t f f f 3 3 _null_ _null_ )); +DATA(insert OID = 1255 ( pg_proc PGNSP 81 PGUID 0 1255 0 0 0 0 0 f f r 18 0 0 0 0 0 t f f f 3 _null_ _null_ )); DESCR(""); -DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f r 28 0 0 0 0 0 t f f f 3 3 _null_ _null_ )); +DATA(insert OID = 1259 ( pg_class PGNSP 83 PGUID 0 1259 0 0 0 0 0 f f r 27 0 0 0 0 0 t f f f 3 _null_ _null_ )); DESCR(""); #define RELKIND_INDEX 'i' /* secondary index */ diff --git a/src/include/catalog/pg_database.h b/src/include/catalog/pg_database.h index d62a4b949289a0018370dfe7ccf56f85e00e84fc..c3d80bebcb763b1c449e9da7f211d4194d1e4b9d 100644 --- a/src/include/catalog/pg_database.h +++ b/src/include/catalog/pg_database.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_database.h,v 1.41 2006/07/10 16:20:51 alvherre Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_database.h,v 1.42 2006/11/05 22:42:10 tgl Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -42,8 +42,7 @@ CATALOG(pg_database,1262) BKI_SHARED_RELATION bool datallowconn; /* new connections allowed? */ int4 datconnlimit; /* max connections allowed (-1=no limit) */ Oid datlastsysoid; /* highest OID to consider a system OID */ - TransactionId datvacuumxid; /* all XIDs before this are vacuumed */ - TransactionId datminxid; /* minimum XID present anywhere in the DB */ + TransactionId datfrozenxid; /* all Xids < this are frozen in this DB */ Oid dattablespace; /* default table space for this DB */ text datconfig[1]; /* database-specific GUC (VAR LENGTH) */ aclitem datacl[1]; /* access permissions (VAR LENGTH) */ @@ -60,7 +59,7 @@ typedef FormData_pg_database *Form_pg_database; * compiler constants for pg_database * ---------------- */ -#define Natts_pg_database 12 +#define Natts_pg_database 11 #define Anum_pg_database_datname 1 #define Anum_pg_database_datdba 2 #define Anum_pg_database_encoding 3 @@ -68,13 +67,12 @@ typedef FormData_pg_database *Form_pg_database; #define Anum_pg_database_datallowconn 5 #define Anum_pg_database_datconnlimit 6 #define Anum_pg_database_datlastsysoid 7 -#define Anum_pg_database_datvacuumxid 8 -#define Anum_pg_database_datminxid 9 -#define Anum_pg_database_dattablespace 10 -#define Anum_pg_database_datconfig 11 -#define Anum_pg_database_datacl 12 +#define Anum_pg_database_datfrozenxid 8 +#define Anum_pg_database_dattablespace 9 +#define Anum_pg_database_datconfig 10 +#define Anum_pg_database_datacl 11 -DATA(insert OID = 1 ( template1 PGUID ENCODING t t -1 0 0 0 1663 _null_ _null_ )); +DATA(insert OID = 1 ( template1 PGUID ENCODING t t -1 0 0 1663 _null_ _null_ )); SHDESCR("Default template database"); #define TemplateDbOid 1 diff --git a/src/include/commands/vacuum.h b/src/include/commands/vacuum.h index fed481971d2bbbe94dd5212ef15ff262ac83df70..5808a581cc561810361d76bee208bb701082313e 100644 --- a/src/include/commands/vacuum.h +++ b/src/include/commands/vacuum.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/commands/vacuum.h,v 1.67 2006/07/13 18:01:02 momjian Exp $ + * $PostgreSQL: pgsql/src/include/commands/vacuum.h,v 1.68 2006/11/05 22:42:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -104,8 +104,9 @@ typedef struct VacAttrStats } VacAttrStats; -/* Default statistics target (GUC parameter) */ +/* GUC parameters */ extern DLLIMPORT int default_statistics_target; /* DLLIMPORT for PostGIS */ +extern int vacuum_freeze_min_age; /* in commands/vacuum.c */ @@ -117,14 +118,13 @@ extern void vac_update_relstats(Oid relid, BlockNumber num_pages, double num_tuples, bool hasindex, - TransactionId minxid, - TransactionId vacuumxid); + TransactionId frozenxid); extern void vacuum_set_xid_limits(VacuumStmt *vacstmt, bool sharedRel, TransactionId *oldestXmin, TransactionId *freezeLimit); +extern void vac_update_datfrozenxid(void); extern bool vac_is_partial_index(Relation indrel); extern void vacuum_delay_point(void); -extern TransactionId vactuple_get_minxid(HeapTuple tuple); /* in commands/vacuumlazy.c */ extern void lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt); diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index eb8539f1399b3949c4e1a77e410adfe45b414b4b..155db7314d5310fec48aa4325802f7feb7fa1c64 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -4,7 +4,7 @@ * Interface to hba.c * * - * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.44 2006/10/04 00:30:08 momjian Exp $ + * $PostgreSQL: pgsql/src/include/libpq/hba.h,v 1.45 2006/11/05 22:42:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -40,7 +40,6 @@ extern void load_role(void); extern int hba_getauthmethod(hbaPort *port); extern int authident(hbaPort *port); extern bool read_pg_database_line(FILE *fp, char *dbname, Oid *dboid, - Oid *dbtablespace, TransactionId *dbminxid, - TransactionId *dbvacuumxid); + Oid *dbtablespace, TransactionId *dbfrozenxid); #endif /* HBA_H */ diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h index 4f7351236f65cb5c7e6f5ae676e397effd4aad70..f79bf2907ce29bd3ca88a28ad2bb0947a70e4ff3 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.333 2006/10/13 21:43:19 tgl Exp $ + * $PostgreSQL: pgsql/src/include/nodes/parsenodes.h,v 1.334 2006/11/05 22:42:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -1756,8 +1756,8 @@ typedef struct VacuumStmt bool vacuum; /* do VACUUM step */ bool full; /* do FULL (non-concurrent) vacuum */ bool analyze; /* do ANALYZE step */ - bool freeze; /* early-freeze option */ bool verbose; /* print progress info */ + int freeze_min_age; /* min freeze age, or -1 to use default */ RangeVar *relation; /* single table to process, or NULL */ List *va_cols; /* list of column names, or NIL for all */ } VacuumStmt; diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h index 3de77f2ed177873ebacf680d416254ee8cb7a03b..b36334fce8e03be611dffd773995f411c389307a 100644 --- a/src/include/postmaster/autovacuum.h +++ b/src/include/postmaster/autovacuum.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/postmaster/autovacuum.h,v 1.4 2006/03/05 15:58:58 momjian Exp $ + * $PostgreSQL: pgsql/src/include/postmaster/autovacuum.h,v 1.5 2006/11/05 22:42:10 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -21,6 +21,7 @@ extern int autovacuum_vac_thresh; extern double autovacuum_vac_scale; extern int autovacuum_anl_thresh; extern double autovacuum_anl_scale; +extern int autovacuum_freeze_max_age; extern int autovacuum_vac_cost_delay; extern int autovacuum_vac_cost_limit;