agora inbox for [email protected]
help / color / mirror / Atom feedFrom: Kyotaro Horiguchi <[email protected]>
Subject: [PATCH v35 15/21] In-place persistance change to UNLOGGED
Date: Tue, 27 Aug 2024 11:19:53 +0900
This commit enables changing the persistence of relations to UNLOGGED
without creating a new storage file. ALTER TABLE LOGGED will continue
to create a new storage as before.
---
src/backend/catalog/storage.c | 64 +++++++++
src/backend/commands/tablecmds.c | 226 +++++++++++++++++++++++++------
2 files changed, 251 insertions(+), 39 deletions(-)
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index 51be987a5f8..4a2c620402b 100644
--- a/src/backend/catalog/storage.c
+++ b/src/backend/catalog/storage.c
@@ -458,6 +458,54 @@ RelationTruncate(Relation rel, BlockNumber nblocks)
FreeSpaceMapVacuumRange(rel, nblocks, InvalidBlockNumber);
}
+/*
+ * Reset an unlogged relation using the INIT fork, intended for use during the
+ * commit of prepared transactions. The relation is assumed to be UNLOGGED, so
+ * no WAL-logging is required.
+ */
+static void
+ResetUnloggedRelation(RelFileLocator rloc, ProcNumber backend)
+{
+ char *srcpath;
+ char *dstpath;
+ SMgrRelation srel = smgropen(rloc, backend);
+ ForkNumber forks[MAX_FORKNUM];
+ BlockNumber blocks[MAX_FORKNUM];
+ int nforks = 0;
+
+ srel = smgropen(rloc, backend);
+
+ Assert(smgrexists(srel, INIT_FORKNUM));
+
+ for (int i = 0 ; i <= MAX_FORKNUM ; i++)
+ {
+ if (i == INIT_FORKNUM || !smgrexists(srel, i))
+ continue;
+
+ forks[nforks] = i;
+ blocks[nforks] = 0;
+ nforks++;
+ }
+
+ /*
+ * This relation is unlogged. Therefore, unlike RelationTruncate(), there
+ * is no need to call RelationPreTruncate().
+ */
+ smgrtruncate(srel, forks, nforks, blocks);
+
+ /* Note that this leaves the first segment of the main fork. */
+ for (int i = 0 ; i < nforks ; i++)
+ smgrunlink(srel, forks[i], false);
+
+ /* copy init fork to main fork */
+ srcpath = GetRelationPath(rloc.dbOid, rloc.spcOid, rloc.relNumber,
+ backend, INIT_FORKNUM);
+ dstpath = GetRelationPath(rloc.dbOid, rloc.spcOid, rloc.relNumber,
+ backend, MAIN_FORKNUM);
+ copy_file_extended(srcpath, dstpath, true);
+ fsync_fname(dstpath, false);
+}
+
/*
* RelationPreTruncate
* Perform AM-independent work before a physical truncation.
@@ -1181,6 +1229,22 @@ smgr_undo(UndoLogRecord *record, ULogOp op, bool recovered, bool redo)
smgrclose(reln);
}
+ else if (!redo && recovered && ulrec->forknum == INIT_FORKNUM)
+ {
+ /*
+ * System has been crashed until the transaction was
+ * prepared. Now that the init fork is persists, the relation
+ * needs to be cleared.
+ */
+ ResetUnloggedRelation(ulrec->rlocator, ulrec->backend);
+ ereport(WARNING,
+ errmsg("unlogged relation %u/%u/%u was reset",
+ ulrec->rlocator.spcOid, ulrec->rlocator.dbOid,
+ ulrec->rlocator.relNumber),
+ errdetail("Server experinced a crash after the transaction that altered the relation was prepared."));
+
+ }
+
}
else
elog(PANIC, "smgr_undo: unknown ulogop code %d", op);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 251aea55d24..f8d240f374f 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -5708,6 +5708,143 @@ ATParseTransformCmd(List **wqueue, AlteredTableInfo *tab, Relation rel,
return newcmd;
}
+/*
+ * RelationChangePersistence: perform in-place persistence change of a relation
+ */
+static void
+RelationChangePersistence(AlteredTableInfo *tab, char persistence,
+ LOCKMODE lockmode)
+{
+ Relation rel;
+ Relation classRel;
+ HeapTuple tuple,
+ newtuple;
+ Datum new_val[Natts_pg_class];
+ bool new_null[Natts_pg_class],
+ new_repl[Natts_pg_class];
+ List *relids;
+ ListCell *lc_oid;
+
+ Assert(tab->rewrite == AT_REWRITE_ALTER_PERSISTENCE);
+ Assert(lockmode == AccessExclusiveLock);
+
+ /*
+ * Use ATRewriteTable instead of this function if the following condition
+ * is not satisfied.
+ */
+ Assert(tab->constraints == NULL && tab->partition_constraint == NULL &&
+ tab->newvals == NULL && !tab->verify_new_notnull);
+
+ rel = table_open(tab->relid, lockmode);
+
+ Assert(rel->rd_rel->relpersistence != persistence);
+
+ elog(DEBUG1, "perform in-place persistence change");
+
+ /*
+ * Initially, gather all relations that require a persistence change.
+ */
+
+ /* Collect OIDs of indexes and toast relations */
+ relids = RelationGetIndexList(rel);
+ relids = lcons_oid(rel->rd_id, relids);
+
+ /* Add toast relation if any */
+ if (OidIsValid(rel->rd_rel->reltoastrelid))
+ {
+ List *toastidx;
+ Relation toastrel = table_open(rel->rd_rel->reltoastrelid, lockmode);
+
+ relids = lappend_oid(relids, rel->rd_rel->reltoastrelid);
+ toastidx = RelationGetIndexList(toastrel);
+ relids = list_concat(relids, toastidx);
+ pfree(toastidx);
+ table_close(toastrel, NoLock);
+ }
+
+ table_close(rel, NoLock);
+
+ /* Make changes in storage */
+ classRel = table_open(RelationRelationId, RowExclusiveLock);
+
+ foreach(lc_oid, relids)
+ {
+ Oid reloid = lfirst_oid(lc_oid);
+ Relation r = relation_open(reloid, lockmode);
+ SMgrRelation srel;
+ bool persistent = (persistence == RELPERSISTENCE_PERMANENT);
+ bool is_index;
+
+ /*
+ * Reconstruct the storage when permanent and unlogged storage types
+ * are incompatible.
+ */
+ if (r->rd_rel->relkind == RELKIND_INDEX &&
+ !r->rd_indam->amunloggedstoragecompatible)
+ {
+ int reindex_flags;
+ ReindexParams params = {0};
+
+ /* reindex doesn't allow concurrent use of the index */
+ table_close(r, NoLock);
+
+ reindex_flags =
+ REINDEX_REL_SUPPRESS_INDEX_USE |
+ REINDEX_REL_CHECK_CONSTRAINTS;
+
+ /* Set the same persistence with the parent relation. */
+ if (persistent)
+ reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT;
+ else
+ reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED;
+
+ /* this doesn't fire REINDEX event triegger */
+ reindex_index(NULL, reloid, reindex_flags, persistence, ¶ms);
+
+ continue;
+ }
+
+ /* Currently, only allowing changes to UNLOGGED. */
+ Assert(!persistent);
+
+ RelationAssumePersistenceChange(r);
+
+ /* switch buffer persistence */
+ srel = RelationGetSmgr(r);
+ log_smgrbufpersistence(srel->smgr_rlocator.locator, persistent);
+ SetRelationBuffersPersistence(srel, persistent);
+
+ /* then create the init fork */
+ is_index = (r->rd_rel->relkind == RELKIND_INDEX);
+ RelationCreateFork(srel, INIT_FORKNUM, !is_index, true);
+ if (is_index)
+ r->rd_indam->ambuildempty(r);
+
+ /* Update catalog */
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(reloid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for relation %u", reloid);
+
+ memset(new_val, 0, sizeof(new_val));
+ memset(new_null, false, sizeof(new_null));
+ memset(new_repl, false, sizeof(new_repl));
+
+ new_val[Anum_pg_class_relpersistence - 1] = CharGetDatum(persistence);
+ new_null[Anum_pg_class_relpersistence - 1] = false;
+ new_repl[Anum_pg_class_relpersistence - 1] = true;
+
+ newtuple = heap_modify_tuple(tuple, RelationGetDescr(classRel),
+ new_val, new_null, new_repl);
+
+ CatalogTupleUpdate(classRel, &newtuple->t_self, newtuple);
+ heap_freetuple(newtuple);
+
+ table_close(r, NoLock);
+ }
+
+ table_close(classRel, NoLock);
+}
+
/*
* ATRewriteTables: ALTER TABLE phase 3
*/
@@ -5840,48 +5977,59 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode,
tab->relid,
tab->rewrite);
- /*
- * Create transient table that will receive the modified data.
- *
- * Ensure it is marked correctly as logged or unlogged. We have
- * to do this here so that buffers for the new relfilenumber will
- * have the right persistence set, and at the same time ensure
- * that the original filenumbers's buffers will get read in with
- * the correct setting (i.e. the original one). Otherwise a
- * rollback after the rewrite would possibly result with buffers
- * for the original filenumbers having the wrong persistence
- * setting.
- *
- * NB: This relies on swap_relation_files() also swapping the
- * persistence. That wouldn't work for pg_class, but that can't be
- * unlogged anyway.
- */
- OIDNewHeap = make_new_heap(tab->relid, NewTableSpace, NewAccessMethod,
- persistence, lockmode);
+ if (tab->rewrite == AT_REWRITE_ALTER_PERSISTENCE &&
+ persistence == RELPERSISTENCE_UNLOGGED)
+ {
+ /* Make in-place persistence change. */
+ RelationChangePersistence(tab, persistence, lockmode);
+ }
+ else
+ {
+ /*
+ * Create transient table that will receive the modified data.
+ *
+ * Ensure it is marked correctly as logged or unlogged. We
+ * have to do this here so that buffers for the new
+ * relfilenumber will have the right persistence set, and at
+ * the same time ensure that the original filenumbers's buffers
+ * will get read in with the correct setting (i.e. the original
+ * one). Otherwise a rollback after the rewrite would possibly
+ * result with buffers for the original filenumbers having the
+ * wrong persistence setting.
+ *
+ * NB: This relies on swap_relation_files() also swapping the
+ * persistence. That wouldn't work for pg_class, but that can't
+ * be unlogged anyway.
+ */
+ OIDNewHeap = make_new_heap(tab->relid, NewTableSpace,
+ NewAccessMethod,
+ persistence, lockmode);
- /*
- * Copy the heap data into the new table with the desired
- * modifications, and test the current data within the table
- * against new constraints generated by ALTER TABLE commands.
- */
- ATRewriteTable(tab, OIDNewHeap, lockmode);
+ /*
+ * Copy the heap data into the new table with the desired
+ * modifications, and test the current data within the table
+ * against new constraints generated by ALTER TABLE commands.
+ */
+ ATRewriteTable(tab, OIDNewHeap, lockmode);
- /*
- * Swap the physical files of the old and new heaps, then rebuild
- * indexes and discard the old heap. We can use RecentXmin for
- * the table's new relfrozenxid because we rewrote all the tuples
- * in ATRewriteTable, so no older Xid remains in the table. Also,
- * we never try to swap toast tables by content, since we have no
- * interest in letting this code work on system catalogs.
- */
- finish_heap_swap(tab->relid, OIDNewHeap,
- false, false, true,
- !OidIsValid(tab->newTableSpace),
- RecentXmin,
- ReadNextMultiXactId(),
- persistence);
+ /*
+ * Swap the physical files of the old and new heaps, then
+ * rebuild indexes and discard the old heap. We can use
+ * RecentXmin for the table's new relfrozenxid because we
+ * rewrote all the tuples in ATRewriteTable, so no older Xid
+ * remains in the table. Also, we never try to swap toast
+ * tables by content, since we have no interest in letting this
+ * code work on system catalogs.
+ */
+ finish_heap_swap(tab->relid, OIDNewHeap,
+ false, false, true,
+ !OidIsValid(tab->newTableSpace),
+ RecentXmin,
+ ReadNextMultiXactId(),
+ persistence);
- InvokeObjectPostAlterHook(RelationRelationId, tab->relid, 0);
+ InvokeObjectPostAlterHook(RelationRelationId, tab->relid, 0);
+ }
}
else if (tab->rewrite > 0 && tab->relkind == RELKIND_SEQUENCE)
{
--
2.43.5
----Next_Part(Thu_Oct_31_17_01_30_2024_620)--
Content-Type: Text/X-Patch; charset=us-ascii
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="v35-0016-Add-test-for-ALTER-TABLE-UNLOGGED.patch"
view thread (4+ messages) latest in thread
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected]
Subject: Re: [PATCH v35 15/21] In-place persistance change to UNLOGGED
In-Reply-To: <no-message-id-55613@localhost>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox