From 2e0426ba321a0402de5211a1c2264330a8de9c99 Mon Sep 17 00:00:00 2001 From: Greg Stark Date: Thu, 31 Mar 2022 15:49:19 -0400 Subject: [PATCH v9 2/3] Update relfrozenxmin when truncating temp tables Make ON COMMIT DELETE ROWS reset relfrozenxmin and other table stats to the same values used in initial table creation. Otherwise even typical short-lived transactions in long-lived sessions using temporary tables can easily cause them to reach transaction wraparound and autovacuum cannot come to the rescue for temporary tables. Also optimize the relminmxid used for for temporary tables to be our own oldest MultiXactId instead of the globally oldest one. This avoids the expensive calculation of the latter on every transaction commit. This code path is also used by truncation of tables created within the same transaction. --- src/backend/access/heap/heapam_handler.c | 25 ++++++--- src/backend/access/transam/multixact.c | 15 ++++++ src/backend/catalog/heap.c | 64 ++++++++++++++++++++++++ src/include/access/multixact.h | 1 + 4 files changed, 99 insertions(+), 6 deletions(-) diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index e2e35b71eaa..8f18dcc79d2 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -586,11 +586,15 @@ heapam_relation_set_new_filelocator(Relation rel, SMgrRelation srel; /* - * 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. + * Initialize to the minimum XID that could put tuples in the + * table. Anything "removable" can't be inserted later or it would be + * immediately removable. */ - *freezeXid = RecentXmin; + if (IsBootstrapProcessingMode()) + *freezeXid = FirstNormalTransactionId; + else + *freezeXid = GetOldestNonRemovableTransactionId(rel); + Assert(TransactionIdIsNormal(*freezeXid)); /* * Similarly, initialize the minimum Multixact to the first value that @@ -598,9 +602,18 @@ heapam_relation_set_new_filelocator(Relation rel, * could reuse values from their local cache, so we are careful to * consider all currently running multis. * - * XXX this could be refined further, but is it worth the hassle? + * In the case of temporary tables we can refine this slightly and use a + * our own oldest visible MultiXactId. This is also cheaper to calculate + * which is nice since temporary tables might be getting created often. */ - *minmulti = GetOldestMultiXactId(); + if (persistence == RELPERSISTENCE_TEMP) + { + *minmulti = GetOurOldestMultiXactId(); + } + else + { + *minmulti = GetOldestMultiXactId(); + } srel = RelationCreateStorage(*newrlocator, persistence, true); diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index fe6698d5ffa..55b301f28f9 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -2491,6 +2491,21 @@ ExtendMultiXactMember(MultiXactOffset offset, int nmembers) } } +/* + * GetOurOldestMultiXactId + * + * Expose the oldest MultiXactId possibly seen as live by *this* + * transaction. This is mainly useful for initializing relminmxid on temp + * tables since they can't been modified by other transactions. + */ + +MultiXactId +GetOurOldestMultiXactId(void) +{ + MultiXactIdSetOldestVisible(); + return OldestVisibleMXactId[MyBackendId]; +} + /* * GetOldestMultiXactId * diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 2a0d82aedd7..d4c7d67b97f 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -30,6 +30,7 @@ #include "postgres.h" #include "access/genam.h" +#include "access/heapam.h" #include "access/multixact.h" #include "access/relation.h" #include "access/table.h" @@ -68,10 +69,12 @@ #include "pgstat.h" #include "storage/lmgr.h" #include "storage/predicate.h" +#include "storage/procarray.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" +#include "utils/snapmgr.h" #include "utils/syscache.h" @@ -2993,6 +2996,57 @@ RelationTruncateIndexes(Relation heapRelation) } } +/* + * Reset the relfrozenxid and other stats to the same values used when + * creating tables. This is used after non-transactional truncation. + * + * Doing this reduces the need for long-running programs to vacuum their own + * temporary tables (since they're not covered by autovacuum) at least in the + * case where they're ON COMMIT DELETE ROWS. + * + * see also src/backend/commands/vacuum.c vac_update_relstats() + * also see AddNewRelationTuple() above + */ + +static void +ResetVacStats(Relation rel) +{ + HeapTuple ctup; + Form_pg_class pgcform; + Relation classRel; + + /* Fetch a copy of the tuple to scribble on */ + classRel = table_open(RelationRelationId, RowExclusiveLock); + ctup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(RelationGetRelid(rel))); + if (!HeapTupleIsValid(ctup)) + elog(ERROR, "pg_class entry for relid %u vanished during truncation", + RelationGetRelid(rel)); + pgcform = (Form_pg_class) GETSTRUCT(ctup); + + pgcform->relpages = 0; + pgcform->reltuples = -1; + pgcform->relallvisible = 0; + if (IsBootstrapProcessingMode()) + pgcform->relfrozenxid = FirstNormalTransactionId; + else + pgcform->relfrozenxid = GetOldestNonRemovableTransactionId(rel); + Assert(TransactionIdIsNormal(pgcform->relfrozenxid)); + + /* + * If this is a temp table we can use the oldest mxid visible from this + * transaction. Temp tables are the important case since autovacuum can't + * get them. + */ + if (pgcform->relpersistence == RELPERSISTENCE_TEMP) + pgcform->relminmxid = GetOurOldestMultiXactId(); + else + pgcform->relminmxid = GetOldestMultiXactId(); + + heap_inplace_update(classRel, ctup); + + table_close(classRel, RowExclusiveLock); +} + /* * heap_truncate * @@ -3001,6 +3055,14 @@ RelationTruncateIndexes(Relation heapRelation) * This is not transaction-safe! There is another, transaction-safe * implementation in commands/tablecmds.c. We now use this only for * ON COMMIT truncation of temporary tables, where it doesn't matter. + * + * Or whenever a table's relfilenode was created within the same transaction + * such as when the table was created or truncated (normally) within this + * transaction. + * + * The correctness of this code depends on the fact that the table creation or + * truncation would be rolled back *including* the insert/update to the + * pg_class row that we update in place here. */ void heap_truncate(List *relids) @@ -3057,6 +3119,7 @@ heap_truncate_one_rel(Relation rel) /* Truncate the underlying relation */ table_relation_nontransactional_truncate(rel); + ResetVacStats(rel); /* If the relation has indexes, truncate the indexes too */ RelationTruncateIndexes(rel); @@ -3068,6 +3131,7 @@ heap_truncate_one_rel(Relation rel) Relation toastrel = table_open(toastrelid, AccessExclusiveLock); table_relation_nontransactional_truncate(toastrel); + ResetVacStats(toastrel); RelationTruncateIndexes(toastrel); /* keep the lock... */ table_close(toastrel, NoLock); diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h index 246f757f6ab..3cd49532d48 100644 --- a/src/include/access/multixact.h +++ b/src/include/access/multixact.h @@ -139,6 +139,7 @@ extern void MultiXactGetCheckptMulti(bool is_shutdown, MultiXactId *oldestMulti, Oid *oldestMultiDB); extern void CheckPointMultiXact(void); +extern MultiXactId GetOurOldestMultiXactId(void); extern MultiXactId GetOldestMultiXactId(void); extern void TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB); -- 2.40.0