From 916e6a6121f03b65f361e1ca064fb5d4b6181cee Mon Sep 17 00:00:00 2001
From: Peter Geoghegan <pg@bowt.ie>
Date: Sat, 22 Apr 2023 12:41:00 -0700
Subject: [PATCH v3 5/9] Move Interpreting XID stamps from tuple headers.

This is intended to be fairly close to a mechanical change.  It isn't
entirely mechanical, though, since the original wording has been
slightly modified for it to work in context.

Structuring things this way should make life a little easier for doc
translators.
---
 doc/src/sgml/maintenance.sgml |  80 ++++------------------
 doc/src/sgml/xact.sgml        | 125 ++++++++++++++++++++++++++++------
 2 files changed, 120 insertions(+), 85 deletions(-)

diff --git a/doc/src/sgml/maintenance.sgml b/doc/src/sgml/maintenance.sgml
index e130dfdbd..cb6f28e1e 100644
--- a/doc/src/sgml/maintenance.sgml
+++ b/doc/src/sgml/maintenance.sgml
@@ -447,75 +447,25 @@ analyze threshold = analyze base threshold + analyze scale factor * number of tu
     <secondary>wraparound</secondary>
    </indexterm>
 
-    <indexterm>
-     <primary>wraparound</primary>
-     <secondary>of transaction IDs</secondary>
-    </indexterm>
+   <indexterm>
+    <primary>wraparound</primary>
+    <secondary>of transaction IDs</secondary>
+   </indexterm>
 
    <para>
-    <productname>PostgreSQL</productname>'s
-    <link linkend="mvcc-intro">MVCC</link> transaction semantics
-    depend on being able to compare transaction ID (<acronym>XID</acronym>)
-    numbers: a row version with an insertion XID greater than the current
-    transaction's XID is <quote>in the future</quote> and should not be visible
-    to the current transaction.  But since transaction IDs have limited size
-    (32 bits) a cluster that runs for a long time (more
-    than 4 billion transactions) would suffer <firstterm>transaction ID
-    wraparound</firstterm>: the XID counter wraps around to zero, and all of a sudden
-    transactions that were in the past appear to be in the future &mdash; which
-    means their output become invisible.  In short, catastrophic data loss.
-    (Actually the data is still there, but that's cold comfort if you cannot
-    get at it.)  To avoid this, it is necessary to vacuum every table
-    in every database at least once every two billion transactions.
+    <productname>PostgreSQL</productname>'s <link
+     linkend="mvcc-intro">MVCC</link> transaction semantics depend on
+    being able to compare <glossterm linkend="glossary-xid">transaction
+     ID numbers (<acronym>XID</acronym>)</glossterm> to determine
+    whether or not the row is visible to each query's MVCC snapshot
+    (see <xref linkend="interpreting-xid-stamps"/>).  But since
+    on-disk storage of transaction IDs in heap pages uses a truncated
+    32-bit representation to save space (rather than the full 64-bit
+    representation), it is necessary to vacuum every table in every
+    database <emphasis>at least</emphasis> once every two billion
+    transactions (though far more frequent vacuuming is typical).
    </para>
 
-   <para>
-    The reason that periodic vacuuming solves the problem is that
-    <command>VACUUM</command> will mark rows as <emphasis>frozen</emphasis>, indicating that
-    they were inserted by a transaction that committed sufficiently far in
-    the past that the effects of the inserting transaction are certain to be
-    visible to all current and future transactions.
-    Normal XIDs are
-    compared using modulo-2<superscript>32</superscript> arithmetic. This means
-    that for every normal XID, there are two billion XIDs that are
-    <quote>older</quote> and two billion that are <quote>newer</quote>; another
-    way to say it is that the normal XID space is circular with no
-    endpoint. Therefore, once a row version has been created with a particular
-    normal XID, the row version will appear to be <quote>in the past</quote> for
-    the next two billion transactions, no matter which normal XID we are
-    talking about. If the row version still exists after more than two billion
-    transactions, it will suddenly appear to be in the future. To
-    prevent this, <productname>PostgreSQL</productname> reserves a special XID,
-    <literal>FrozenTransactionId</literal>, which does not follow the normal XID
-    comparison rules and is always considered older
-    than every normal XID.
-    Frozen row versions are treated as if the inserting XID were
-    <literal>FrozenTransactionId</literal>, so that they will appear to be
-    <quote>in the past</quote> to all normal transactions regardless of wraparound
-    issues, and so such row versions will be valid until deleted, no matter
-    how long that is.
-   </para>
-
-   <note>
-    <para>
-     In <productname>PostgreSQL</productname> versions before 9.4, freezing was
-     implemented by actually replacing a row's insertion XID
-     with <literal>FrozenTransactionId</literal>, which was visible in the
-     row's <structname>xmin</structname> system column.  Newer versions just set a flag
-     bit, preserving the row's original <structname>xmin</structname> for possible
-     forensic use.  However, rows with <structname>xmin</structname> equal
-     to <literal>FrozenTransactionId</literal> (2) may still be found
-     in databases <application>pg_upgrade</application>'d from pre-9.4 versions.
-    </para>
-    <para>
-     Also, system catalogs may contain rows with <structname>xmin</structname> equal
-     to <literal>BootstrapTransactionId</literal> (1), indicating that they were
-     inserted during the first phase of <application>initdb</application>.
-     Like <literal>FrozenTransactionId</literal>, this special XID is treated as
-     older than every normal XID.
-    </para>
-   </note>
-
    <para>
     <xref linkend="guc-vacuum-freeze-min-age"/>
     controls how old an XID value has to be before rows bearing that XID will be
diff --git a/doc/src/sgml/xact.sgml b/doc/src/sgml/xact.sgml
index b467660ee..0762442e1 100644
--- a/doc/src/sgml/xact.sgml
+++ b/doc/src/sgml/xact.sgml
@@ -22,6 +22,8 @@
    single-statement transactions.
   </para>
 
+  <sect2 id="virtual-xids">
+   <title>Virtual Transaction IDs</title>
   <para>
    Every transaction is identified by a unique
    <literal>VirtualTransactionId</literal> (also called
@@ -46,29 +48,111 @@
    started, particularly if the transaction started with statements that
    only performed database reads.
   </para>
+ </sect2>
 
-  <para>
-   The internal transaction ID type <type>xid</type> is 32 bits wide
-   and <link linkend="vacuum-for-wraparound">wraps around</link> every
-   4 billion transactions. A 32-bit epoch is incremented during each
-   wraparound. There is also a 64-bit type <type>xid8</type> which
-   includes this epoch and therefore does not wrap around during the
-   life of an installation;  it can be converted to xid by casting.
-   The functions in <xref linkend="functions-pg-snapshot"/>
-   return <type>xid8</type> values.  Xids are used as the
-   basis for <productname>PostgreSQL</productname>'s <link
-   linkend="mvcc">MVCC</link> concurrency mechanism and streaming
-   replication.
-  </para>
+  <sect2 id="permanent-xids">
+   <title>Permanent Transaction IDs</title>
+   <para>
+    The internal transaction ID type <type>xid</type> is 32 bits wide
+    and wraps around every
+    4 billion transactions. A 32-bit epoch is incremented during each
+    wraparound. There is also a 64-bit type <type>xid8</type> which
+    includes this epoch and therefore does not wrap around during the
+    life of an installation;  it can be converted to xid by casting.
+    The functions in <xref linkend="functions-pg-snapshot"/>
+    return <type>xid8</type> values.  Xids are used as the
+    basis for <productname>PostgreSQL</productname>'s <link
+    linkend="mvcc">MVCC</link> concurrency mechanism and streaming
+    replication.
+   </para>
 
-  <para>
-   When a top-level transaction with a (non-virtual) xid commits,
-   it is marked as committed in the <filename>pg_xact</filename>
-   directory. Additional information is recorded in the
-   <filename>pg_commit_ts</filename> directory if <xref
-   linkend="guc-track-commit-timestamp"/> is enabled.
-  </para>
+   <para>
+    When a top-level transaction with a (non-virtual) xid commits,
+    it is marked as committed in the <filename>pg_xact</filename>
+    directory. Additional information is recorded in the
+    <filename>pg_commit_ts</filename> directory if <xref
+    linkend="guc-track-commit-timestamp"/> is enabled.
+   </para>
 
+  <sect3 id="interpreting-xid-stamps">
+   <title><type>TransactionId</type> comparison rules</title>
+   <para>
+    The system often needs to compare <structfield>t_xmin</structfield>
+    and <structfield>t_xmax</structfield> fields for MVCC snapshot
+    visibility checks.
+   </para>
+
+   <para>
+    We use a truncated 32-bit representation of transaction IDs, rather
+    than using the full 64-bit representation.  The 2.1 billion XIDs
+    <quote>distance</quote> invariant must be preserved because
+    transaction IDs stored in heap row headers use a truncated 32-bit
+    representation (rather than the full 64-bit representation).  Since
+    all unfrozen transaction IDs from heap tuple headers
+    <emphasis>must</emphasis> be from the same transaction ID epoch (or
+    from a space in the 64-bit representation that spans two adjoining
+    transaction ID epochs), there isn't any need to store a separate
+    epoch field in each tuple header (see <xref
+     linkend="interpreting-xid-stamps"/> for further details).  This
+    scheme has the advantage of requiring much less space than a design
+    that stores an XID epoch alongside each XID stored in each heap
+    tuple header.  It has the disadvantage of constraining the system's
+    ability to allocate new XIDs (in the worst case scenario where
+    <literal>xidStopLimit</literal> mode is used to preserve the
+    <quote>distance</quote> invariant).
+   </para>
+
+   <para>
+    <command>VACUUM</command> <link linkend="routine-vacuuming">will
+     mark tuple headers <emphasis>frozen</emphasis></link>, indicating
+    that all eligible rows on the page were inserted by a transaction
+    that committed sufficiently far in the past that the effects of the
+    inserting transaction are certain to be visible to all current and
+    future transactions.  Normal XIDs are compared using
+    modulo-2<superscript>32</superscript> arithmetic. This means that
+    for every normal XID, there are two billion XIDs that are
+    <quote>older</quote> and two billion that are <quote>newer</quote>;
+    another way to say it is that the normal XID space is circular with
+    no endpoint. Therefore, once a row version has been created with a
+    particular normal XID, the row version will appear to be <quote>in
+     the past</quote> for the next two billion transactions, no matter
+    which normal XID we are talking about. If the row version still
+    exists after more than two billion transactions, it will suddenly
+    appear to be in the future. To prevent this,
+    <productname>PostgreSQL</productname> reserves a special XID,
+    <literal>FrozenTransactionId</literal>, which does not follow the
+    normal XID comparison rules and is always considered older than
+    every normal XID.  Frozen row versions are treated as if the
+    inserting XID were <literal>FrozenTransactionId</literal>, so that
+    they will appear to be <quote>in the past</quote> to all normal
+    transactions regardless of wraparound issues, and so such row
+    versions will be valid until deleted, no matter how long that is.
+   </para>
+
+   <note>
+    <para>
+     In <productname>PostgreSQL</productname> versions before 9.4, freezing was
+     implemented by actually replacing a row's insertion XID
+     with <literal>FrozenTransactionId</literal>, which was visible in the
+     row's <structname>xmin</structname> system column.  Newer versions just set a flag
+     bit, preserving the row's original <structname>xmin</structname> for possible
+     forensic use.  However, rows with <structname>xmin</structname> equal
+     to <literal>FrozenTransactionId</literal> (2) may still be found
+     in databases <application>pg_upgrade</application>'d from pre-9.4 versions.
+    </para>
+    <para>
+     Also, system catalogs may contain rows with <structname>xmin</structname> equal
+     to <literal>BootstrapTransactionId</literal> (1), indicating that they were
+     inserted during the first phase of <application>initdb</application>.
+     Like <literal>FrozenTransactionId</literal>, this special XID is treated as
+     older than every normal XID.
+    </para>
+   </note>
+ </sect3>
+ </sect2>
+
+ <sect2 id="global-transaction-ids">
+  <title>Global Transaction Identifiers</title>
   <para>
    In addition to <literal>vxid</literal> and <type>xid</type>,
    prepared transactions are also assigned Global Transaction
@@ -77,6 +161,7 @@
    prepared transactions.  The mapping of GID to xid is shown in <link
    linkend="view-pg-prepared-xacts"><structname>pg_prepared_xacts</structname></link>.
   </para>
+ </sect2>
  </sect1>
 
  <sect1 id="xact-locking">
-- 
2.40.1

