From 48188e1621bb6711e7d092bee48523b18cd80177 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Sun, 5 Nov 2006 22:42:10 +0000
Subject: [PATCH] Fix recently-understood problems with handling of XID
 freezing, particularly in PITR scenarios.  We now WAL-log the replacement of
 old XIDs with FrozenTransactionId, so that such replacement is guaranteed to
 propagate to PITR slave databases.  Also, rather than relying on hint-bit
 updates to be preserved, pg_clog is not truncated until all instances of an
 XID are known to have been replaced by FrozenTransactionId.  Add new GUC
 variables and pg_autovacuum columns to allow management of the freezing
 policy, so that users can trade off the size of pg_clog against the amount of
 freezing work done.  Revise the already-existing code that forces autovacuum
 of tables approaching the wraparound point to make it more bulletproof; also,
 revise the autovacuum logic so that anti-wraparound vacuuming is done
 per-table rather than per-database.  initdb forced because of changes in
 pg_class, pg_database, and pg_autovacuum catalogs.  Heikki Linnakangas, Simon
 Riggs, and Tom Lane.

---
 doc/src/sgml/catalogs.sgml                    |  83 +--
 doc/src/sgml/config.sgml                      |  46 +-
 doc/src/sgml/maintenance.sgml                 | 240 +++++----
 doc/src/sgml/manage-ag.sgml                   |  15 +-
 doc/src/sgml/ref/vacuum.sgml                  |  33 +-
 src/backend/access/heap/heapam.c              | 297 ++++++++++-
 src/backend/access/transam/clog.c             |  74 ++-
 src/backend/access/transam/rmgr.c             |   4 +-
 src/backend/access/transam/varsup.c           |  82 ++-
 src/backend/access/transam/xact.c             |   8 +-
 src/backend/access/transam/xlog.c             |  32 +-
 src/backend/catalog/heap.c                    |  59 +--
 src/backend/commands/analyze.c                |   6 +-
 src/backend/commands/dbcommands.c             |  29 +-
 src/backend/commands/vacuum.c                 | 490 ++++++------------
 src/backend/commands/vacuumlazy.c             | 123 ++---
 src/backend/libpq/hba.c                       |  14 +-
 src/backend/nodes/copyfuncs.c                 |   4 +-
 src/backend/nodes/equalfuncs.c                |   4 +-
 src/backend/parser/gram.y                     |  12 +-
 src/backend/postmaster/autovacuum.c           | 415 +++++++--------
 src/backend/postmaster/postmaster.c           |  30 +-
 src/backend/storage/ipc/procarray.c           |   6 +-
 src/backend/utils/init/flatfiles.c            |  36 +-
 src/backend/utils/init/postinit.c             |   7 +-
 src/backend/utils/misc/guc.c                  |  20 +-
 src/backend/utils/misc/postgresql.conf.sample |   3 +
 src/backend/utils/time/tqual.c                |  10 +-
 src/include/access/clog.h                     |   3 +-
 src/include/access/heapam.h                   |  14 +-
 src/include/access/htup.h                     |  20 +-
 src/include/access/rmgr.h                     |   6 +-
 src/include/access/transam.h                  |   9 +-
 src/include/access/xlog.h                     |   3 +-
 src/include/catalog/catversion.h              |   4 +-
 src/include/catalog/pg_attribute.h            |  16 +-
 src/include/catalog/pg_autovacuum.h           |  20 +-
 src/include/catalog/pg_class.h                |  29 +-
 src/include/catalog/pg_database.h             |  18 +-
 src/include/commands/vacuum.h                 |  10 +-
 src/include/libpq/hba.h                       |   5 +-
 src/include/nodes/parsenodes.h                |   4 +-
 src/include/postmaster/autovacuum.h           |   3 +-
 43 files changed, 1284 insertions(+), 1062 deletions(-)

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