From 215ac4ad6589e0f6a31cc4cd867aedba3cd42924 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Fri, 29 Nov 2013 11:26:41 -0300 Subject: [PATCH] Truncate pg_multixact/'s contents during crash recovery Commit 9dc842f08 of 8.2 era prevented MultiXact truncation during crash recovery, because there was no guarantee that enough state had been setup, and because it wasn't deemed to be a good idea to remove data during crash recovery anyway. Since then, due to Hot-Standby, streaming replication and PITR, the amount of time a cluster can spend doing crash recovery has increased significantly, to the point that a cluster may even never come out of it. This has made not truncating the content of pg_multixact/ not defensible anymore. To fix, take care to setup enough state for multixact truncation before crash recovery starts (easy since checkpoints contain the required information), and move the current end-of-recovery actions to a new TrimMultiXact() function, analogous to TrimCLOG(). At some later point, this should probably done similarly to the way clog.c is doing it, which is to just WAL log truncations, but we can't do that for the back branches. Back-patch to 9.0. 8.4 also has the problem, but since there's no hot standby there, it's much less pressing. In 9.2 and earlier, this patch is simpler than in newer branches, because multixact access during recovery isn't required. Add appropriate checks to make sure that's not happening. Andres Freund --- src/backend/access/transam/multixact.c | 53 ++++++++++++++++++++------ src/backend/access/transam/xlog.c | 30 +++++++++++++-- src/include/access/multixact.h | 1 + 3 files changed, 70 insertions(+), 14 deletions(-) diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c index 90fa030caf..ca702eec81 100644 --- a/src/backend/access/transam/multixact.c +++ b/src/backend/access/transam/multixact.c @@ -1768,14 +1768,37 @@ MaybeExtendOffsetSlru(void) * * StartupXLOG has already established nextMXact/nextOffset by calling * MultiXactSetNextMXact and/or MultiXactAdvanceNextMXact, and the oldestMulti - * info from pg_control and/or MultiXactAdvanceOldest. Note that we may - * already have replayed WAL data into the SLRU files. - * - * We don't need any locks here, really; the SLRU locks are taken - * only because slru.c expects to be called with locks held. + * info from pg_control and/or MultiXactAdvanceOldest, but we haven't yet + * replayed WAL. */ void StartupMultiXact(void) +{ + MultiXactId multi = MultiXactState->nextMXact; + MultiXactOffset offset = MultiXactState->nextOffset; + int pageno; + + /* + * Initialize offset's idea of the latest page number. + */ + pageno = MultiXactIdToOffsetPage(multi); + MultiXactOffsetCtl->shared->latest_page_number = pageno; + + /* + * Initialize member's idea of the latest page number. + */ + pageno = MXOffsetToMemberPage(offset); + MultiXactMemberCtl->shared->latest_page_number = pageno; +} + +/* + * This must be called ONCE at the end of startup/recovery. + * + * We don't need any locks here, really; the SLRU locks are taken only because + * slru.c expects to be called with locks held. + */ +void +TrimMultiXact(void) { MultiXactId multi = MultiXactState->nextMXact; MultiXactOffset offset = MultiXactState->nextOffset; @@ -1785,7 +1808,9 @@ StartupMultiXact(void) /* * During a binary upgrade, make sure that the offsets SLRU is large - * enough to contain the next value that would be created. + * enough to contain the next value that would be created. It's fine to do + * this here and not in StartupMultiXact() since binary upgrades should + * never need crash recovery. */ if (IsBinaryUpgrade) MaybeExtendOffsetSlru(); @@ -1794,7 +1819,7 @@ StartupMultiXact(void) LWLockAcquire(MultiXactOffsetControlLock, LW_EXCLUSIVE); /* - * Initialize our idea of the latest page number. + * (Re-)Initialize our idea of the latest page number. */ pageno = MultiXactIdToOffsetPage(multi); MultiXactOffsetCtl->shared->latest_page_number = pageno; @@ -1824,7 +1849,7 @@ StartupMultiXact(void) LWLockAcquire(MultiXactMemberControlLock, LW_EXCLUSIVE); /* - * Initialize our idea of the latest page number. + * (Re-)Initialize our idea of the latest page number. */ pageno = MXOffsetToMemberPage(offset); MultiXactMemberCtl->shared->latest_page_number = pageno; @@ -2258,9 +2283,15 @@ SlruScanDirCbFindEarliest(SlruCtl ctl, char *filename, int segpage, void *data) * Remove all MultiXactOffset and MultiXactMember segments before the oldest * ones still of interest. * - * This is called by vacuum after it has successfully advanced a database's - * datminmxid value; the cutoff value we're passed is the minimum of all - * databases' datminmxid values. + * On a primary, this is called by vacuum after it has successfully advanced a + * database's datminmxid value; the cutoff value we're passed is the minimum of + * all databases' datminmxid values. + * + * During crash recovery, it's called from CreateRestartPoint() instead. We + * rely on the fact that xlog_redo() will already have called + * MultiXactAdvanceOldest(). Our latest_page_number will already have been + * initialized by StartupMultiXact() and kept up to date as new pages are + * zeroed. */ void TruncateMultiXact(MultiXactId oldestMXact) diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index 91f62368fe..3d7d3bc962 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -5195,6 +5195,14 @@ StartupXLOG(void) XLogCtl->ckptXidEpoch = checkPoint.nextXidEpoch; XLogCtl->ckptXid = checkPoint.nextXid; + /* + * Startup MultiXact. We need to do this early for two reasons: one + * is that we might try to access multixacts when we do tuple freezing, + * and the other is we need its state initialized because we attempt + * truncation during restartpoints. + */ + StartupMultiXact(); + /* * Initialize unlogged LSN. On a clean shutdown, it's restored from the * control file. On recovery, all unlogged relations are blown away, so @@ -5395,8 +5403,9 @@ StartupXLOG(void) ProcArrayInitRecovery(ShmemVariableCache->nextXid); /* - * Startup commit log and subtrans only. Other SLRUs are not - * maintained during recovery and need not be started yet. + * Startup commit log and subtrans only. MultiXact has already + * been started up and other SLRUs are not maintained during + * recovery and need not be started yet. */ StartupCLOG(); StartupSUBTRANS(oldestActiveXID); @@ -6061,8 +6070,8 @@ StartupXLOG(void) /* * Perform end of recovery actions for any SLRUs that need it. */ - StartupMultiXact(); TrimCLOG(); + TrimMultiXact(); /* Reload shared-memory state for prepared transactions */ RecoverPreparedTransactions(); @@ -7459,6 +7468,21 @@ CreateRestartPoint(int flags) } LWLockRelease(ControlFileLock); + /* + * Due to an historical accident multixact truncations are not WAL-logged, + * but just performed everytime the mxact horizon is increased. So, unless + * we explicitly execute truncations on a standby it will never clean out + * /pg_multixact which obviously is bad, both because it uses space and + * because we can wrap around into pre-existing data... + * + * We can only do the truncation here, after the UpdateControlFile() + * above, because we've now safely established a restart point, that + * guarantees we will not need need to access those multis. + * + * It's probably worth improving this. + */ + TruncateMultiXact(lastCheckPoint.oldestMulti); + /* * Delete old log files (those no longer needed even for previous * checkpoint/restartpoint) to prevent the disk holding the xlog from diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h index e6db81a827..6085ea3ec1 100644 --- a/src/include/access/multixact.h +++ b/src/include/access/multixact.h @@ -98,6 +98,7 @@ extern Size MultiXactShmemSize(void); extern void MultiXactShmemInit(void); extern void BootStrapMultiXact(void); extern void StartupMultiXact(void); +extern void TrimMultiXact(void); extern void ShutdownMultiXact(void); extern void SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid); -- 2.40.0