On Wed, 5 Apr 2023 at 13:42, Andres Freund <and...@anarazel.de> wrote:
> > Somehow it doesn't feel right to use vac_update_relstats() in > heapam_handler.c. > > I also don't like that your patch references > heapam_relation_nontransactional_truncate in AddNewRelationTuple() - we > shouldn't add more comments piercing tableam than necessary. I'm really puzzled because this does look like it was in the last patch on the mailing list archive. But it's definitely not the code I have here. I guess I did some cleanup that I never posted, so sorry. I've attached patches using GetOldestNonRemovableTransactinId() and it seems to have fixed the race condition here. At least I can't reproduce it any more. > Not if you determine a relation specific xmin, and the relation is not a > shared relation. > > ISTM that the problem here really is that you're relying on RecentXmin, rather > than computing something more accurate. Why not use > GetOldestNonRemovableTransactionId(rel) - It's a bit more expensive, but I > don't think it'll matter compared to the cost of truncating the relation. I am a bit nervous about the overhead here because if your transaction touched *any* temporary tables then this gets called for *every* temporary table with ON COMMIT DELETE. That could be a lot and it's not obvious to users that having temporary tables will impose an overhead even if they're not actually using them. So I went ahead and used GetOldestNonRemovableTransactionId and tried to do some profiling. But this is on a cassert enabled build with -O0 so it's not serious profiling. I can repeat it on a real build if it matters. But it's been a long time since I've read gprof output. This is for -F PreCommit_on_commit_actions so the percentages are as a percent of just the precommit cleanup: index % time self children called name 0.00 0.00 10102/10102 CommitTransaction (1051) [1] 100.0 0.01 31.47 10102 PreCommit_on_commit_actions [1] 0.01 31.43 10100/10100 heap_truncate [2] 0.00 0.03 1005050/1005260 lappend_oid [325] ----------------------------------------------- 0.01 31.43 10100/10100 PreCommit_on_commit_actions [1] [2] 99.9 0.01 31.43 10100 heap_truncate [2] 0.09 27.30 1005050/1005050 heap_truncate_one_rel [3] 0.20 3.57 1005050/6087120 table_open <cycle 1> [465] 0.01 0.22 1005050/6045137 table_close [48] 0.00 0.03 1005050/1017744 lappend [322] 0.01 0.00 10100/10100 heap_truncate_check_FKs [425] ----------------------------------------------- 0.09 27.30 1005050/1005050 heap_truncate [2] [3] 87.0 0.09 27.30 1005050 heap_truncate_one_rel [3] 0.02 12.23 1005050/1005050 RelationTruncateIndexes [5] 0.06 10.08 1005050/1005050 ResetVacStats [7] 0.03 4.89 1005050/1005050 table_relation_nontransactional_truncate [12] I think this is saying that more than half the time is being spent just checking for indexes. There were no indexes on these temporary tables. Does not having any indexes cause the relcache treat it as a cache miss every time? 0.06 10.08 1005050/1005050 heap_truncate_one_rel [3] [7] 32.2 0.06 10.08 1005050 ResetVacStats [7] 0.02 3.83 1005050/1005250 SearchSysCacheCopy [16] 0.20 3.57 1005050/6087120 table_open <cycle 1> [465] 0.01 2.02 1005050/1005050 heap_inplace_update [35] 0.01 0.22 1005050/6045137 table_close [48] 0.00 0.20 1005050/1005150 GetOldestNonRemovableTransactionId [143] 0.00 0.01 1005050/1005150 GetOurOldestMultiXactId [421] 0.00 0.00 1005050/1008750 ObjectIdGetDatum [816] I guess this means GetOldestNonRemovableTransactionId is not the main cost in ResetVacStats though I don't understand why the syscache would be so slow. I think there's a facility for calculating the Horizons and then reusing them for a while but I don't see how to use that here. It would be appropriate I think. > > > Honestly I'm glad I wrote the test because it was hard to know whether > > my code was doing anything at all without it (and it wasn't in the > > first cut...) But I don't think there's much value in having it be in > > the regression suite. We don't generally write tests to ensure that a > > specific internal implementation behaves in the specific way it was > > written to. > > To me it seems important to test that your change actually does what it > intends to. Possibly the test needs to be relaxed some, but I do think we want > tests for the change. > > Greetings, > > Andres Freund -- greg
From 8dc1dc9d50e08c63d25a9c4c3e860453fbd9e531 Mon Sep 17 00:00:00 2001 From: Greg Stark <st...@mit.edu> Date: Thu, 31 Mar 2022 15:50:02 -0400 Subject: [PATCH v9 3/3] Add test for truncating temp tables advancing relfrozenxid This test depends on other transactions not running at the same time so that relfrozenxid can advance so it has to be moved to its own schedule. --- src/test/regress/expected/temp.out | 37 ++++++++++++++++++++++++++++++ src/test/regress/parallel_schedule | 10 +++++--- src/test/regress/sql/temp.sql | 30 ++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/test/regress/expected/temp.out b/src/test/regress/expected/temp.out index a5b3ed34a36..244b868ef74 100644 --- a/src/test/regress/expected/temp.out +++ b/src/test/regress/expected/temp.out @@ -82,6 +82,43 @@ SELECT * FROM temptest; ----- (0 rows) +DROP TABLE temptest; +-- Test that ON COMMIT DELETE ROWS resets the relfrozenxid when the +-- table is truncated. This requires this test not be run in parallel +-- with other tests as concurrent transactions will hold back the +-- globalxmin +CREATE TEMP TABLE temptest(col text) ON COMMIT DELETE ROWS; +SELECT reltoastrelid, reltoastrelid::regclass AS relname FROM pg_class where oid = 'temptest'::regclass \gset toast_ +SELECT relpages, reltuples, relfrozenxid FROM pg_class where oid = 'temptest'::regclass \gset old_ +SELECT relpages, reltuples, relfrozenxid FROM pg_class where oid = :toast_reltoastrelid \gset toast_old_ +BEGIN; +INSERT INTO temptest (select repeat('foobar',generate_series(1,1000))); +ANALYZE temptest; -- update relpages, reltuples +SELECT relpages, reltuples, relfrozenxid FROM pg_class where oid = 'temptest'::regclass \gset temp_ +SELECT relpages, reltuples, relfrozenxid FROM pg_class where oid = :toast_reltoastrelid \gset toast_temp_ +COMMIT; +SELECT relpages, reltuples, relfrozenxid FROM pg_class where oid = 'temptest'::regclass \gset new_ +SELECT relpages, reltuples, relfrozenxid FROM pg_class where oid = :toast_reltoastrelid \gset toast_new_ +-- make sure relpages and reltuple match a newly created table and +-- relfrozenxid is advanced +SELECT :old_relpages <> :temp_relpages AS pages_analyzed, + :old_relpages = :new_relpages AS pages_reset, + :old_reltuples <> :temp_reltuples AS tuples_analyzed, + :old_reltuples = :new_reltuples AS tuples_reset, + :old_relfrozenxid <> :new_relfrozenxid AS frozenxid_advanced; + pages_analyzed | pages_reset | tuples_analyzed | tuples_reset | frozenxid_advanced +----------------+-------------+-----------------+--------------+-------------------- + t | t | t | t | t +(1 row) + +-- The toast table can't be analyzed so relpages and reltuples can't +-- be tested easily make sure frozenxid is advanced +SELECT :toast_old_relfrozenxid <> :toast_new_relfrozenxid AS frozenxid_advanced; + frozenxid_advanced +-------------------- + t +(1 row) + DROP TABLE temptest; -- Test ON COMMIT DROP BEGIN; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 36240356393..be4ffca3f39 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -108,10 +108,14 @@ test: json jsonb json_encoding jsonpath jsonpath_encoding jsonb_jsonpath sqljson # ---------- # Another group of parallel tests # with depends on create_misc -# NB: temp.sql does a reconnect which transiently uses 2 connections, -# so keep this parallel group to at most 19 tests # ---------- -test: plancache limit plpgsql copy2 temp domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml +test: plancache limit plpgsql copy2 domain rangefuncs prepare conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml + +# ---------- +# Run this alone because it transiently uses 2 connections and also +# tests relfrozenxid advances when truncating temp tables +# ---------- +test: temp # ---------- # Another group of parallel tests diff --git a/src/test/regress/sql/temp.sql b/src/test/regress/sql/temp.sql index 424d12b2833..5f8647a8aa1 100644 --- a/src/test/regress/sql/temp.sql +++ b/src/test/regress/sql/temp.sql @@ -79,6 +79,36 @@ SELECT * FROM temptest; DROP TABLE temptest; +-- Test that ON COMMIT DELETE ROWS resets the relfrozenxid when the +-- table is truncated. This requires this test not be run in parallel +-- with other tests as concurrent transactions will hold back the +-- globalxmin +CREATE TEMP TABLE temptest(col text) ON COMMIT DELETE ROWS; + +SELECT reltoastrelid, reltoastrelid::regclass AS relname FROM pg_class where oid = 'temptest'::regclass \gset toast_ +SELECT relpages, reltuples, relfrozenxid FROM pg_class where oid = 'temptest'::regclass \gset old_ +SELECT relpages, reltuples, relfrozenxid FROM pg_class where oid = :toast_reltoastrelid \gset toast_old_ +BEGIN; +INSERT INTO temptest (select repeat('foobar',generate_series(1,1000))); +ANALYZE temptest; -- update relpages, reltuples +SELECT relpages, reltuples, relfrozenxid FROM pg_class where oid = 'temptest'::regclass \gset temp_ +SELECT relpages, reltuples, relfrozenxid FROM pg_class where oid = :toast_reltoastrelid \gset toast_temp_ +COMMIT; +SELECT relpages, reltuples, relfrozenxid FROM pg_class where oid = 'temptest'::regclass \gset new_ +SELECT relpages, reltuples, relfrozenxid FROM pg_class where oid = :toast_reltoastrelid \gset toast_new_ +-- make sure relpages and reltuple match a newly created table and +-- relfrozenxid is advanced +SELECT :old_relpages <> :temp_relpages AS pages_analyzed, + :old_relpages = :new_relpages AS pages_reset, + :old_reltuples <> :temp_reltuples AS tuples_analyzed, + :old_reltuples = :new_reltuples AS tuples_reset, + :old_relfrozenxid <> :new_relfrozenxid AS frozenxid_advanced; +-- The toast table can't be analyzed so relpages and reltuples can't +-- be tested easily make sure frozenxid is advanced +SELECT :toast_old_relfrozenxid <> :toast_new_relfrozenxid AS frozenxid_advanced; + +DROP TABLE temptest; + -- Test ON COMMIT DROP BEGIN; -- 2.40.0
From d6909ba259ed49e54c1de241acb150ef712e6a08 Mon Sep 17 00:00:00 2001 From: Greg Stark <st...@mit.edu> Date: Thu, 31 Mar 2022 15:48:38 -0400 Subject: [PATCH v9 1/3] Add warnings when old temporary tables are found to still be in use during autovacuum. Long lived sessions using temporary tables are required to vacuum them themselves. For the warning to be useful modify checkTempNamespaceStatus to return the backend pid using it so that we can inform super-user which pid to terminate. Otherwise it's quite tricky to determine as a user. Rename the function to avoid an incompatible ABI break. --- src/backend/access/transam/varsup.c | 12 ++++--- src/backend/catalog/namespace.c | 9 +++-- src/backend/postmaster/autovacuum.c | 52 ++++++++++++++++++++++------- src/include/catalog/namespace.h | 4 +-- 4 files changed, 57 insertions(+), 20 deletions(-) diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c index 334adac09e8..e2438e67597 100644 --- a/src/backend/access/transam/varsup.c +++ b/src/backend/access/transam/varsup.c @@ -129,14 +129,16 @@ GetNewTransactionId(bool isSubXact) errmsg("database is not accepting commands to avoid wraparound data loss in database \"%s\"", oldest_datname), errhint("Stop the postmaster and vacuum that database in single-user mode.\n" - "You might also need to commit or roll back old prepared transactions, or drop stale replication slots."))); + "You might also need to commit or roll back old prepared transactions,\n" + "drop temporary tables, or drop stale replication slots."))); else ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("database is not accepting commands to avoid wraparound data loss in database with OID %u", oldest_datoid), errhint("Stop the postmaster and vacuum that database in single-user mode.\n" - "You might also need to commit or roll back old prepared transactions, or drop stale replication slots."))); + "You might also need to commit or roll back old prepared transactions,\n" + "drop temporary tables, or drop stale replication slots."))); } else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit)) { @@ -149,14 +151,16 @@ GetNewTransactionId(bool isSubXact) oldest_datname, xidWrapLimit - xid), errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n" - "You might also need to commit or roll back old prepared transactions, or drop stale replication slots."))); + "You might also need to commit or roll back old prepared transactions,\n" + "drop temporary tables, or drop stale replication slots."))); else ereport(WARNING, (errmsg("database with OID %u must be vacuumed within %u transactions", oldest_datoid, xidWrapLimit - xid), errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n" - "You might also need to commit or roll back old prepared transactions, or drop stale replication slots."))); + "You might also need to commit or roll back old prepared transactions,\n" + "drop temporary tables, or drop stale replication slots."))); } /* Re-acquire lock and start over */ diff --git a/src/backend/catalog/namespace.c b/src/backend/catalog/namespace.c index 14e57adee2b..dfa7f8bc3db 100644 --- a/src/backend/catalog/namespace.c +++ b/src/backend/catalog/namespace.c @@ -3269,15 +3269,18 @@ isOtherTempNamespace(Oid namespaceId) /* * checkTempNamespaceStatus - is the given namespace owned and actively used - * by a backend? + * by a backend? Optionally return the pid of the owning backend if there is + * one. Returned pid is only meaningful when TEMP_NAMESPACE_IN_USE but note + * below about race conditions. * * Note: this can be used while scanning relations in pg_class to detect * orphaned temporary tables or namespaces with a backend connected to a * given database. The result may be out of date quickly, so the caller * must be careful how to handle this information. + * */ TempNamespaceStatus -checkTempNamespaceStatus(Oid namespaceId) +checkTempNamespaceStatusAndPid(Oid namespaceId, pid_t *pid) { PGPROC *proc; int backendId; @@ -3304,6 +3307,8 @@ checkTempNamespaceStatus(Oid namespaceId) return TEMP_NAMESPACE_IDLE; /* Yup, so namespace is busy */ + if (pid) + *pid = proc->pid; return TEMP_NAMESPACE_IN_USE; } diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c index 53c8f8d79cb..6e694aff0f2 100644 --- a/src/backend/postmaster/autovacuum.c +++ b/src/backend/postmaster/autovacuum.c @@ -2128,6 +2128,8 @@ do_autovacuum(void) bool dovacuum; bool doanalyze; bool wraparound; + TempNamespaceStatus temp_status; + pid_t temp_pid; if (classForm->relkind != RELKIND_RELATION && classForm->relkind != RELKIND_MATVIEW) @@ -2135,6 +2137,16 @@ do_autovacuum(void) relid = classForm->oid; + /* Fetch reloptions and the pgstat entry for this table */ + relopts = extract_autovac_opts(tuple, pg_class_desc); + tabentry = pgstat_fetch_stat_tabentry_ext(classForm->relisshared, + relid); + + /* Check if it needs vacuum or analyze */ + relation_needs_vacanalyze(relid, relopts, classForm, tabentry, + effective_multixact_freeze_max_age, + &dovacuum, &doanalyze, &wraparound); + /* * Check if it is a temp table (presumably, of some other backend's). * We cannot safely process other backends' temp tables. @@ -2146,7 +2158,8 @@ do_autovacuum(void) * using the temporary schema. Also, for safety, ignore it if the * namespace doesn't exist or isn't a temp namespace after all. */ - if (checkTempNamespaceStatus(classForm->relnamespace) == TEMP_NAMESPACE_IDLE) + temp_status = checkTempNamespaceStatusAndPid(classForm->relnamespace, &temp_pid); + if (temp_status == TEMP_NAMESPACE_IDLE) { /* * The table seems to be orphaned -- although it might be that @@ -2157,19 +2170,34 @@ do_autovacuum(void) */ orphan_oids = lappend_oid(orphan_oids, relid); } + else if (temp_status == TEMP_NAMESPACE_NOT_TEMP) + { + elog(LOG, "autovacuum: found temporary table \"%s.%s.%s\" in non-temporary namespace", + get_database_name(MyDatabaseId), + get_namespace_name(classForm->relnamespace), + NameStr(classForm->relname)); + } + else if (temp_status == TEMP_NAMESPACE_IN_USE && wraparound) + { + /* The table is not orphaned -- however it seems to be in need + * of a wraparound vacuum which we cannot do. Sessions using + * long-lived temporary tables need to be responsible for + * vacuuming them and failing to do so is endangering the + * whole cluster. + */ + ereport(LOG, + (errmsg("autovacuum: cannot vacuum temporary table \"%s.%s.%s\" in danger of causing transaction wraparound", + get_database_name(MyDatabaseId), + get_namespace_name(classForm->relnamespace), + NameStr(classForm->relname)), + errhint("Long-lived clients must vacuum temporary tables themselves periodically.\n" + "As super-user drop this table or terminate this session with pg_terminate_backend(%lu).", + (unsigned long)temp_pid) + )); + } continue; } - /* Fetch reloptions and the pgstat entry for this table */ - relopts = extract_autovac_opts(tuple, pg_class_desc); - tabentry = pgstat_fetch_stat_tabentry_ext(classForm->relisshared, - relid); - - /* Check if it needs vacuum or analyze */ - relation_needs_vacanalyze(relid, relopts, classForm, tabentry, - effective_multixact_freeze_max_age, - &dovacuum, &doanalyze, &wraparound); - /* Relations that need work are added to table_oids */ if (dovacuum || doanalyze) table_oids = lappend_oid(table_oids, relid); @@ -2316,7 +2344,7 @@ do_autovacuum(void) continue; } - if (checkTempNamespaceStatus(classForm->relnamespace) != TEMP_NAMESPACE_IDLE) + if (checkTempNamespaceStatusAndPid(classForm->relnamespace, NULL) != TEMP_NAMESPACE_IDLE) { UnlockRelationOid(relid, AccessExclusiveLock); continue; diff --git a/src/include/catalog/namespace.h b/src/include/catalog/namespace.h index f64a0ec26b9..bc66db0f09d 100644 --- a/src/include/catalog/namespace.h +++ b/src/include/catalog/namespace.h @@ -39,7 +39,7 @@ typedef struct _FuncCandidateList } *FuncCandidateList; /* - * Result of checkTempNamespaceStatus + * Result of checkTempNamespaceStatusAndPid */ typedef enum TempNamespaceStatus { @@ -155,7 +155,7 @@ extern bool isTempToastNamespace(Oid namespaceId); extern bool isTempOrTempToastNamespace(Oid namespaceId); extern bool isAnyTempNamespace(Oid namespaceId); extern bool isOtherTempNamespace(Oid namespaceId); -extern TempNamespaceStatus checkTempNamespaceStatus(Oid namespaceId); +extern TempNamespaceStatus checkTempNamespaceStatusAndPid(Oid namespaceId, pid_t *pid); extern int GetTempNamespaceBackendId(Oid namespaceId); extern Oid GetTempToastNamespace(void); extern void GetTempNamespaceState(Oid *tempNamespaceId, -- 2.40.0
From 2e0426ba321a0402de5211a1c2264330a8de9c99 Mon Sep 17 00:00:00 2001 From: Greg Stark <st...@mit.edu> 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