Set cutoff xmin more aggressively when vacuuming a temporary table.
authorTom Lane <[email protected]>
Tue, 1 Sep 2020 22:37:12 +0000 (18:37 -0400)
committerTom Lane <[email protected]>
Tue, 1 Sep 2020 22:40:43 +0000 (18:40 -0400)
Since other sessions aren't allowed to look into a temporary table
of our own session, we do not need to worry about the global xmin
horizon when setting the vacuum XID cutoff.  Indeed, if we're not
inside a transaction block, we may set oldestXmin to be the next
XID, because there cannot be any in-doubt tuples in a temp table,
nor any tuples that are dead but still visible to some snapshot of
our transaction.  (VACUUM, of course, is never inside a transaction
block; but we need to test that because CLUSTER shares the same code.)

This approach allows us to always clean out a temp table completely
during VACUUM, independently of concurrent activity.  Aside from
being useful in its own right, that simplifies building reproducible
test cases.

Discussion: https://postgr.es/m/3490536.1598629609@sss.pgh.pa.us

src/backend/access/heap/vacuumlazy.c
src/backend/commands/cluster.c
src/backend/commands/vacuum.c
src/include/commands/cluster.h
src/include/commands/vacuum.h

index 53b1a952543b72194a4abd58a7b251510b8e8385..92389e6666bc3667603ee5a98735bbf85fff0a31 100644 (file)
@@ -471,6 +471,7 @@ heap_vacuum_rel(Relation onerel, VacuumParams *params,
                          params->freeze_table_age,
                          params->multixact_freeze_min_age,
                          params->multixact_freeze_table_age,
+                         true, /* we must be a top-level command */
                          &OldestXmin, &FreezeLimit, &xidFullScanLimit,
                          &MultiXactCutoff, &mxactFullScanLimit);
 
index 04d12a7ece69855b12c86c468d17706b08795edf..0d647e912c471c3961d128ffa55460887a90491b 100644 (file)
@@ -67,10 +67,13 @@ typedef struct
 } RelToCluster;
 
 
-static void rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose);
+static void rebuild_relation(Relation OldHeap, Oid indexOid,
+                            bool isTopLevel, bool verbose);
 static void copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
-                           bool verbose, bool *pSwapToastByContent,
-                           TransactionId *pFreezeXid, MultiXactId *pCutoffMulti);
+                           bool isTopLevel, bool verbose,
+                           bool *pSwapToastByContent,
+                           TransactionId *pFreezeXid,
+                           MultiXactId *pCutoffMulti);
 static List *get_tables_to_cluster(MemoryContext cluster_context);
 
 
@@ -170,7 +173,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
        table_close(rel, NoLock);
 
        /* Do the job. */
-       cluster_rel(tableOid, indexOid, stmt->options);
+       cluster_rel(tableOid, indexOid, stmt->options, isTopLevel);
    }
    else
    {
@@ -219,7 +222,8 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
            PushActiveSnapshot(GetTransactionSnapshot());
            /* Do the job. */
            cluster_rel(rvtc->tableOid, rvtc->indexOid,
-                       stmt->options | CLUOPT_RECHECK);
+                       stmt->options | CLUOPT_RECHECK,
+                       isTopLevel);
            PopActiveSnapshot();
            CommitTransactionCommand();
        }
@@ -250,7 +254,7 @@ cluster(ClusterStmt *stmt, bool isTopLevel)
  * and error messages should refer to the operation as VACUUM not CLUSTER.
  */
 void
-cluster_rel(Oid tableOid, Oid indexOid, int options)
+cluster_rel(Oid tableOid, Oid indexOid, int options, bool isTopLevel)
 {
    Relation    OldHeap;
    bool        verbose = ((options & CLUOPT_VERBOSE) != 0);
@@ -400,7 +404,7 @@ cluster_rel(Oid tableOid, Oid indexOid, int options)
    TransferPredicateLocksToHeapRelation(OldHeap);
 
    /* rebuild_relation does all the dirty work */
-   rebuild_relation(OldHeap, indexOid, verbose);
+   rebuild_relation(OldHeap, indexOid, isTopLevel, verbose);
 
    /* NB: rebuild_relation does table_close() on OldHeap */
 
@@ -545,11 +549,12 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal)
  *
  * OldHeap: table to rebuild --- must be opened and exclusive-locked!
  * indexOid: index to cluster by, or InvalidOid to rewrite in physical order.
+ * isTopLevel: should be passed down from ProcessUtility.
  *
  * NB: this routine closes OldHeap at the right time; caller should not.
  */
 static void
-rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
+rebuild_relation(Relation OldHeap, Oid indexOid, bool isTopLevel, bool verbose)
 {
    Oid         tableOid = RelationGetRelid(OldHeap);
    Oid         tableSpace = OldHeap->rd_rel->reltablespace;
@@ -577,7 +582,7 @@ rebuild_relation(Relation OldHeap, Oid indexOid, bool verbose)
                               AccessExclusiveLock);
 
    /* Copy the heap data into the new table in the desired order */
-   copy_table_data(OIDNewHeap, tableOid, indexOid, verbose,
+   copy_table_data(OIDNewHeap, tableOid, indexOid, isTopLevel, verbose,
                    &swap_toast_by_content, &frozenXid, &cutoffMulti);
 
    /*
@@ -728,7 +733,8 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, char relpersistence,
  * *pCutoffMulti receives the MultiXactId used as a cutoff point.
  */
 static void
-copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
+copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
+               bool isTopLevel, bool verbose,
                bool *pSwapToastByContent, TransactionId *pFreezeXid,
                MultiXactId *pCutoffMulti)
 {
@@ -826,7 +832,7 @@ copy_table_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, bool verbose,
     * Since we're going to rewrite the whole table anyway, there's no reason
     * not to be aggressive about this.
     */
-   vacuum_set_xid_limits(OldHeap, 0, 0, 0, 0,
+   vacuum_set_xid_limits(OldHeap, 0, 0, 0, 0, isTopLevel,
                          &OldestXmin, &FreezeXid, NULL, &MultiXactCutoff,
                          NULL);
 
index 308a51d95d7adb3024a17fb5e0992861c2b67e14..ddeec870d8115b57d126d4f539bd5af17bdf1e07 100644 (file)
@@ -907,6 +907,9 @@ get_all_vacuum_rels(int options)
 /*
  * vacuum_set_xid_limits() -- compute oldestXmin and freeze cutoff points
  *
+ * Input parameters are the target relation, applicable freeze age settings,
+ * and isTopLevel which should be passed down from ProcessUtility.
+ *
  * The output parameters are:
  * - oldestXmin is the cutoff value used to distinguish whether tuples are
  *  DEAD or RECENTLY_DEAD (see HeapTupleSatisfiesVacuum).
@@ -931,6 +934,7 @@ vacuum_set_xid_limits(Relation rel,
                      int freeze_table_age,
                      int multixact_freeze_min_age,
                      int multixact_freeze_table_age,
+                     bool isTopLevel,
                      TransactionId *oldestXmin,
                      TransactionId *freezeLimit,
                      TransactionId *xidFullScanLimit,
@@ -946,32 +950,53 @@ vacuum_set_xid_limits(Relation rel,
    MultiXactId mxactLimit;
    MultiXactId safeMxactLimit;
 
-   /*
-    * 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 anywhere (usually no
-    * XID assigned), it's safe to ignore it.  In theory it could be
-    * problematic to ignore lazy vacuums in 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 is always an independent transaction.
-    */
-   *oldestXmin = GetOldestNonRemovableTransactionId(rel);
-
-   if (OldSnapshotThresholdActive())
+   if (RELATION_IS_LOCAL(rel) && !IsInTransactionBlock(isTopLevel))
+   {
+       /*
+        * If we are processing a temp relation (which by prior checks must be
+        * one belonging to our session), and we are not inside any
+        * transaction block, then there can be no tuples in the rel that are
+        * still in-doubt, nor can there be any that are dead but possibly
+        * still interesting to some snapshot our session holds.  We don't
+        * need to care whether other sessions could see such tuples, either.
+        * So we can aggressively set the cutoff xmin to be the nextXid.
+        */
+       *oldestXmin = ReadNewTransactionId();
+   }
+   else
    {
-       TransactionId limit_xmin;
-       TimestampTz limit_ts;
+       /*
+        * Otherwise, calculate the cutoff xmin normally.
+        *
+        * 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
+        * anywhere (usually no XID assigned), it's safe to ignore it.  In
+        * theory it could be problematic to ignore lazy vacuums in 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 is
+        * always an independent transaction.
+        */
+       *oldestXmin = GetOldestNonRemovableTransactionId(rel);
 
-       if (TransactionIdLimitedForOldSnapshots(*oldestXmin, rel, &limit_xmin, &limit_ts))
+       if (OldSnapshotThresholdActive())
        {
-           /*
-            * TODO: We should only set the threshold if we are pruning on the
-            * basis of the increased limits. Not as crucial here as it is for
-            * opportunistic pruning (which often happens at a much higher
-            * frequency), but would still be a significant improvement.
-            */
-           SetOldSnapshotThresholdTimestamp(limit_ts, limit_xmin);
-           *oldestXmin = limit_xmin;
+           TransactionId limit_xmin;
+           TimestampTz limit_ts;
+
+           if (TransactionIdLimitedForOldSnapshots(*oldestXmin, rel,
+                                                   &limit_xmin, &limit_ts))
+           {
+               /*
+                * TODO: We should only set the threshold if we are pruning on
+                * the basis of the increased limits.  Not as crucial here as
+                * it is for opportunistic pruning (which often happens at a
+                * much higher frequency), but would still be a significant
+                * improvement.
+                */
+               SetOldSnapshotThresholdTimestamp(limit_ts, limit_xmin);
+               *oldestXmin = limit_xmin;
+           }
        }
    }
 
@@ -1905,7 +1930,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params)
            cluster_options |= CLUOPT_VERBOSE;
 
        /* VACUUM FULL is now a variant of CLUSTER; see cluster.c */
-       cluster_rel(relid, InvalidOid, cluster_options);
+       cluster_rel(relid, InvalidOid, cluster_options, true);
    }
    else
        table_relation_vacuum(onerel, params, vac_strategy);
index e05884781b94d972129682070af3c59317daa1eb..1eb144204b6a865b42ed3da9fd37c839023a8ac0 100644 (file)
@@ -19,7 +19,8 @@
 
 
 extern void cluster(ClusterStmt *stmt, bool isTopLevel);
-extern void cluster_rel(Oid tableOid, Oid indexOid, int options);
+extern void cluster_rel(Oid tableOid, Oid indexOid, int options,
+                       bool isTopLevel);
 extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid,
                                       bool recheck, LOCKMODE lockmode);
 extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal);
index a4cd7214009ca3f87024f333ca944a6fb899f142..d9475c99890c361956310529684ff856e0edbbfd 100644 (file)
@@ -267,6 +267,7 @@ extern void vacuum_set_xid_limits(Relation rel,
                                  int freeze_min_age, int freeze_table_age,
                                  int multixact_freeze_min_age,
                                  int multixact_freeze_table_age,
+                                 bool isTopLevel,
                                  TransactionId *oldestXmin,
                                  TransactionId *freezeLimit,
                                  TransactionId *xidFullScanLimit,