Hi, On 2020-09-21 14:20:03 -0700, Andres Freund wrote: > I can give it a try. I can see several paths of varying invasiveness, > not sure yet what the best approach is. Let me think about if for a bit.
Ugh, sorry for taking so long to get around to this. Attached is a *prototype* implemention of this concept, which clearly is lacking some comment work (and is intentionally lacking some re-indentation). I described my thoughts about how to limit the horizons for temp tables in https://www.postgresql.org/message-id/20201014203103.72oke6hqywcyhx7s%40alap3.anarazel.de Besides comments this probably mainly needs a bit more tests around temp table vacuuming. Should have at least an isolation test that verifies that temp table rows can be a) vacuumed b) pruned away in the presence of other sessions with xids. Greetings, Andres Freund
diff --git i/src/backend/commands/vacuum.c w/src/backend/commands/vacuum.c index ddeec870d81..8851fafed64 100644 --- i/src/backend/commands/vacuum.c +++ w/src/backend/commands/vacuum.c @@ -950,24 +950,8 @@ vacuum_set_xid_limits(Relation rel, MultiXactId mxactLimit; MultiXactId safeMxactLimit; - 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 - { - /* - * 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 diff --git i/src/backend/storage/ipc/procarray.c w/src/backend/storage/ipc/procarray.c index 07c5eeb7495..c3cfe34ffee 100644 --- i/src/backend/storage/ipc/procarray.c +++ w/src/backend/storage/ipc/procarray.c @@ -153,6 +153,8 @@ typedef struct ProcArrayStruct * I.e. the difference to GlobalVisCatalogRels is that * replication slot's catalog_xmin is not taken into account. * + * 4) GlobalVisTempRels, XXXX + * * GlobalVisTestFor(relation) returns the appropriate state * for the relation. * @@ -234,6 +236,9 @@ typedef struct ComputeXidHorizonsResult * defined tables. */ TransactionId data_oldest_nonremovable; + + TransactionId temp_oldest_nonremovable; + } ComputeXidHorizonsResult; @@ -257,12 +262,13 @@ static TransactionId standbySnapshotPendingXmin; /* * State for visibility checks on different types of relations. See struct - * GlobalVisState for details. As shared, catalog, and user defined + * GlobalVisState for details. As shared, catalog, normal and temporary * relations can have different horizons, one such state exists for each. */ static GlobalVisState GlobalVisSharedRels; static GlobalVisState GlobalVisCatalogRels; static GlobalVisState GlobalVisDataRels; +static GlobalVisState GlobalVisTempRels; /* * This backend's RecentXmin at the last time the accurate xmin horizon was @@ -1668,6 +1674,10 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) h->oldest_considered_running = initial; h->shared_oldest_nonremovable = initial; h->data_oldest_nonremovable = initial; + if (TransactionIdIsValid(MyProc->xid)) + h->temp_oldest_nonremovable = MyProc->xid; + else + h->temp_oldest_nonremovable = initial; } /* @@ -1760,6 +1770,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) TransactionIdOlder(h->shared_oldest_nonremovable, kaxmin); h->data_oldest_nonremovable = TransactionIdOlder(h->data_oldest_nonremovable, kaxmin); + /* temp relations cannot be accessed in recovery */ } else { @@ -1785,6 +1796,7 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) h->data_oldest_nonremovable = TransactionIdRetreatedBy(h->data_oldest_nonremovable, vacuum_defer_cleanup_age); + /* defer doesn't apply to temp relations */ } /* @@ -1844,6 +1856,8 @@ ComputeXidHorizons(ComputeXidHorizonsResult *h) h->catalog_oldest_nonremovable)); Assert(TransactionIdPrecedesOrEquals(h->oldest_considered_running, h->data_oldest_nonremovable)); + Assert(TransactionIdPrecedesOrEquals(h->oldest_considered_running, + h->temp_oldest_nonremovable)); Assert(!TransactionIdIsValid(h->slot_xmin) || TransactionIdPrecedesOrEquals(h->oldest_considered_running, h->slot_xmin)); @@ -1878,6 +1892,8 @@ GetOldestNonRemovableTransactionId(Relation rel) return horizons.shared_oldest_nonremovable; else if (RelationIsAccessibleInLogicalDecoding(rel)) return horizons.catalog_oldest_nonremovable; + else if (RELATION_IS_LOCAL(rel)) + return horizons.temp_oldest_nonremovable; else return horizons.data_oldest_nonremovable; } @@ -2357,6 +2373,9 @@ GetSnapshotData(Snapshot snapshot) GlobalVisDataRels.definitely_needed = FullTransactionIdNewer(def_vis_fxid_data, GlobalVisDataRels.definitely_needed); + GlobalVisTempRels.definitely_needed = + FullTransactionIdNewer(latest_completed, + GlobalVisTempRels.definitely_needed); /* * Check if we know that we can initialize or increase the lower @@ -2375,6 +2394,18 @@ GetSnapshotData(Snapshot snapshot) GlobalVisDataRels.maybe_needed = FullTransactionIdNewer(GlobalVisDataRels.maybe_needed, oldestfxid); + if (TransactionIdIsNormal(myxid)) + { + GlobalVisTempRels.maybe_needed = + FullTransactionIdNewer(GlobalVisTempRels.maybe_needed, + FullXidRelativeTo(latest_completed, myxid)); + } + else + { + GlobalVisTempRels.maybe_needed = + FullTransactionIdNewer(GlobalVisTempRels.maybe_needed, + latest_completed); + } } RecentXmin = xmin; @@ -3892,6 +3923,8 @@ GlobalVisTestFor(Relation rel) state = &GlobalVisSharedRels; else if (need_catalog) state = &GlobalVisCatalogRels; + else if (RELATION_IS_LOCAL(rel)) + state = &GlobalVisTempRels; else state = &GlobalVisDataRels; @@ -3942,6 +3975,9 @@ GlobalVisUpdateApply(ComputeXidHorizonsResult *horizons) GlobalVisDataRels.maybe_needed = FullXidRelativeTo(horizons->latest_completed, horizons->data_oldest_nonremovable); + GlobalVisTempRels.maybe_needed = + FullXidRelativeTo(horizons->latest_completed, + horizons->temp_oldest_nonremovable); /* * In longer running transactions it's possible that transactions we @@ -3957,6 +3993,9 @@ GlobalVisUpdateApply(ComputeXidHorizonsResult *horizons) GlobalVisDataRels.definitely_needed = FullTransactionIdNewer(GlobalVisDataRels.maybe_needed, GlobalVisDataRels.definitely_needed); + GlobalVisTempRels.definitely_needed = + FullTransactionIdNewer(GlobalVisTempRels.maybe_needed, + GlobalVisTempRels.definitely_needed); ComputeXidHorizonsResultLastXmin = RecentXmin; } diff --git i/contrib/pg_surgery/Makefile w/contrib/pg_surgery/Makefile index a66776c4c41..48a96008cea 100644 --- i/contrib/pg_surgery/Makefile +++ w/contrib/pg_surgery/Makefile @@ -9,7 +9,7 @@ EXTENSION = pg_surgery DATA = pg_surgery--1.0.sql PGFILEDESC = "pg_surgery - perform surgery on a damaged relation" -REGRESS = heap_surgery +REGRESS = --schedule=$(srcdir)/surgery_schedule ifdef USE_PGXS PG_CONFIG = pg_config diff --git i/contrib/pg_surgery/expected/heap_surgery.out w/contrib/pg_surgery/expected/heap_surgery.out index d4a757ffa01..6ef5597e7cc 100644 --- i/contrib/pg_surgery/expected/heap_surgery.out +++ w/contrib/pg_surgery/expected/heap_surgery.out @@ -1,4 +1,10 @@ create extension pg_surgery; +select pg_sleep(0.1); -- ensure concurrent transaction is ready + pg_sleep +---------- + +(1 row) + -- create a normal heap table and insert some rows. -- use a temp table so that vacuum behavior doesn't depend on global xmin create temp table htab (a int); diff --git i/contrib/pg_surgery/sql/heap_surgery.sql w/contrib/pg_surgery/sql/heap_surgery.sql index 6526b27535d..212c657f3c7 100644 --- i/contrib/pg_surgery/sql/heap_surgery.sql +++ w/contrib/pg_surgery/sql/heap_surgery.sql @@ -1,5 +1,7 @@ create extension pg_surgery; +select pg_sleep(0.1); -- ensure concurrent transaction is ready + -- create a normal heap table and insert some rows. -- use a temp table so that vacuum behavior doesn't depend on global xmin create temp table htab (a int);