From 62d37de2ddad70e168095c8b671f38cb144a9228 Mon Sep 17 00:00:00 2001
From: Radim Marek <radim@boringsql.com>
Date: Sun, 27 Sep 2026 08:46:18 -0700
Subject: [PATCH v3] doc: Document REPACK limits and point to VACUUM for
 wraparound

REPACK (CONCURRENTLY) uses some memory per concurrently updated or
deleted row and fails after about 100 million of them.  Document that,
and say that VACUUM, not REPACK, is the tool to prevent wraparound.

Discussion: https://postgr.es/m/CAJgoLk+dodrwuwCuXERGYwgjQzSwrKd+xgitYrL32oWDt39zvA@mail.gmail.com
---
 doc/src/sgml/ref/repack.sgml | 71 ++++++++++++++++++++++++------------
 1 file changed, 48 insertions(+), 23 deletions(-)

diff --git a/doc/src/sgml/ref/repack.sgml b/doc/src/sgml/ref/repack.sgml
index 346cba89c90..a85f73f07ec 100644
--- a/doc/src/sgml/ref/repack.sgml
+++ b/doc/src/sgml/ref/repack.sgml
@@ -165,14 +165,19 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
    </para>
 
    <para>
     It is advisable to set <xref linkend="guc-maintenance-work-mem"/> to a
     reasonably large value (but not more than the amount of RAM you can
     dedicate to the <command>REPACK</command> operation) before repacking.
    </para>
+
+   <para>
+    See below for additional resource requirements used when the
+    <literal>CONCURRENTLY</literal> option is given.
+   </para>
   </refsect2>
 
  </refsect1>
 
  <refsect1>
   <title>Parameters</title>
 
@@ -210,58 +215,61 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
     <term><literal>CONCURRENTLY</literal></term>
     <listitem>
      <para>
       Allow other transactions to use the table while it is being repacked.
      </para>
 
      <para>
-      Internally, <command>REPACK</command> copies the contents of the table
+      Without this option, <command>REPACK</command> copies the contents of the table
       (ignoring dead tuples) into a new file, sorted by the specified index,
       and also creates a new file for each index. Then it swaps the old and
       new files for the table and all the indexes, and deletes the old
       files. The <literal>ACCESS EXCLUSIVE</literal> lock is needed to make
       sure that the old files do not change during the processing because the
       changes would get lost due to the swap.
      </para>
 
      <para>
-      With the <literal>CONCURRENTLY</literal> option, the <literal>ACCESS
-      EXCLUSIVE</literal> lock is only acquired to swap the table and index
-      files. The data changes that took place during the creation of the new
+      With the <literal>CONCURRENTLY</literal> option, the bulk of the
+      operation is run with <literal>SHARE UPDATE EXCLUSIVE</literal> lock,
+      and the <literal>ACCESS EXCLUSIVE</literal> lock is only acquired
+      during the final phase to swap the table and index files.
+      The data changes that took place during the creation of the new
       table and index files are captured using logical decoding
-      (<xref linkend="logicaldecoding"/>) and applied before
-      the <literal>ACCESS EXCLUSIVE</literal> lock is requested. Thus the lock
-      is typically held only for the time needed to swap the files, which
-      should be pretty short. However, the time might still be noticeable if
-      too many data changes have been done to the table while
-      <command>REPACK</command> was waiting for the lock: those changes must
-      be processed just before the files are swapped, while the
-      <literal>ACCESS EXCLUSIVE</literal> lock is being held.
+      (see <xref linkend="logicaldecoding"/>) and applied before
+      the <literal>ACCESS EXCLUSIVE</literal> lock is requested.
+      Thus the lock is typically held only for the time needed to swap the
+      files, which should be pretty short.
+      However, the time might still be noticeable if too many data changes
+      have been done to the table while <command>REPACK</command> was
+      waiting for the lock: those changes must be processed just before
+      the files are swapped, while the <literal>ACCESS EXCLUSIVE</literal>
+      lock is being held.
      </para>
 
      <para>
-      Note that <command>REPACK</command> with the
-      <literal>CONCURRENTLY</literal> option does not try to order the rows
+      Note that <command>REPACK (CONCURRENTLY)</command>
+      does not try to order the rows
       inserted into the table after the repacking started. Also
-      note <command>REPACK</command> might fail to complete due to DDL
-      commands executed on the table by other transactions during the
+      note <command>REPACK</command> might fail to complete if DDL
+      commands are executed on the table by other transactions during the
       repacking.
      </para>
 
      <note>
       <para>
        In addition to the temporary space requirements explained in
        <xref linkend="sql-repack-notes-on-resources"/>,
-       the <literal>CONCURRENTLY</literal> option can add to the usage of
-       temporary space a bit more. The reason is that other transactions can
-       perform DML operations which cannot be applied to the new file until
-       <command>REPACK</command> has copied all the existing tuples from the
-       old file. Thus the tuples inserted into the old file during the copying
-       are also stored separately in a temporary file, until they can be
-       processed.
+       the <literal>CONCURRENTLY</literal> option causes the command
+       to require additional memory.
+       Each row that is updated or deleted takes a small additional amount
+       (16 bytes per row), not limited by <xref linkend="guc-maintenance-work-mem"/>,
+       and the command fails if more than about 104 million rows
+       are concurrently updated or deleted; though depending on
+       system memory pressure, the actual limit might be lower.
       </para>
      </note>
 
      <para>
       The <literal>CONCURRENTLY</literal> option cannot be used in the
       following cases:
 
@@ -380,14 +388,31 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING
   <title>Notes</title>
 
    <para>
     To repack a table, one must have the <literal>MAINTAIN</literal> privilege
     on the table.
    </para>
 
+   <para>
+    <command>REPACK</command> is primarily meant to remove bloat and, with
+    <literal>USING INDEX</literal>, to cluster the table, in order to improve
+    performance.  Although it also advances the table's
+    <structfield>relfrozenxid</structfield> and <structfield>relminmxid</structfield>,
+    it is not a good way to prevent transaction ID or multixact ID wraparound:
+    it rewrites the whole table and all of its indexes, so it takes much longer
+    than <command>VACUUM</command>, and it can fail after most of the work
+    is done, especially with <literal>CONCURRENTLY</literal> on a busy table.
+    <command>VACUUM</command> is recommended for that instead (see
+    <xref linkend="vacuum-for-wraparound"/>).
+    When a table gets close to wraparound, <command>VACUUM</command> skips index
+    vacuuming on its own (see <xref linkend="guc-vacuum-failsafe-age"/>), and its
+    <link linkend="sql-vacuum"><literal>INDEX_CLEANUP OFF</literal></link>
+    option can do the same earlier.
+   </para>
+
    <para>
     <command>REPACK</command> refuses to process a table on which an invalid
     index exists.  Such indexes must be dropped or reindexed by the user ahead
     of time.
    </para>
 
    <para>
-- 
2.47.3

