]> granicus.if.org Git - postgresql/commitdiff
Fix recently-understood problems with handling of XID freezing, particularly
authorTom Lane <tgl@sss.pgh.pa.us>
Sun, 5 Nov 2006 22:42:10 +0000 (22:42 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sun, 5 Nov 2006 22:42:10 +0000 (22:42 +0000)
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.

43 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/config.sgml
doc/src/sgml/maintenance.sgml
doc/src/sgml/manage-ag.sgml
doc/src/sgml/ref/vacuum.sgml
src/backend/access/heap/heapam.c
src/backend/access/transam/clog.c
src/backend/access/transam/rmgr.c
src/backend/access/transam/varsup.c
src/backend/access/transam/xact.c
src/backend/access/transam/xlog.c
src/backend/catalog/heap.c
src/backend/commands/analyze.c
src/backend/commands/dbcommands.c
src/backend/commands/vacuum.c
src/backend/commands/vacuumlazy.c
src/backend/libpq/hba.c
src/backend/nodes/copyfuncs.c
src/backend/nodes/equalfuncs.c
src/backend/parser/gram.y
src/backend/postmaster/autovacuum.c
src/backend/postmaster/postmaster.c
src/backend/storage/ipc/procarray.c
src/backend/utils/init/flatfiles.c
src/backend/utils/init/postinit.c
src/backend/utils/misc/guc.c
src/backend/utils/misc/postgresql.conf.sample
src/backend/utils/time/tqual.c
src/include/access/clog.h
src/include/access/heapam.h
src/include/access/htup.h
src/include/access/rmgr.h
src/include/access/transam.h
src/include/access/xlog.h
src/include/catalog/catversion.h
src/include/catalog/pg_attribute.h
src/include/catalog/pg_autovacuum.h
src/include/catalog/pg_class.h
src/include/catalog/pg_database.h
src/include/commands/vacuum.h
src/include/libpq/hba.h
src/include/nodes/parsenodes.h
src/include/postmaster/autovacuum.h

index af28a26b1a03edecf422137edf3d42d2ee4f90e8..1ade58644e9863edbde33d5b1efd29619c6e93df 100644 (file)
@@ -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
  -->
       <entry></entry>
       <entry>Custom <varname>vacuum_cost_limit</> parameter</entry>
      </row>
+
+     <row>
+      <entry><structfield>freeze_min_age</structfield></entry>
+      <entry><type>integer</type></entry>
+      <entry></entry>
+      <entry>Custom <varname>vacuum_freeze_min_age</> parameter</entry>
+     </row>
+
+     <row>
+      <entry><structfield>freeze_max_age</structfield></entry>
+      <entry><type>integer</type></entry>
+      <entry></entry>
+      <entry>Custom <varname>autovacuum_freeze_max_age</> parameter</entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
    live tuples currently estimated to be in the relation.
   </para>
 
+  <para>
+   Also, the autovacuum daemon will perform a <command>VACUUM</> operation
+   to prevent transaction ID wraparound if the table's
+   <structname>pg_class</>.<structfield>relfrozenxid</> field attains an age
+   of more than <structfield>freeze_max_age</> transactions, whether the table
+   has been changed or not.  The system will launch autovacuum to perform
+   such <command>VACUUM</>s even if autovacuum is otherwise disabled.
+   See <xref linkend="vacuum-for-wraparound"> for more about wraparound
+   prevention.
+  </para>
+
   <para>
    Any of the numerical fields can contain <literal>-1</> (or indeed
    any negative value) to indicate that the system-wide default should
    <varname>autovacuum_vacuum_cost_delay</> configuration parameter,
    or from <varname>vacuum_cost_delay</> if the former is set to a negative
    value.  The same applies to <structfield>vac_cost_limit</>.
+   Also, autovacuum will ignore attempts to set a per-table
+   freeze_max_age larger than the system-wide setting (it can only be set
+   smaller), and the freeze_min_age value will be limited to half the
+   system-wide <varname>autovacuum_freeze_max_age</> setting.
   </para>
 
  </sect1>
      </row>
 
      <row>
-      <entry><structfield>relminxid</structfield></entry>
+      <entry><structfield>relfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
       <entry>
-       The minimum transaction ID present in all rows in this table.  This
-       value is used to determine the database-global
-       <structname>pg_database</>.<structfield>datminxid</> value.
-      </entry>
-     </row>
-
-     <row>
-      <entry><structfield>relvacuumxid</structfield></entry>
-      <entry><type>xid</type></entry>
-      <entry></entry>
-      <entry>
-       The transaction ID that was used as cleaning point as of the last vacuum
-       operation.  All rows inserted, updated or deleted in this table by
-       transactions whose IDs are below this one have been marked as known good
-       or deleted.  This is used to determine the database-global
-       <structname>pg_database</>.<structfield>datvacuumxid</> value.
+       All transaction IDs before this one have been replaced with a permanent
+       (<quote>frozen</>) transaction ID in this table.  This is used to track
+       whether the table needs to be vacuumed in order to prevent transaction
+       ID wraparound or to allow <literal>pg_clog</> to be shrunk.  Zero
+       (<symbol>InvalidTransactionId</symbol>) if the relation is not a table.
       </entry>
      </row>
 
      </row>
 
      <row>
-      <entry><structfield>datvacuumxid</structfield></entry>
-      <entry><type>xid</type></entry>
-      <entry></entry>
-      <entry>
-       The transaction ID that was used as cleaning point as of the last vacuum
-       operation.  All rows inserted or deleted by transaction IDs before this one
-       have been marked as known good or deleted.  This
-       is used to determine when commit-log space can be recycled.
-       If <symbol>InvalidTransactionId</symbol>, then the minimum is unknown and can be
-       determined by scanning <structname>pg_class</>.<structfield>relvacuumxid</>.
-      </entry>
-     </row>
-
-     <row>
-      <entry><structfield>datminxid</structfield></entry>
+      <entry><structfield>datfrozenxid</structfield></entry>
       <entry><type>xid</type></entry>
       <entry></entry>
       <entry>
-       The minimum transaction ID present in all tables in this database.
-       All rows inserted by transaction IDs before this one have been
-       relabeled with a permanent (<quote>frozen</>) transaction ID in this
-       database.  This is useful to check whether a database must be
-       vacuumed soon to avoid transaction ID wrap-around problems.
-       If <symbol>InvalidTransactionId</symbol>, then the minimum is unknown and can be
-       determined by scanning <structname>pg_class</>.<structfield>relminxid</>.
+       All transaction IDs before this one have been replaced with a permanent
+       (<quote>frozen</>) transaction ID in this database.  This is used to
+       track whether the database needs to be vacuumed in order to prevent
+       transaction ID wraparound or to allow <literal>pg_clog</> to be shrunk.
+       It is the minimum of the per-table
+       <structname>pg_class</>.<structfield>relfrozenxid</> values.
       </entry>
      </row>
 
index 8f0058b7ee8c1fc754ba25ec3fb44760a218a590..3f62a2853417c4015bd9d73a6c8a555a06289f2c 100644 (file)
@@ -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>
index 6033cf2c5ed114b9ca5ddcff5c37fd1fee0fcf2d..4b321ca31bfc205a7b7f5996abc5099dcc6e7db3 100644 (file)
@@ -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>
    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>
 
     Recommended practice for most sites is to schedule a database-wide
     <command>VACUUM</> once a day at a low-usage time of day,
     supplemented by more frequent vacuuming of heavily-updated tables
-    if necessary. (Some installations with an extremely high
-    rate of data modification <command>VACUUM</command> busy tables as
-    often as once every few minutes.)  If you have multiple databases
+    if necessary. (Some installations with extremely high update rates
+    vacuum their busiest tables as often as once every few minutes.)
+    If you have multiple databases
     in a cluster, don't forget to <command>VACUUM</command> each one;
     the program <xref linkend="app-vacuumdb" endterm="app-vacuumdb-title">
     may be helpful.
     transactions that were in the past appear to be in the future &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
     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>
index fcc6e81b7b1a7f3da638a7399c85b9308c24f44b..e86cac91e4d1a376acd80f718b82f423bab6f8ca 100644 (file)
@@ -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
index 0764aa6807972d12aba1d8cdc1c6ecd57fd6c2f2..cf039113f644938e492fd8a176367fdb83036ab0 100644 (file)
@@ -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>
index 57acaf2bb8c896b8b69ac4d95c6baf88bf26bfd0..12775cc2db73fe89f020ab39cae6c1c9ec307bf4 100644 (file)
@@ -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");
+}
index f57bdefa3a0d3a97bea89ffc5993a0dad355dce2..5817239a374481cbea5848c5a85ae7d027aabb7d 100644 (file)
@@ -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");
 }
index 7db05c9c482bac4906fed9a2686b325a2e1b6015..08de22eaa4abdfa1365af2ce6c71d15d2c01f766 100644 (file)
@@ -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},
index 04e9840cb5d9060e3db444c6939ca01514b98756..a8abaaea35cf0721bc4f0594f79333dae388b38f 100644 (file)
@@ -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,
index 8e1724989cb4d2038c4bea29aba638637f6d4b23..3c6e2ebf5cdc9bff3bb8dd49583130d9ee65d2bf 100644 (file)
@@ -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;
 
        /*
index ba029397f871b4ca4508d94825e71daf9ea0f0a8..03440cbf48ba92b240b2a61587859c8a9db7c1c8 100644 (file)
@@ -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
  *
index d30556d48c15d0b71fc2a4aca70a1836a263ed63..d6822c73c698fc4af7c529f6e0188247e36c50c4 100644 (file)
@@ -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;
index cb538bdd42b52d62bfa320c1c6ca2f54791ad934..95410258d34ec07b85afb15c385c348b03690fe6 100644 (file)
@@ -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 */
index cd327bb8d15a6120cdb3839efd1fd1e7096f05c5..566d559c78a95a6b8e564bd05b3b51772c28d4d2 100644 (file)
@@ -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;
index e9f0bf363ea9109ae9fb2db3d8b5b4a3c708fe0d..b15fcc1059f8c21692282abfadc004a4ed749bad 100644 (file)
@@ -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"
 #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",
index c89dc20404c97b2675743367c1c0c28a1862fe05..b57ce14a70069d512d8d5b41174e79da5eefbe0a 100644 (file)
@@ -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;
index 776d167ff2e66d789aaadab789ca501f37f44ab1..cfacd208c6b29cf78cdd8253e7aaa1a99b9061b3 100644 (file)
@@ -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");
index 8efe73904c0fe3d00df76e0d4f7532a2556498bf..c1858e6746d101f47547a1ab4e67c32898c1f372 100644 (file)
@@ -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);
 
index 0ac818f8a292334371070f9da6180b6f2810c7b4..a42afb77a3da11e9c124becb7da6a81fb90e00de 100644 (file)
@@ -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);
 
index 1d1e105c74ecebe3a1dfd7be91cf44730de73df2..c90743a1017b7d33b31c02c782dac3f3791fb2da 100644 (file)
@@ -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;
index 2ba12c2f9e66e71eba65e0f2ece30a9ef3ecd331..11552c9464428dc9bdff57d3099dfc3559081e65 100644 (file)
@@ -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);
index ed4de7269806cbac8c76352871b4106a9db30392..caf7d9a82d47663adc3d42e8e525466108804d88 100644 (file)
@@ -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);
index 39de167fe561d510f99ca5e349dd86bb6cb8c9b0..b037e594941d22d97c9fecd002af9f0cd93a1d3e 100644 (file)
@@ -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))
        {
index 8867ec3e015fc520582de52978bc42821fc99b3d..dc117145fc0182479803bf906e2f1f16b311e9bf 100644 (file)
@@ -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);
 }
 
 
index 9ab8c9ba97f31659ca6e37b6a84fd05fe4d14187..82532a196b00136a2e380815c392b9b8a08a2064 100644 (file)
@@ -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)
                {
index f0ff66fa66a1f01a818ddbd8c1eba476154c4524..5bbf89da5bd7420d2efd0a313256b547d43b30a2 100644 (file)
@@ -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,
index 1d0e842bba07c4e6a260953790c8f7186f9fe728..3a0dedba672082cd9d1b29514164b53a12413e15 100644 (file)
                                        # vacuum
 #autovacuum_analyze_scale_factor = 0.1 # fraction of rel size before 
                                        # analyze
+#autovacuum_freeze_max_age = 200000000 # maximum XID age before forced vacuum
+                                       # (change requires restart)
 #autovacuum_vacuum_cost_delay = -1     # default vacuum cost delay for 
                                        # autovacuum, -1 means use 
                                        # vacuum_cost_delay
 #default_transaction_isolation = 'read committed'
 #default_transaction_read_only = off
 #statement_timeout = 0                 # 0 is disabled
+#vacuum_freeze_min_age = 100000000
 
 # - Locale and Formatting -
 
index eedc6222be841a8d26f90e8a9fceb415c0c46f26..2bdb6d9e7141ee558e373fd6d3383a2d8cece73e 100644 (file)
@@ -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))
                {
index 999f15bdf7f37de828d695cfaf1a627a4e2ddece..0cb76000a29b8ef556d7c7edc08e96e65b1c26fa 100644 (file)
@@ -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);
index b2dd0f3390d3412413f0f70c92403a08ed855a22..4b3dd57534c21a75f7465291b19c47a860bfb20d 100644 (file)
@@ -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,
index edfce82bc044dfd1bb65cbfee2959293c09a4589..ed1f082de05b2f4ace750579471abcd8044413c7 100644 (file)
@@ -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 */
index 471b0cfb76475a6e9ffdba9e4de988bd88de55dc..7be2dfc9f657528fe2bb7dfa122525373b05395e 100644 (file)
@@ -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
index f1b91145f6755cdfd181190a975f415f5f756a9c..96cc65f94d53089dd66551e575e9978bcad3ecc0 100644 (file)
@@ -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);
 
index a5ae94b91aa517ef292fb56ef3465150cc301ec7..93c95aa462728fda850f6d3e48864751e4dfa583 100644 (file)
@@ -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 */
index f5da840d2d022e1a718f34a3dc8ab8c1c19041b8..2f61b94675826298761b312fef2e3d086ca31ae8 100644 (file)
@@ -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
index b96a5b83404dcf9ecd5a0d827c59989caec6b317..3fcc5ed67469ab9da61894b77fe94313b75f4b98 100644 (file)
@@ -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));
index 5e3db86a43b38d6de321a56c8f050ea68c6dc6dd..e46da9c649d2a818e3fae8eec258696ecba6a6ee 100644 (file)
@@ -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 $
  *
  *-------------------------------------------------------------------------
  */
 #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 */
 
index aa0c02ca1d9ae88fc8efce1503b5e8a6549cec14..75aee92e51261acce0f6a69e6f3d05f00e9f390d 100644 (file)
@@ -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 _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 _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 _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 */
index d62a4b949289a0018370dfe7ccf56f85e00e84fc..c3d80bebcb763b1c449e9da7f211d4194d1e4b9d 100644 (file)
@@ -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 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
 
index fed481971d2bbbe94dd5212ef15ff262ac83df70..5808a581cc561810361d76bee208bb701082313e 100644 (file)
@@ -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);
index eb8539f1399b3949c4e1a77e410adfe45b414b4b..155db7314d5310fec48aa4325802f7feb7fa1c64 100644 (file)
@@ -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 */
index 4f7351236f65cb5c7e6f5ae676e397effd4aad70..f79bf2907ce29bd3ca88a28ad2bb0947a70e4ff3 100644 (file)
@@ -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;
index 3de77f2ed177873ebacf680d416254ee8cb7a03b..b36334fce8e03be611dffd773995f411c389307a 100644 (file)
@@ -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;