-<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.135 2006/10/23 18:10:30 petere Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/catalogs.sgml,v 2.136 2006/11/05 22:42:06 tgl Exp $ -->
<!--
Documentation of the system catalogs, directed toward PostgreSQL developers
-->
<entry></entry>
<entry>Custom <varname>vacuum_cost_limit</> parameter</entry>
</row>
+
+ <row>
+ <entry><structfield>freeze_min_age</structfield></entry>
+ <entry><type>integer</type></entry>
+ <entry></entry>
+ <entry>Custom <varname>vacuum_freeze_min_age</> parameter</entry>
+ </row>
+
+ <row>
+ <entry><structfield>freeze_max_age</structfield></entry>
+ <entry><type>integer</type></entry>
+ <entry></entry>
+ <entry>Custom <varname>autovacuum_freeze_max_age</> parameter</entry>
+ </row>
</tbody>
</tgroup>
</table>
live tuples currently estimated to be in the relation.
</para>
+ <para>
+ Also, the autovacuum daemon will perform a <command>VACUUM</> operation
+ to prevent transaction ID wraparound if the table's
+ <structname>pg_class</>.<structfield>relfrozenxid</> field attains an age
+ of more than <structfield>freeze_max_age</> transactions, whether the table
+ has been changed or not. The system will launch autovacuum to perform
+ such <command>VACUUM</>s even if autovacuum is otherwise disabled.
+ See <xref linkend="vacuum-for-wraparound"> for more about wraparound
+ prevention.
+ </para>
+
<para>
Any of the numerical fields can contain <literal>-1</> (or indeed
any negative value) to indicate that the system-wide default should
<varname>autovacuum_vacuum_cost_delay</> configuration parameter,
or from <varname>vacuum_cost_delay</> if the former is set to a negative
value. The same applies to <structfield>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 <varname>autovacuum_freeze_max_age</> setting.
</para>
</sect1>
</row>
<row>
- <entry><structfield>relminxid</structfield></entry>
+ <entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
<entry>
- The minimum transaction ID present in all rows in this table. This
- value is used to determine the database-global
- <structname>pg_database</>.<structfield>datminxid</> value.
- </entry>
- </row>
-
- <row>
- <entry><structfield>relvacuumxid</structfield></entry>
- <entry><type>xid</type></entry>
- <entry></entry>
- <entry>
- 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
- <structname>pg_database</>.<structfield>datvacuumxid</> value.
+ All transaction IDs before this one have been replaced with a permanent
+ (<quote>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 <literal>pg_clog</> to be shrunk. Zero
+ (<symbol>InvalidTransactionId</symbol>) if the relation is not a table.
</entry>
</row>
</row>
<row>
- <entry><structfield>datvacuumxid</structfield></entry>
- <entry><type>xid</type></entry>
- <entry></entry>
- <entry>
- 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 <symbol>InvalidTransactionId</symbol>, then the minimum is unknown and can be
- determined by scanning <structname>pg_class</>.<structfield>relvacuumxid</>.
- </entry>
- </row>
-
- <row>
- <entry><structfield>datminxid</structfield></entry>
+ <entry><structfield>datfrozenxid</structfield></entry>
<entry><type>xid</type></entry>
<entry></entry>
<entry>
- 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 (<quote>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 <symbol>InvalidTransactionId</symbol>, then the minimum is unknown and can be
- determined by scanning <structname>pg_class</>.<structfield>relminxid</>.
+ All transaction IDs before this one have been replaced with a permanent
+ (<quote>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 <literal>pg_clog</> to be shrunk.
+ It is the minimum of the per-table
+ <structname>pg_class</>.<structfield>relfrozenxid</> values.
</entry>
</row>
-<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.93 2006/11/04 18:20:27 tgl Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/config.sgml,v 1.94 2006/11/05 22:42:07 tgl Exp $ -->
<chapter Id="runtime-config">
<title>Server Configuration</title>
</listitem>
</varlistentry>
+ <varlistentry id="guc-autovacuum-freeze-max-age" xreflabel="autovacuum_freeze_max_age">
+ <term><varname>autovacuum_freeze_max_age</varname> (<type>integer</type>)</term>
+ <indexterm>
+ <primary><varname>autovacuum_freeze_max_age</> configuration parameter</primary>
+ </indexterm>
+ <listitem>
+ <para>
+ Specifies the maximum age (in transactions) that a table's
+ <structname>pg_class</>.<structfield>relfrozenxid</> field can
+ attain before a <command>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
+ <structname>pg_autovacuum</>.
+ For more information see <xref linkend="vacuum-for-wraparound">.
+ </para>
+ </listitem>
+ </varlistentry>
+
<varlistentry id="guc-autovacuum-vacuum-cost-delay" xreflabel="autovacuum_vacuum_cost_delay">
<term><varname>autovacuum_vacuum_cost_delay</varname> (<type>integer</type>)</term>
<indexterm>
</para>
</listitem>
</varlistentry>
-
+
<varlistentry id="guc-statement-timeout" xreflabel="statement_timeout">
<term><varname>statement_timeout</varname> (<type>integer</type>)</term>
<indexterm>
</para>
</listitem>
</varlistentry>
+
+ <varlistentry id="guc-vacuum-freeze-min-age" xreflabel="vacuum_freeze_min_age">
+ <term><varname>vacuum_freeze_min_age</varname> (<type>integer</type>)</term>
+ <indexterm>
+ <primary><varname>vacuum_freeze_min_age</> configuration parameter</primary>
+ </indexterm>
+ <listitem>
+ <para>
+ Specifies the cutoff age (in transactions) that <command>VACUUM</>
+ should use to decide whether to replace transaction IDs with
+ <literal>FrozenXID</> while scanning a table.
+ The default is 100000000 (100 million). Although users can set this
+ value anywhere from zero to 1000000000, <command>VACUUM</> will
+ silently limit the effective value to half the value of <xref
+ linkend="guc-autovacuum-freeze-max-age">, so that there is not an
+ unreasonably short time between forced autovacuums.
+ For more information see <xref linkend="vacuum-for-wraparound">.
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
</sect2>
-<!-- $PostgreSQL: pgsql/doc/src/sgml/maintenance.sgml,v 1.63 2006/10/23 18:10:31 petere Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/maintenance.sgml,v 1.64 2006/11/05 22:42:07 tgl Exp $ -->
<chapter id="maintenance">
<title>Routine Database Maintenance Tasks</title>
<para>
The other main category of maintenance task is periodic <quote>vacuuming</>
of the database. This activity is discussed in
- <xref linkend="routine-vacuuming">. Closely related to this updating
+ <xref linkend="routine-vacuuming">. Closely related to this is updating
the statistics that will be used by the query planner, as discussed in
<xref linkend="vacuum-for-statistics">.
</para>
will continue to function as normal, though you will not be able to modify the
definition of a table with commands such as <command>ALTER TABLE ADD COLUMN</command>
while it is being vacuumed.
- Beginning in <productname>PostgreSQL</productname> 8.0, there are
- configuration parameters that can be adjusted to further reduce the
- performance impact of background vacuuming. See
+ Also, <command>VACUUM</command> 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
<xref linkend="runtime-config-resource-vacuum-cost">.
</para>
Recommended practice for most sites is to schedule a database-wide
<command>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 <command>VACUUM</command> 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 <command>VACUUM</command> each one;
the program <xref linkend="app-vacuumdb" endterm="app-vacuumdb-title">
may be helpful.
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 <emphasis>necessary to vacuum every table
- in every database at least once every billion transactions</emphasis>.
+ get at it.) To avoid this, it is necessary to vacuum every table
+ in every database at least once every two billion transactions.
</para>
<para>
- 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, <productname>PostgreSQL</productname>
- keeps track of the time of the last database-wide <command>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.
- </para>
-
- <para>
- The new approach to XID comparison distinguishes two special XIDs,
- numbers 1 and 2 (<literal>BootstrapXID</> and
- <literal>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
+ <productname>PostgreSQL</productname> distinguishes a special XID
+ <literal>FrozenXID</>. This XID is always considered older
+ than every normal XID. Normal XIDs are
compared using modulo-2<superscript>31</> arithmetic. This means
that for every normal XID, there are two billion XIDs that are
<quote>older</> and two billion that are <quote>newer</>; another
two-billion-transactions-old mark. Once they are assigned this
special XID, they will appear to be <quote>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 <command>VACUUM</>.
+ row versions will be good until deleted, no matter how long that is.
+ This reassignment of old XIDs is handled by <command>VACUUM</>.
+ </para>
+
+ <para>
+ <command>VACUUM</>'s behavior is controlled by the configuration parameter
+ <xref linkend="guc-vacuum-freeze-min-age">: any XID older than
+ <varname>vacuum_freeze_min_age</> transactions is replaced by
+ <literal>FrozenXID</>. Larger values of <varname>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.
+ </para>
+
+ <para>
+ The maximum time that a table can go unvacuumed is two billion
+ transactions minus the <varname>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 <firstterm>autovacuum</> facility described in
+ <xref linkend="autovacuum"> is invoked on any table
+ that might contain XIDs older than the age specified by the
+ configuration parameter
+ <xref linkend="guc-autovacuum-freeze-max-age">. (This will happen
+ even if autovacuum is otherwise disabled.)
+ </para>
+
+ <para>
+ This implies that if a table is not otherwise vacuumed,
+ autovacuum will be invoked on it approximately once every
+ <varname>autovacuum_freeze_max_age</> minus
+ <varname>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 <varname>autovacuum_freeze_max_age</> or by decreasing
+ <varname>vacuum_freeze_min_age</>.
+ </para>
+
+ <para>
+ The sole disadvantage of increasing <varname>autovacuum_freeze_max_age</>
+ is that the <filename>pg_clog</> subdirectory of the database cluster
+ will take more space, because it must store the commit status for all
+ transactions back to the <varname>autovacuum_freeze_max_age</> horizon.
+ The commit status uses two bits per transaction, so if
+ <varname>autovacuum_freeze_max_age</> has its maximum allowed value of
+ a little less than two billion, <filename>pg_clog</> can be expected to
+ grow to about half a gigabyte. If this is trivial compared to your
+ total database size, setting <varname>autovacuum_freeze_max_age</> to
+ its maximum allowed value is recommended. Otherwise, set it depending
+ on what you are willing to allow for <filename>pg_clog</> storage.
+ (The default, 200 million transactions, translates to about 50MB of
+ <filename>pg_clog</> storage.)
</para>
<para>
- <command>VACUUM</>'s normal policy is to reassign <literal>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 <quote>frozen</>.) With this policy,
- the maximum safe interval between <command>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 <varname>vacuum_freeze_min_age</> is that
+ it may cause <command>VACUUM</> to do useless work: changing a table row's
+ XID to <literal>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.
</para>
<para>
- Since periodic <command>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, <command>VACUUM</> stores transaction ID
- statistics in the system table <literal>pg_database</>. In particular,
- the <literal>datfrozenxid</> column of a database's
- <literal>pg_database</> row is updated at the completion of any
- database-wide <command>VACUUM</command> operation (i.e.,
- <command>VACUUM</> that does not
- name a specific table). The value stored in this field is the freeze
- cutoff XID that was used by that <command>VACUUM</> command. All normal
+ To track the age of the oldest XIDs in a database,
+ <command>VACUUM</> stores XID
+ statistics in the system tables <structname>pg_class</> and
+ <structname>pg_database</>. In particular,
+ the <structfield>relfrozenxid</> column of a table's
+ <structname>pg_class</> row contains the freeze cutoff XID that was used
+ by the last <command>VACUUM</> for that table. All normal
XIDs older than this cutoff XID are guaranteed to have been replaced by
- <literal>FrozenXID</> within that database. A convenient way to
- examine this information is to execute the query
+ <literal>FrozenXID</> within the table. Similarly,
+ the <structfield>datfrozenxid</> column of a database's
+ <structname>pg_database</> row is a lower bound on the normal XIDs
+ appearing in that database — it is just the minimum of the
+ per-table <structfield>relfrozenxid</> values within the database.
+ A convenient way to
+ examine this information is to execute queries such as
<programlisting>
+SELECT relname, age(relfrozenxid) FROM pg_class WHERE relkind = 'r';
SELECT datname, age(datfrozenxid) FROM pg_database;
</programlisting>
The <literal>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
+ <command>VACUUM</>, <literal>age(relfrozenxid)</> should be a little
+ more than the <varname>vacuum_freeze_min_age</> setting that was used
+ (more by the number of transactions started since the <command>VACUUM</>
+ started). If <literal>age(relfrozenxid)</> exceeds
+ <varname>autovacuum_freeze_max_age</>, an autovacuum will soon be forced
+ for the table.
</para>
<para>
- With the standard freezing policy, the <literal>age</> column will start
- at one billion for a freshly-vacuumed database. When the <literal>age</>
- approaches two billion, the database must be vacuumed again to avoid
- risk of wraparound failures. Recommended practice is to <command>VACUUM</command> 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 <command>VACUUM</> automatically delivers a warning
- if there are any <literal>pg_database</> entries showing an
- <literal>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:
<programlisting>
-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
</programlisting>
- </para>
- <para>
- If the warnings emitted by <command>VACUUM</> go ignored, then
- <productname>PostgreSQL</productname> 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:
<programlisting>
-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".
</programlisting>
page for details about using a single-user backend.
</para>
- <para>
- <command>VACUUM</> with the <command>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
- <command>VACUUM FREEZE</> is performed in an otherwise-idle
- database, it is guaranteed that <emphasis>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
- <command>initdb</> to prepare the <literal>template0</> database.
- It should also be used to prepare any user-created databases that
- are to be marked <literal>datallowconn</> = <literal>false</> in
- <literal>pg_database</>, since there isn't any convenient way to
- <command>VACUUM</command> a database that you can't connect to.
- </para>
-
- <warning>
- <para>
- A database that is marked <literal>datallowconn</> = <literal>false</>
- in <literal>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
- <literal>datallowconn</> = <literal>false</>.
- </para>
- </warning>
</sect2>
<sect2 id="autovacuum">
<para>
The autovacuum daemon, when enabled, runs every <xref
- linkend="guc-autovacuum-naptime"> 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 <command>VACUUM</command> call, or <command>VACUUM
- FREEZE</command> 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 <command>VACUUM</command>
- or <command>ANALYZE</command> 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.
+ <command>VACUUM</command> or <command>ANALYZE</command> commands are
+ issued as needed.
</para>
<para>
- For each table, two conditions are used to determine which operation(s)
+ Tables whose <structfield>relfrozenxid</> value is more than
+ <varname>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
<command>VACUUM</command> exceeds the <quote>vacuum threshold</quote>, the
table is vacuumed. The vacuum threshold is defined as:
</para>
<para>
- 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
<structname>pg_autovacuum</structname>.
The first, <structname>pg_autovacuum</>.<structfield>enabled</>,
can be set to <literal>false</literal> 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
(<structname>pg_autovacuum</structname>.<structfield>vac_cost_delay</structfield>)
and the vacuum cost limit
(<structname>pg_autovacuum</structname>.<structfield>vac_cost_limit</structfield>),
are used to set table-specific values for the
<xref linkend="runtime-config-resource-vacuum-cost" endterm="runtime-config-resource-vacuum-cost-title">
feature.
+ The last two parameters,
+ (<structname>pg_autovacuum</structname>.<structfield>freeze_min_age</structfield>)
+ and
+ (<structname>pg_autovacuum</structname>.<structfield>freeze_max_age</structfield>),
+ are used to set table-specific values for
+ <xref linkend="guc-vacuum-freeze-min-age"> and
+ <xref linkend="guc-autovacuum-freeze-max-age"> respectively.
</para>
<para>
-<!-- $PostgreSQL: pgsql/doc/src/sgml/manage-ag.sgml,v 2.48 2006/09/16 00:30:14 momjian Exp $ -->
+<!-- $PostgreSQL: pgsql/doc/src/sgml/manage-ag.sgml,v 2.49 2006/11/05 22:42:07 tgl Exp $ -->
<chapter id="managing-databases">
<title>Managing Databases</title>
should always be marked with <literal>datistemplate = true</>.
</para>
- <para>
- After preparing a template database, or making any changes to one,
- it is a good idea to perform <command>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 <quote>frozen</> and will not be subject to transaction
- ID wraparound problems. This is particularly important for a database
- that will have <literal>datallowconn</literal> set to false, since it
- will be impossible to do routine maintenance <command>VACUUM</> in
- such a database.
- See <xref linkend="vacuum-for-wraparound"> for more information.
- </para>
-
<note>
<para>
<literal>template1</> and <literal>template0</> do not have any special
<!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/vacuum.sgml,v 1.42 2006/10/31 01:52:31 neilc Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/vacuum.sgml,v 1.43 2006/11/05 22:42:07 tgl Exp $
PostgreSQL documentation
-->
<refsynopsisdiv>
<synopsis>
-VACUUM [ FULL | FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table</replaceable> ]
-VACUUM [ FULL | FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table</replaceable> [ (<replaceable class="PARAMETER">column</replaceable> [, ...] ) ] ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] [ <replaceable class="PARAMETER">table</replaceable> ]
+VACUUM [ FULL ] [ FREEZE ] [ VERBOSE ] ANALYZE [ <replaceable class="PARAMETER">table</replaceable> [ (<replaceable class="PARAMETER">column</replaceable> [, ...] ) ] ]
</synopsis>
</refsynopsisdiv>
blocks. This form is much slower and requires an exclusive lock on each
table while it is being processed.
</para>
-
- <para>
- <literal>FREEZE</literal> is a special-purpose option that
- causes tuples to be marked <quote>frozen</quote> 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 <quote>frozen</> and will not be
- subject to transaction ID wraparound problems, no matter how long the
- database is left unvacuumed.
- <literal>FREEZE</literal> 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 <command>VACUUM</> operations.
- See <xref linkend="maintenance"> for details.
- </para>
</refsect1>
<refsect1>
<listitem>
<para>
Selects aggressive <quote>freezing</quote> of tuples.
+ Specifying <literal>FREEZE</literal> is equivalent to performing
+ <command>VACUUM</command> with the
+ <xref linkend="guc-vacuum-freeze-min-age"> parameter
+ set to zero. The <literal>FREEZE</literal> option is deprecated and
+ will be removed in a future release; set the parameter instead.
</para>
</listitem>
</varlistentry>
it is sometimes advisable to use the cost-based vacuum delay feature.
See <xref linkend="runtime-config-resource-vacuum-cost"> for details.
</para>
+
+ <para>
+ <productname>PostgreSQL</productname> includes an <quote>autovacuum</>
+ facility which can automate routine vacuum maintenance. For more
+ information about automatic and manual vacuuming, see
+ <xref linkend="routine-vacuuming">.
+ </para>
</refsect1>
<refsect1>
*
*
* 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
}
+/*
+ * 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
* ----------------
/*
* 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)
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.
while (unused < unend)
{
+ /* unused[] entries are zero-based */
lp = PageGetItemId(page, *unused + 1);
lp->lp_flags &= ~LP_USED;
unused++;
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)
{
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)
{
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");
+}
* 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 $
*
*-------------------------------------------------------------------------
*/
static int ZeroCLOGPage(int pageno, bool writeXlog);
static bool CLOGPagePrecedes(int page1, int page2);
static void WriteZeroPageXlogRec(int pageno);
+static void WriteTruncateXlogRec(int pageno);
/*
/*
* 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)
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);
(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
*/
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
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");
}
*
* 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"
{"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},
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
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,
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),
/*
* 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;
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;
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,
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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;
/*
* 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 $
*
*-------------------------------------------------------------------------
*/
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
*
*
*
* 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
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)
*/
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:
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;
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
vac_update_relstats(RelationGetRelid(onerel),
RelationGetNumberOfBlocks(onerel),
totalrows, hasindex,
- InvalidTransactionId, InvalidTransactionId);
+ InvalidTransactionId);
for (ind = 0; ind < nindexes; ind++)
{
vac_update_relstats(RelationGetRelid(Irel[ind]),
RelationGetNumberOfBlocks(Irel[ind]),
totalindexrows, false,
- InvalidTransactionId, InvalidTransactionId);
+ InvalidTransactionId);
}
/* report results to the stats collector, too */
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
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;
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",
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);
/*
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)
{
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)));
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;
/* 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;
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
#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"
#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"
#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.
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;
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,
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.
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);
}
/*
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
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"),
*/
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;
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;
}
/*
- * 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.
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;
}
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);
{
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);
}
}
/*
* 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"),
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);
}
{
relation_close(onerel, lmode);
CommitTransactionCommand();
- return; /* assume no long-lived data in temp tables */
+ return;
}
/*
int nindexes,
i;
VRelStats *vacrelstats;
- TransactionId FreezeLimit,
- OldestXmin;
vacuum_set_xid_limits(vacstmt, onerel->rd_rel->relisshared,
&OldestXmin, &FreezeLimit);
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);
{
/* Try to shrink heap */
repair_frag(vacrelstats, onerel, &vacuum_pages, &fraged_pages,
- nindexes, Irel, OldestXmin);
+ nindexes, Irel);
vac_close_indexes(nindexes, Irel, NoLock);
}
else
/* 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,
* 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;
Buffer buf;
OffsetNumber offnum,
maxoff;
- bool pgchanged,
- notup;
+ bool notup;
+ OffsetNumber frozen[MaxOffsetNumber];
+ int nfrozen;
vacuum_delay_point();
continue;
}
- pgchanged = false;
+ nfrozen = 0;
notup = true;
maxoff = PageGetMaxOffsetNumber(page);
for (offnum = FirstOffsetNumber;
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",
}
else
{
- TransactionId min;
-
num_tuples += 1;
notup = false;
if (tuple.t_len < min_tlen)
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 */
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);
}
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
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;
* 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.
* 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,
/* 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",
/* 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",
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
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,
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);
* 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.
Relation *Irel;
int nindexes;
BlockNumber possibly_freeable;
- TransactionId OldestXmin,
- FreezeLimit;
if (vacstmt->verbose)
elevel = INFO;
/* 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);
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);
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,
* 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;
Page page;
OffsetNumber offnum,
maxoff;
- bool pgchanged,
- tupgone,
+ bool tupgone,
hastup;
int prev_dead_count;
+ OffsetNumber frozen[MaxOffsetNumber];
+ int nfrozen;
vacuum_delay_point();
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);
continue;
}
- pgchanged = false;
+ nfrozen = 0;
hastup = false;
prev_dead_count = vacrelstats->num_dead_tuples;
maxoff = PageGetMaxOffsetNumber(page);
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",
}
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.
if (hastup)
vacrelstats->nonempty_pages = blkno + 1;
- if (pgchanged)
- MarkBufferDirty(buf);
UnlockReleaseBuffer(buf);
}
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",
* 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;
* 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)
{
* 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;
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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];
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");
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
*
*
* 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
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;
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;
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;
}
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;
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;
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
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;
{
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 */
Oid toastrelid;
bool dovacuum;
bool doanalyze;
+ int freeze_min_age;
int vacuum_cost_delay;
int vacuum_cost_limit;
} 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,
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);
/*
{
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 */
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
* 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)
/*
* 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 */
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");
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;
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);
}
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.
avRel;
HeapTuple tuple;
HeapScanDesc relScan;
+ Form_pg_database dbForm;
List *vacuum_tables = NIL;
List *toast_table_ids = NIL;
ListCell *cell;
*/
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
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);
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();
}
* 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
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
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) ?
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;
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)
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;
/*
* 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;
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);
* bother to report "<IDLE>" 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);
*
*
* 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
*
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
* (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 &&
/*
* 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)
{
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);
*
*
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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))
{
* 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 $
*
*-------------------------------------------------------------------------
*/
/*
* 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
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
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);
}
}
}
/*
- * 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);
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);
}
*
*
* 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 $
*
*
*-------------------------------------------------------------------------
char *filename;
FILE *db_file;
char thisname[NAMEDATALEN];
- TransactionId dummyxid;
+ TransactionId db_frozenxid;
filename = database_getflatfilename();
db_file = AllocateFile(filename, "r");
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)
{
* Written by Peter Eisentraut <peter_e@gmx.net>.
*
* 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 $
*
*--------------------------------------------------------------------
*/
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."),
&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,
# 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
#default_transaction_isolation = 'read committed'
#default_transaction_read_only = off
#statement_timeout = 0 # 0 is disabled
+#vacuum_freeze_min_age = 100000000
# - Locale and Formatting -
* 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 $
*
*-------------------------------------------------------------------------
*/
{
/*
* "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))
{
* 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
/* 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);
* 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 $
*
*-------------------------------------------------------------------------
*/
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);
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,
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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
#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 */
*
* 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
/*
* 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
#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
* 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 $
*
*-------------------------------------------------------------------------
*/
* 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)
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 */
/* 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);
* 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
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 */
* 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 $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200609181
+#define CATALOG_VERSION_NO 200611051
#endif
* 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
{ 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));
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));
* 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 $
*
*-------------------------------------------------------------------------
*/
#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;
/* ----------------
* 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
#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 */
* 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
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.
/* 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
* ----------------
*/
-#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
#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 */
* 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
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) */
* 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
#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
* 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 $
*
*-------------------------------------------------------------------------
*/
} 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 */
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);
* 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 $
*
*-------------------------------------------------------------------------
*/
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 */
* 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 $
*
*-------------------------------------------------------------------------
*/
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;
* 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 $
*
*-------------------------------------------------------------------------
*/
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;