WIP: Rework the way multixact truncations work. tmp-multixact-truncate
authorAndres Freund <[email protected]>
Mon, 15 Jun 2015 22:13:03 +0000 (00:13 +0200)
committerAndres Freund <[email protected]>
Mon, 15 Jun 2015 22:13:03 +0000 (00:13 +0200)
The fact that multixact truncations are not WAL logged has caused a fair
share of problems. Amongst others it requires to do computations during
recovery while the database is not in a consistent state, delaying
truncations till checkpoints, and it handling members being truncated,
but offset not.

We tried to put bandaids on lots of these issues over the last few
years, but it seems time to change course. Thus this patch introduces
WAL logging, even in the back branches.

This allows:
1) to perform the truncation directly during VACUUM, instead of delaying it
   to the checkpoint.
2) to avoid looking at the offsets SLRU for truncation during recovery,
   we can just use the master's values.
3) simplify a fair amount of logic to keep in memory limits straight

During the course of fixing this a bunch of bugs had to be fixed:

1) Data was not purged from memory the member's slru before deleting
   segments. This happend to be hard or impossible to hit due to the
   interlock between checkpoints and truncation.

2) find_multixact_start() relied on SimpleLruDoesPhysicalPageExist - but
   that doesn't work for offsets that haven't yet been flushed to
   disk. Flush out before running to fix. Not pretty.

WIP COMMIT MESSAGE

src/backend/access/rmgrdesc/mxactdesc.c
src/backend/access/transam/multixact.c
src/backend/access/transam/slru.c
src/backend/access/transam/xlog.c
src/backend/commands/vacuum.c
src/include/access/multixact.h
src/include/access/slru.h

index 572951ec2f11d67f77be1842d99b4853d365c47e..df55df1b5d450cde764c24884b3c13835a917251 100644 (file)
@@ -70,6 +70,13 @@ multixact_desc(StringInfo buf, XLogReaderState *record)
        for (i = 0; i < xlrec->nmembers; i++)
            out_member(buf, &xlrec->members[i]);
    }
+   else if (info == XLOG_MULTIXACT_TRUNCATE_ID)
+   {
+       xl_multixact_truncate *xlrec = (xl_multixact_truncate *) rec;
+       appendStringInfo(buf, "offsets [%u, %u), members [%u, %u)",
+                        xlrec->startOff, xlrec->endOff,
+                        xlrec->startMemb, xlrec->endMemb);
+   }
 }
 
 const char *
@@ -88,6 +95,9 @@ multixact_identify(uint8 info)
        case XLOG_MULTIXACT_CREATE_ID:
            id = "CREATE_ID";
            break;
+       case XLOG_MULTIXACT_TRUNCATE_ID:
+           id = "TRUNCATE";
+           break;
    }
 
    return id;
index 594026f387a9b01d0f3de4ac36574f9bd78fe334..44497084ab61f75a90214aae77684548b768a0ee 100644 (file)
@@ -49,9 +49,7 @@
  * value is removed; the cutoff value is stored in pg_class.  The minimum value
  * across all tables in each database is stored in pg_database, and the global
  * minimum across all databases is part of pg_control and is kept in shared
- * memory.  At checkpoint time, after the value is known flushed in WAL, any
- * files that correspond to multixacts older than that value are removed.
- * (These files are also removed when a restartpoint is executed.)
+ * memory.  Whenever that minimum is advanced, the SLRUs are truncated.
  *
  * When new multixactid values are to be created, care is taken that the
  * counter does not fall within the wraparound horizon considering the global
@@ -83,6 +81,7 @@
 #include "postmaster/autovacuum.h"
 #include "storage/lmgr.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/procarray.h"
 #include "utils/builtins.h"
 #include "utils/memutils.h"
 
 /* page in which a member is to be found */
 #define MXOffsetToMemberPage(xid) ((xid) / (TransactionId) MULTIXACT_MEMBERS_PER_PAGE)
+#define MXOffsetToMemberSegment(xid) (MXOffsetToMemberPage(xid) / SLRU_PAGES_PER_SEGMENT)
 
 /* Location (byte offset within page) of flag word for a given member */
 #define MXOffsetToFlagsOffset(xid) \
@@ -218,11 +218,12 @@ typedef struct MultiXactStateData
    bool        oldestOffsetKnown;
 
    /*
-    * This is what the previous checkpoint stored as the truncate position.
-    * This value is the oldestMultiXactId that was valid when a checkpoint
-    * was last executed.
+    * True if a multixact truncation WAL record was replayed since the last
+    * checkpoint. This is used to trigger 'legacy truncations', i.e. truncate
+    * by looking at the data directory during WAL replay, when the primary is
+    * too old to general truncation records.
     */
-   MultiXactId lastCheckpointedOldest;
+   bool        sawTruncationCkptCyle;
 
    /* support for anti-wraparound measures */
    MultiXactId multiVacLimit;
@@ -231,8 +232,7 @@ typedef struct MultiXactStateData
    MultiXactId multiWrapLimit;
 
    /* support for members anti-wraparound measures */
-   MultiXactOffset offsetStopLimit;
-   bool offsetStopLimitKnown;
+   MultiXactOffset offsetStopLimit; /* known if oldestOffsetKnown */
 
    /*
     * Per-backend data starts here.  We have two arrays stored in the area
@@ -362,12 +362,13 @@ static bool MultiXactOffsetPrecedes(MultiXactOffset offset1,
                        MultiXactOffset offset2);
 static void ExtendMultiXactOffset(MultiXactId multi);
 static void ExtendMultiXactMember(MultiXactOffset offset, int nmembers);
-static void DetermineSafeOldestOffset(MultiXactId oldestMXact);
 static bool MultiXactOffsetWouldWrap(MultiXactOffset boundary,
                         MultiXactOffset start, uint32 distance);
-static bool SetOffsetVacuumLimit(bool finish_setup);
+static bool SetOffsetVacuumLimit(void);
 static bool find_multixact_start(MultiXactId multi, MultiXactOffset *result);
 static void WriteMZeroPageXlogRec(int pageno, uint8 info);
+static void WriteMTruncateXlogRec(MultiXactOffset startOff, MultiXactOffset endOff,
+                                 MultiXactOffset startMemb, MultiXactOffset endMemb);
 
 
 /*
@@ -1100,7 +1101,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
     *----------
     */
 #define OFFSET_WARN_SEGMENTS   20
-   if (MultiXactState->offsetStopLimitKnown &&
+   if (MultiXactState->oldestOffsetKnown &&
        MultiXactOffsetWouldWrap(MultiXactState->offsetStopLimit, nextOffset,
                                 nmembers))
    {
@@ -1140,7 +1141,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
            SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
    }
 
-   if (MultiXactState->offsetStopLimitKnown &&
+   if (MultiXactState->oldestOffsetKnown &&
        MultiXactOffsetWouldWrap(MultiXactState->offsetStopLimit,
                                 nextOffset,
                                 nmembers + MULTIXACT_MEMBERS_PER_PAGE * SLRU_PAGES_PER_SEGMENT * OFFSET_WARN_SEGMENTS))
@@ -2018,13 +2019,21 @@ StartupMultiXact(void)
 void
 TrimMultiXact(void)
 {
-   MultiXactId multi = MultiXactState->nextMXact;
-   MultiXactOffset offset = MultiXactState->nextOffset;
+   MultiXactId nextMXact;
+   MultiXactOffset offset;
    MultiXactId oldestMXact;
+   MultiXactId oldestMXactDB;
    int         pageno;
    int         entryno;
    int         flagsoff;
 
+   LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
+   nextMXact = MultiXactState->nextMXact;
+   offset = MultiXactState->nextOffset;
+   oldestMXact = MultiXactState->oldestMultiXactId;
+   oldestMXactDB = MultiXactState->oldestMultiXactDB;
+   MultiXactState->finishedStartup = true;
+   LWLockRelease(MultiXactGenLock);
 
    /* Clean up offsets state */
    LWLockAcquire(MultiXactOffsetControlLock, LW_EXCLUSIVE);
@@ -2032,20 +2041,20 @@ TrimMultiXact(void)
    /*
     * (Re-)Initialize our idea of the latest page number for offsets.
     */
-   pageno = MultiXactIdToOffsetPage(multi);
+   pageno = MultiXactIdToOffsetPage(nextMXact);
    MultiXactOffsetCtl->shared->latest_page_number = pageno;
 
    /*
     * Zero out the remainder of the current offsets page.  See notes in
     * TrimCLOG() for motivation.
     */
-   entryno = MultiXactIdToOffsetEntry(multi);
+   entryno = MultiXactIdToOffsetEntry(nextMXact);
    if (entryno != 0)
    {
        int         slotno;
        MultiXactOffset *offptr;
 
-       slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, multi);
+       slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, nextMXact);
        offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno];
        offptr += entryno;
 
@@ -2094,12 +2103,11 @@ TrimMultiXact(void)
 
    LWLockRelease(MultiXactMemberControlLock);
 
-   if (SetOffsetVacuumLimit(true) && IsUnderPostmaster)
-       SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
-   LWLockAcquire(MultiXactGenLock, LW_SHARED);
-   oldestMXact = MultiXactState->lastCheckpointedOldest;
-   LWLockRelease(MultiXactGenLock);
-   DetermineSafeOldestOffset(oldestMXact);
+   /*
+    * Recompute limits once fully started, we now can compute how far an
+    * members wraparound is away.
+    */
+   SetMultiXactIdLimit(oldestMXact, oldestMXactDB);
 }
 
 /*
@@ -2268,8 +2276,19 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
     (errmsg("MultiXactId wrap limit is %u, limited by database with OID %u",
             multiWrapLimit, oldest_datoid)));
 
+   /*
+    * Computing the actual limits is only possible once the data directory is
+    * in a consistent state. There's no need to compute the limits while
+    * still replaying WAL as no new multis can be computed anyway. So we'll
+    * only do further checks once TrimMultiXact() has been called.
+    */
+   if (!MultiXactState->finishedStartup)
+       return;
+
+   Assert(!InRecovery);
+
    /* Set limits for offset vacuum. */
-   needs_offset_vacuum = SetOffsetVacuumLimit(false);
+   needs_offset_vacuum = SetOffsetVacuumLimit();
 
    /*
     * If past the autovacuum force point, immediately signal an autovac
@@ -2279,11 +2298,11 @@ SetMultiXactIdLimit(MultiXactId oldest_datminmxid, Oid oldest_datoid)
     * another iteration immediately if there are still any old databases.
     */
    if ((MultiXactIdPrecedes(multiVacLimit, curMulti) ||
-        needs_offset_vacuum) && IsUnderPostmaster && !InRecovery)
+        needs_offset_vacuum) && IsUnderPostmaster)
        SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
 
    /* Give an immediate warning if past the wrap warn point */
-   if (MultiXactIdPrecedes(multiWarnLimit, curMulti) && !InRecovery)
+   if (MultiXactIdPrecedes(multiWarnLimit, curMulti))
    {
        char       *oldest_datname;
 
@@ -2351,27 +2370,32 @@ MultiXactAdvanceNextMXact(MultiXactId minMulti,
 }
 
 /*
- * Update our oldestMultiXactId value, but only if it's more recent than
- * what we had.  However, even if not, always update the oldest multixact
- * offset limit.
+ * During WAL replay update our oldestMultiXactId value, but only if it's more
+ * recent than what we had.
  */
 void
 MultiXactAdvanceOldest(MultiXactId oldestMulti, Oid oldestMultiDB)
 {
    if (MultiXactIdPrecedes(MultiXactState->oldestMultiXactId, oldestMulti))
+   {
+       /*
+        * If there has been a truncation on the master, detected via a moving
+        * oldestMulti, without a corresponding truncation record we know that
+        * the primary is still running an older version of postgres that
+        * doesn't yet log multixact truncations. So perform truncation
+        * ourselves.
+        */
+       if (!MultiXactState->sawTruncationCkptCyle)
+       {
+           ereport(LOG, (errmsg("performing legacy multixact truncation, upgrade master")));
+           TruncateMultiXact(oldestMulti, oldestMultiDB, true);
+       }
+
        SetMultiXactIdLimit(oldestMulti, oldestMultiDB);
-}
+   }
 
-/*
- * Update the "safe truncation point".  This is the newest value of oldestMulti
- * that is known to be flushed as part of a checkpoint record.
- */
-void
-MultiXactSetSafeTruncate(MultiXactId safeTruncateMulti)
-{
-   LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
-   MultiXactState->lastCheckpointedOldest = safeTruncateMulti;
-   LWLockRelease(MultiXactGenLock);
+   /* only looked at in the startup process, no lock necessary */
+   MultiXactState->sawTruncationCkptCyle = false;
 }
 
 /*
@@ -2526,127 +2550,43 @@ GetOldestMultiXactId(void)
    return oldestMXact;
 }
 
-/*
- * Based on the given oldest MultiXactId, determine what's the oldest member
- * offset and install the limit info in MultiXactState, where it can be used to
- * prevent overrun of old data in the members SLRU area.
- */
-static void
-DetermineSafeOldestOffset(MultiXactId oldestMXact)
-{
-   MultiXactOffset oldestOffset;
-   MultiXactOffset nextOffset;
-   MultiXactOffset offsetStopLimit;
-   MultiXactOffset prevOffsetStopLimit;
-   MultiXactId     nextMXact;
-   bool            finishedStartup;
-   bool            prevOffsetStopLimitKnown;
-
-   /* Fetch values from shared memory. */
-   LWLockAcquire(MultiXactGenLock, LW_SHARED);
-   finishedStartup = MultiXactState->finishedStartup;
-   nextMXact = MultiXactState->nextMXact;
-   nextOffset = MultiXactState->nextOffset;
-   prevOffsetStopLimit = MultiXactState->offsetStopLimit;
-   prevOffsetStopLimitKnown = MultiXactState->offsetStopLimitKnown;
-   LWLockRelease(MultiXactGenLock);
-
-   /* Don't worry about this until after we've started up. */
-   if (!finishedStartup)
-       return;
-
-   /*
-    * Determine the offset of the oldest multixact.  Normally, we can read
-    * the offset from the multixact itself, but there's an important special
-    * case: if there are no multixacts in existence at all, oldestMXact
-    * obviously can't point to one.  It will instead point to the multixact
-    * ID that will be assigned the next time one is needed.
-    *
-    * NB: oldestMXact should be the oldest multixact that still exists in the
-    * SLRU, unlike in SetOffsetVacuumLimit, where we do this same computation
-    * based on the oldest value that might be referenced in a table.
-    */
-   if (nextMXact == oldestMXact)
-       oldestOffset = nextOffset;
-   else
-   {
-       bool        oldestOffsetKnown;
-
-       oldestOffsetKnown = find_multixact_start(oldestMXact, &oldestOffset);
-       if (!oldestOffsetKnown)
-       {
-           ereport(LOG,
-                   (errmsg("MultiXact member wraparound protections are disabled because oldest checkpointed MultiXact %u does not exist on disk",
-                       oldestMXact)));
-           return;
-       }
-   }
-
-   /* move back to start of the corresponding segment */
-   offsetStopLimit = oldestOffset - (oldestOffset %
-       (MULTIXACT_MEMBERS_PER_PAGE * SLRU_PAGES_PER_SEGMENT));
-   /* always leave one segment before the wraparound point */
-   offsetStopLimit -= (MULTIXACT_MEMBERS_PER_PAGE * SLRU_PAGES_PER_SEGMENT);
-
-   /* if nothing has changed, we're done */
-   if (prevOffsetStopLimitKnown && offsetStopLimit == prevOffsetStopLimit)
-       return;
-
-   LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
-   MultiXactState->offsetStopLimit = offsetStopLimit;
-   MultiXactState->offsetStopLimitKnown = true;
-   LWLockRelease(MultiXactGenLock);
-
-   if (!prevOffsetStopLimitKnown && IsUnderPostmaster)
-       ereport(LOG,
-               (errmsg("MultiXact member wraparound protections are now enabled")));
-   ereport(DEBUG1,
-           (errmsg("MultiXact member stop limit is now %u based on MultiXact %u",
-               offsetStopLimit, oldestMXact)));
-}
-
 /*
  * Determine how aggressively we need to vacuum in order to prevent member
  * wraparound.
  *
- * To determine the oldest multixact ID, we look at oldestMultiXactId, not
- * lastCheckpointedOldest.  That's because vacuuming can't help with anything
- * older than oldestMultiXactId; anything older than that isn't referenced
- * by any table.  Offsets older than oldestMultiXactId but not as old as
- * lastCheckpointedOldest will go away after the next checkpoint.
+ * To do so determine what's the oldest member offset and install the limit
+ * info in MultiXactState, where it can be used to prevent overrun of old data
+ * in the members SLRU area.
  *
  * The return value is true if emergency autovacuum is required and false
  * otherwise.
  */
 static bool
-SetOffsetVacuumLimit(bool finish_setup)
+SetOffsetVacuumLimit(void)
 {
    MultiXactId oldestMultiXactId;
    MultiXactId nextMXact;
-   bool        finishedStartup;
    MultiXactOffset oldestOffset = 0;       /* placate compiler */
    MultiXactOffset nextOffset;
    bool        oldestOffsetKnown = false;
-   MultiXactOffset prevOldestOffset;
-   bool        prevOldestOffsetKnown;
+   bool            prevOldestOffsetKnown;
+   MultiXactOffset offsetStopLimit = 0;
 
    /* Read relevant fields from shared memory. */
    LWLockAcquire(MultiXactGenLock, LW_SHARED);
    oldestMultiXactId = MultiXactState->oldestMultiXactId;
    nextMXact = MultiXactState->nextMXact;
    nextOffset = MultiXactState->nextOffset;
-   finishedStartup = MultiXactState->finishedStartup;
-   prevOldestOffset = MultiXactState->oldestOffset;
    prevOldestOffsetKnown = MultiXactState->oldestOffsetKnown;
+   Assert(MultiXactState->finishedStartup);
    LWLockRelease(MultiXactGenLock);
 
-   /* Don't do this until after any recovery is complete. */
-   if (!finishedStartup && !finish_setup)
-       return false;
-
    /*
-    * If no multixacts exist, then oldestMultiXactId will be the next
-    * multixact that will be created, rather than an existing multixact.
+    * Determine the offset of the oldest multixact.  Normally, we can read
+    * the offset from the multixact itself, but there's an important special
+    * case: if there are no multixacts in existence at all, oldestMXact
+    * obviously can't point to one.  It will instead point to the multixact
+    * ID that will be assigned the next time one is needed.
     */
    if (oldestMultiXactId == nextMXact)
    {
@@ -2667,34 +2607,46 @@ SetOffsetVacuumLimit(bool finish_setup)
         */
        oldestOffsetKnown =
            find_multixact_start(oldestMultiXactId, &oldestOffset);
+
+       if (oldestOffsetKnown)
+           ereport(DEBUG1,
+                   (errmsg("oldest MultiXactId member is at offset %u",
+                           oldestOffset)));
+       else
+           ereport(LOG,
+                   (errmsg("MultiXact member wraparound protections are disabled because oldest checkpointed MultiXact %u does not exist on disk",
+                       oldestMultiXactId)));
    }
 
    /*
-    * Except when initializing the system for the first time, there's no
-    * need to update anything if we don't know the oldest offset or if it
-    * hasn't changed.
+    * If we can, compute limits (and install them MultiXactState) to prevent
+    * overrun of old data in the members SLRU area. We can only do so if the
+    * oldest offset is known though.
     */
-   if (finish_setup ||
-       (oldestOffsetKnown && !prevOldestOffsetKnown) ||
-       (oldestOffsetKnown && prevOldestOffset != oldestOffset))
+   if (oldestOffsetKnown)
    {
-       /* Install the new limits. */
-       LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
-       MultiXactState->oldestOffset = oldestOffset;
-       MultiXactState->oldestOffsetKnown = oldestOffsetKnown;
-       MultiXactState->finishedStartup = true;
-       LWLockRelease(MultiXactGenLock);
+       /* move back to start of the corresponding segment */
+       offsetStopLimit = oldestOffset - (oldestOffset %
+           (MULTIXACT_MEMBERS_PER_PAGE * SLRU_PAGES_PER_SEGMENT));
 
-       /* Log the info */
-       if (oldestOffsetKnown)
-           ereport(DEBUG1,
-                   (errmsg("oldest MultiXactId member is at offset %u",
-                       oldestOffset)));
-       else
-           ereport(DEBUG1,
-                   (errmsg("oldest MultiXactId member offset unknown")));
+       /* always leave one segment before the wraparound point */
+       offsetStopLimit -= (MULTIXACT_MEMBERS_PER_PAGE * SLRU_PAGES_PER_SEGMENT);
+
+       if (!prevOldestOffsetKnown && IsUnderPostmaster)
+           ereport(LOG,
+                   (errmsg("MultiXact member wraparound protections are now enabled")));
+       ereport(DEBUG1,
+               (errmsg("MultiXact member stop limit is now %u based on MultiXact %u",
+                       offsetStopLimit, oldestMultiXactId)));
    }
 
+   /* Install the computed values */
+   LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
+   MultiXactState->oldestOffset = oldestOffset;
+   MultiXactState->oldestOffsetKnown = oldestOffsetKnown;
+   MultiXactState->offsetStopLimit = offsetStopLimit;
+   LWLockRelease(MultiXactGenLock);
+
    /*
     * Do we need an emergency autovacuum?  If we're not sure, assume yes.
     */
@@ -2765,9 +2717,18 @@ find_multixact_start(MultiXactId multi, MultiXactOffset *result)
    int         slotno;
    MultiXactOffset *offptr;
 
+   /* XXX: Remove || Startup after WAL page magic bump */
+   Assert(MultiXactState->finishedStartup || AmStartupProcess());
+
    pageno = MultiXactIdToOffsetPage(multi);
    entryno = MultiXactIdToOffsetEntry(multi);
 
+   /*
+    * FIXME: We need to flush out dirty data, so PhysicalPageExists can work
+    * correctly, but SimpleLruFlush() is a pretty big hammer for that.
+    */
+   SimpleLruFlush(MultiXactOffsetCtl, true);
+
    if (!SimpleLruDoesPhysicalPageExist(MultiXactOffsetCtl, pageno))
        return false;
 
@@ -2873,65 +2834,6 @@ MultiXactMemberFreezeThreshold(void)
    return multixacts - victim_multixacts;
 }
 
-/*
- * SlruScanDirectory callback.
- *     This callback deletes segments that are outside the range determined by
- *     the given page numbers.
- *
- * Both range endpoints are exclusive (that is, segments containing any of
- * those pages are kept.)
- */
-typedef struct MembersLiveRange
-{
-   int         rangeStart;
-   int         rangeEnd;
-} MembersLiveRange;
-
-static bool
-SlruScanDirCbRemoveMembers(SlruCtl ctl, char *filename, int segpage,
-                          void *data)
-{
-   MembersLiveRange *range = (MembersLiveRange *) data;
-   MultiXactOffset nextOffset;
-
-   if ((segpage == range->rangeStart) ||
-       (segpage == range->rangeEnd))
-       return false;           /* easy case out */
-
-   /*
-    * To ensure that no segment is spuriously removed, we must keep track of
-    * new segments added since the start of the directory scan; to do this,
-    * we update our end-of-range point as we run.
-    *
-    * As an optimization, we can skip looking at shared memory if we know for
-    * certain that the current segment must be kept.  This is so because
-    * nextOffset never decreases, and we never increase rangeStart during any
-    * one run.
-    */
-   if (!((range->rangeStart > range->rangeEnd &&
-          segpage > range->rangeEnd && segpage < range->rangeStart) ||
-         (range->rangeStart < range->rangeEnd &&
-          (segpage < range->rangeStart || segpage > range->rangeEnd))))
-       return false;
-
-   /*
-    * Update our idea of the end of the live range.
-    */
-   LWLockAcquire(MultiXactGenLock, LW_SHARED);
-   nextOffset = MultiXactState->nextOffset;
-   LWLockRelease(MultiXactGenLock);
-   range->rangeEnd = MXOffsetToMemberPage(nextOffset);
-
-   /* Recheck the deletion condition.  If it still holds, perform deletion */
-   if ((range->rangeStart > range->rangeEnd &&
-        segpage > range->rangeEnd && segpage < range->rangeStart) ||
-       (range->rangeStart < range->rangeEnd &&
-        (segpage < range->rangeStart || segpage > range->rangeEnd)))
-       SlruDeleteSegment(ctl, filename);
-
-   return false;               /* keep going */
-}
-
 typedef struct mxtruncinfo
 {
    int         earliestExistingPage;
@@ -2955,6 +2857,32 @@ SlruScanDirCbFindEarliest(SlruCtl ctl, char *filename, int segpage, void *data)
    return false;               /* keep going */
 }
 
+
+/*
+ * Delete any members segment that doesn't contain the start or end point.
+*/
+static void
+PerformMembersTruncation(MultiXactOffset oldestOffset, MultiXactOffset oldestAliveOffset)
+{
+   int startsegment = MXOffsetToMemberSegment(oldestOffset);
+   int endsegment = MXOffsetToMemberSegment(oldestAliveOffset);
+   int maxsegment = MXOffsetToMemberSegment(MaxMultiXactOffset);
+   int segment = startsegment;
+
+   while (segment != endsegment)
+   {
+       /* verify whether the current segment is to be deleted */
+       if (segment != startsegment && segment != endsegment)
+           SlruDeleteSegment(MultiXactMemberCtl, segment);
+
+       /* move to next segment, handle wraparound correctly */
+       if (segment == maxsegment)
+           segment = 0;
+       else
+           segment += 1;
+   }
+}
+
 /*
  * Remove all MultiXactOffset and MultiXactMember segments before the oldest
  * ones still of interest.
@@ -2967,32 +2895,49 @@ SlruScanDirCbFindEarliest(SlruCtl ctl, char *filename, int segpage, void *data)
  * and kept up to date as new pages are zeroed.
  */
 void
-TruncateMultiXact(void)
+TruncateMultiXact(MultiXactId frozenMulti, Oid minmulti_datoid, bool in_recovery)
 {
    MultiXactId oldestMXact;
    MultiXactOffset oldestOffset;
    MultiXactId     nextMXact;
    MultiXactOffset nextOffset;
+   MultiXactOffset oldestAliveOffset;
    mxtruncinfo trunc;
    MultiXactId earliest;
-   MembersLiveRange range;
 
-   Assert(AmCheckpointerProcess() || AmStartupProcess() ||
-          !IsPostmasterEnvironment);
+   /*
+    * Need to allow being called in recovery for backward compatibility, when
+    * a updated standby replays WAL generated by a non-updated primary.
+    */
+   Assert(in_recovery || !RecoveryInProgress());
+   Assert(!in_recovery || AmStartupProcess());
+   Assert(in_recovery || MultiXactState->finishedStartup);
 
    LWLockAcquire(MultiXactGenLock, LW_SHARED);
-   oldestMXact = MultiXactState->lastCheckpointedOldest;
    nextMXact = MultiXactState->nextMXact;
    nextOffset = MultiXactState->nextOffset;
+   oldestMXact = MultiXactState->oldestMultiXactId;
    LWLockRelease(MultiXactGenLock);
    Assert(MultiXactIdIsValid(oldestMXact));
 
+   /*
+    * Make sure to only attempt truncation if there's values to truncate
+    * away. In normal processing values shouldn't go backwards, but there's
+    * some corner cases (due to bugs) where that's possible.
+    */
+   if (MultiXactIdPrecedesOrEquals(frozenMulti, oldestMXact))
+       return;
+
    /*
     * Note we can't just plow ahead with the truncation; it's possible that
     * there are no segments to truncate, which is a problem because we are
     * going to attempt to read the offsets page to determine where to
     * truncate the members SLRU.  So we first scan the directory to determine
     * the earliest offsets page number that we can read without error.
+    *
+    * XXX: It's also possible that the page that oldestMXact is on has
+    * already been truncated away, and we crashed before updating
+    * oldestMXact.
     */
    trunc.earliestExistingPage = -1;
    SlruScanDirectory(MultiXactOffsetCtl, SlruScanDirCbFindEarliest, &trunc);
@@ -3000,21 +2945,9 @@ TruncateMultiXact(void)
    if (earliest < FirstMultiXactId)
        earliest = FirstMultiXactId;
 
-   /*
-    * If there's nothing to remove, we can bail out early.
-    *
-    * Due to bugs in early releases of PostgreSQL 9.3.X and 9.4.X,
-    * oldestMXact might point to a multixact that does not exist.
-    * Autovacuum will eventually advance it to a value that does exist,
-    * and we want to set a proper offsetStopLimit when that happens,
-    * so call DetermineSafeOldestOffset here even if we're not actually
-    * truncating.
-    */
+   /* If there's nothing to remove, we can bail out early. */
    if (MultiXactIdPrecedes(oldestMXact, earliest))
-   {
-       DetermineSafeOldestOffset(oldestMXact);
        return;
-   }
 
    /*
     * First, compute the safe truncation point for MultiXactMember. This is
@@ -3035,30 +2968,71 @@ TruncateMultiXact(void)
    }
 
    /*
-    * To truncate MultiXactMembers, we need to figure out the active page
-    * range and delete all files outside that range.  The start point is the
-    * start of the segment containing the oldest offset; an end point of the
-    * segment containing the next offset to use is enough.  The end point is
-    * updated as MultiXactMember gets extended concurrently, elsewhere.
+    * Secondly compute up to where to truncate. Lookup the corresponding
+    * member offset for frozenMulti for that.
     */
-   range.rangeStart = MXOffsetToMemberPage(oldestOffset);
-   range.rangeStart -= range.rangeStart % SLRU_PAGES_PER_SEGMENT;
-
-   range.rangeEnd = MXOffsetToMemberPage(nextOffset);
+   if (frozenMulti == nextMXact)
+       oldestAliveOffset = nextOffset;     /* there are NO MultiXacts */
+   else if (!find_multixact_start(frozenMulti, &oldestAliveOffset))
+   {
+       ereport(LOG,
+               (errmsg("supposedly still alive MultiXact %u not found, skipping truncation",
+                       frozenMulti)));
+       return;
+   }
 
-   SlruScanDirectory(MultiXactMemberCtl, SlruScanDirCbRemoveMembers, &range);
+   elog(DEBUG1, "performing multixact truncation starting (%u, %u), segments (%x to %x)",
+        oldestOffset,
+        oldestAliveOffset,
+        MXOffsetToMemberSegment(oldestOffset),
+        MXOffsetToMemberSegment(oldestAliveOffset));
 
-   /* Now we can truncate MultiXactOffset */
-   SimpleLruTruncate(MultiXactOffsetCtl,
-                     MultiXactIdToOffsetPage(oldestMXact));
+   /*
+    * Do truncation, and the WAL logging of the truncation, in a critical
+    * section. That way offsets/members cannot get out of sync anymore,
+    * i.e. once consistent the oldestMulti will always exist in members, even
+    * if we crashed in the wrong moment.
+    */
+   START_CRIT_SECTION();
 
+   /*
+    * Prevent checkpoints from being scheduled concurrently. This is critical
+    * because otherwise a truncation record might not be replayed after a
+    * crash/basebackup, even though the state of the data directory would
+    * require it.  It's not possible, and not needed, to do this during
+    * recovery, when performing a old-style truncation, though, as the
+    * startup process doesn't have a PGXACT entry.
+    */
+   if (!in_recovery)
+   {
+       Assert(!MyPgXact->delayChkpt);
+       MyPgXact->delayChkpt = true;
+   }
 
    /*
-    * Now, and only now, we can advance the stop point for multixact members.
-    * If we did it any sooner, the segments we deleted above might already
-    * have been overwritten with new members.  That would be bad.
+    * Wal log truncation - this has to be flushed before the truncation is
+    * actually performed, for the reasons explained in TruncateCLOG().
     */
-   DetermineSafeOldestOffset(oldestMXact);
+   if (!in_recovery)
+       WriteMTruncateXlogRec(oldestMXact, frozenMulti,
+                             oldestOffset, oldestAliveOffset);
+
+   /* First truncate members */
+   PerformMembersTruncation(oldestOffset, oldestAliveOffset);
+
+   /* Then offsets */
+   SimpleLruTruncate(MultiXactOffsetCtl,
+                     MultiXactIdToOffsetPage(frozenMulti));
+
+   LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
+   MultiXactState->oldestMultiXactId = frozenMulti;
+   MultiXactState->oldestMultiXactDB = minmulti_datoid;
+   LWLockRelease(MultiXactGenLock);
+
+   if (!in_recovery)
+       MyPgXact->delayChkpt = false;
+
+   END_CRIT_SECTION();
 }
 
 /*
@@ -3154,6 +3128,30 @@ WriteMZeroPageXlogRec(int pageno, uint8 info)
    (void) XLogInsert(RM_MULTIXACT_ID, info);
 }
 
+/*
+ * Write a TRUNCATE xlog record
+ *
+ * We must flush the xlog record to disk before returning --- see notes
+ * in TruncateMultiXact().
+ */
+static void
+WriteMTruncateXlogRec(MultiXactOffset startOff, MultiXactOffset endOff,
+                     MultiXactOffset startMemb, MultiXactOffset endMemb)
+{
+   XLogRecPtr  recptr;
+   xl_multixact_truncate xlrec;
+
+   xlrec.startOff = startOff;
+   xlrec.endOff = endOff;
+   xlrec.startMemb = startMemb;
+   xlrec.endMemb = endMemb;
+
+   XLogBeginInsert();
+   XLogRegisterData((char *) (&xlrec), SizeOfMultiXactTruncate);
+   recptr = XLogInsert(RM_MULTIXACT_ID, XLOG_MULTIXACT_TRUNCATE_ID);
+   XLogFlush(recptr);
+}
+
 /*
  * MULTIXACT resource manager's routines
  */
@@ -3236,6 +3234,38 @@ multixact_redo(XLogReaderState *record)
            LWLockRelease(XidGenLock);
        }
    }
+   else if (info == XLOG_MULTIXACT_TRUNCATE_ID)
+   {
+       xl_multixact_truncate xlrec;
+       int pageno;
+
+       memcpy(&xlrec, XLogRecGetData(record),
+              SizeOfMultiXactTruncate);
+
+       pageno = MultiXactIdToOffsetPage(xlrec.endOff);
+
+       elog(LOG, "replaying multixact truncation start: %u, %u, %x to %x",
+            xlrec.startMemb,
+            xlrec.endMemb,
+            MXOffsetToMemberSegment(xlrec.startMemb),
+            MXOffsetToMemberSegment(xlrec.endMemb));
+
+       PerformMembersTruncation(xlrec.startMemb, xlrec.endMemb);
+
+       /*
+        * During XLOG replay, latest_page_number isn't necessarily set up
+        * yet; insert a suitable value to bypass the sanity test in
+        * SimpleLruTruncate.
+        *
+        * XXX: We probably don't need this.
+        */
+       MultiXactOffsetCtl->shared->latest_page_number = pageno;
+       SimpleLruTruncate(MultiXactOffsetCtl,
+                         MultiXactIdToOffsetPage(xlrec.endOff));
+
+       /* only looked at in the startup process, no lock necessary */
+       MultiXactState->sawTruncationCkptCyle = true;
+   }
    else
        elog(PANIC, "multixact_redo: unknown op code %u", info);
 }
index 5fcea113eafeafe7cd30073e5300caa1db7431af..e2b79ae6515d4f19729ff296b9b73b2631ed3ef6 100644 (file)
@@ -134,6 +134,7 @@ static int  SlruSelectLRUPage(SlruCtl ctl, int pageno);
 
 static bool SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename,
                          int segpage, void *data);
+static void SlruInternalDeleteSegment(SlruCtl ctl, char *filename);
 
 /*
  * Initialization of shared memory
@@ -1075,7 +1076,7 @@ SlruSelectLRUPage(SlruCtl ctl, int pageno)
  * Flush dirty pages to disk during checkpoint or database shutdown
  */
 void
-SimpleLruFlush(SlruCtl ctl, bool checkpoint)
+SimpleLruFlush(SlruCtl ctl, bool allow_redirtied)
 {
    SlruShared  shared = ctl->shared;
    SlruFlushData fdata;
@@ -1096,11 +1097,11 @@ SimpleLruFlush(SlruCtl ctl, bool checkpoint)
        SlruInternalWritePage(ctl, slotno, &fdata);
 
        /*
-        * When called during a checkpoint, we cannot assert that the slot is
-        * clean now, since another process might have re-dirtied it already.
-        * That's okay.
+        * In some places (e.g. checkpoints), we cannot assert that the slot
+        * is clean now, since another process might have re-dirtied it
+        * already.  That's okay.
         */
-       Assert(checkpoint ||
+       Assert(allow_redirtied ||
               shared->page_status[slotno] == SLRU_PAGE_EMPTY ||
               (shared->page_status[slotno] == SLRU_PAGE_VALID &&
                !shared->page_dirty[slotno]));
@@ -1210,8 +1211,14 @@ restart:;
    (void) SlruScanDirectory(ctl, SlruScanDirCbDeleteCutoff, &cutoffPage);
 }
 
-void
-SlruDeleteSegment(SlruCtl ctl, char *filename)
+/*
+ * Delete an individual SLRU segment, identified by the filename.
+ *
+ * NB: This does not touch the SLRU buffers themselves, callers have to ensure
+ * they either can't yet contain anything, or have already been cleaned out.
+ */
+static void
+SlruInternalDeleteSegment(SlruCtl ctl, char *filename)
 {
    char        path[MAXPGPATH];
 
@@ -1221,6 +1228,70 @@ SlruDeleteSegment(SlruCtl ctl, char *filename)
    unlink(path);
 }
 
+/*
+ * Delete an individual SLRU segment, identified by the segment number.
+ */
+void
+SlruDeleteSegment(SlruCtl ctl, int segno)
+{
+   SlruShared  shared = ctl->shared;
+   int         slotno;
+   char        path[MAXPGPATH];
+   bool        did_write;
+
+   /* Clean out any possibly existing references to the segment. */
+   LWLockAcquire(shared->ControlLock, LW_EXCLUSIVE);
+restart:
+   did_write = false;
+   for (slotno = 0; slotno < shared->num_slots; slotno++)
+   {
+       int pagesegno = shared->page_number[slotno] / SLRU_PAGES_PER_SEGMENT;
+
+       if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)
+           continue;
+
+       /* not the segment we're looking for */
+       if (pagesegno != segno)
+           continue;
+
+       /* If page is clean, just change state to EMPTY (expected case). */
+       if (shared->page_status[slotno] == SLRU_PAGE_VALID &&
+           !shared->page_dirty[slotno])
+       {
+           shared->page_status[slotno] = SLRU_PAGE_EMPTY;
+           continue;
+       }
+
+       /*
+        * Hmm, we have (or may have) I/O operations acting on the page, so
+        * we've got to wait for them to finish and then start again. This is
+        * the same logic as in SlruSelectLRUPage.  (XXX if page is dirty,
+        * wouldn't it be OK to just discard it without writing it?  For now,
+        * keep the logic the same as it was.)
+        */
+       if (shared->page_status[slotno] == SLRU_PAGE_VALID)
+           SlruInternalWritePage(ctl, slotno, NULL);
+       else
+           SimpleLruWaitIO(ctl, slotno);
+
+       did_write = true;
+   }
+
+   /*
+    * Be extra careful and re-check. The IO functions release the control
+    * lock, so new pages could have been read in.
+    */
+   if (did_write)
+       goto restart;
+
+   snprintf(path, MAXPGPATH, "%s/%04X", ctl->Dir, segno);
+   ereport(DEBUG2,
+           (errmsg("removing file \"%s\"", path)));
+   unlink(path);
+
+   LWLockRelease(shared->ControlLock);
+}
+
 /*
  * SlruScanDirectory callback
  *     This callback reports true if there's any segment prior to the one
@@ -1249,7 +1320,7 @@ SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int segpage, void *data)
    int         cutoffPage = *(int *) data;
 
    if (ctl->PagePrecedes(segpage, cutoffPage))
-       SlruDeleteSegment(ctl, filename);
+       SlruInternalDeleteSegment(ctl, filename);
 
    return false;               /* keep going */
 }
@@ -1261,7 +1332,7 @@ SlruScanDirCbDeleteCutoff(SlruCtl ctl, char *filename, int segpage, void *data)
 bool
 SlruScanDirCbDeleteAll(SlruCtl ctl, char *filename, int segpage, void *data)
 {
-   SlruDeleteSegment(ctl, filename);
+   SlruInternalDeleteSegment(ctl, filename);
 
    return false;               /* keep going */
 }
index 80389ba8e1fc9bec4344b007e0833e9d393b647e..000415961fca90c705369f9953b5a4f8b14d8093 100644 (file)
@@ -6287,7 +6287,6 @@ StartupXLOG(void)
    SetMultiXactIdLimit(checkPoint.oldestMulti, checkPoint.oldestMultiDB);
    SetCommitTsLimit(checkPoint.oldestCommitTs,
                     checkPoint.newestCommitTs);
-   MultiXactSetSafeTruncate(checkPoint.oldestMulti);
    XLogCtl->ckptXidEpoch = checkPoint.nextXidEpoch;
    XLogCtl->ckptXid = checkPoint.nextXid;
 
@@ -6304,10 +6303,8 @@ StartupXLOG(void)
    StartupReorderBuffer();
 
    /*
-    * 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.
+    * Startup MultiXact, we need to do this early, to be able to replay
+    * truncations.
     */
    StartupMultiXact();
 
@@ -8464,12 +8461,6 @@ CreateCheckPoint(int flags)
     */
    END_CRIT_SECTION();
 
-   /*
-    * Now that the checkpoint is safely on disk, we can update the point to
-    * which multixact can be truncated.
-    */
-   MultiXactSetSafeTruncate(checkPoint.oldestMulti);
-
    /*
     * Let smgr do post-checkpoint cleanup (eg, deleting old files).
     */
@@ -8509,11 +8500,6 @@ CreateCheckPoint(int flags)
    if (!RecoveryInProgress())
        TruncateSUBTRANS(GetOldestXmin(NULL, false));
 
-   /*
-    * Truncate pg_multixact too.
-    */
-   TruncateMultiXact();
-
    /* Real work is done, but log and update stats before releasing lock. */
    LogCheckpointEnd(false);
 
@@ -8843,21 +8829,6 @@ CreateRestartPoint(int flags)
            ThisTimeLineID = 0;
    }
 
-   /*
-    * Due to a 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 to access those multis.
-    *
-    * It's probably worth improving this.
-    */
-   TruncateMultiXact();
-
    /*
     * Truncate pg_subtrans if possible.  We can throw away all data before
     * the oldest XMIN of any running transaction.  No future transaction will
@@ -9218,9 +9189,13 @@ xlog_redo(XLogReaderState *record)
        LWLockRelease(OidGenLock);
        MultiXactSetNextMXact(checkPoint.nextMulti,
                              checkPoint.nextMultiOffset);
+       /*
+        * NB: This may perform multixact truncation when replaying WAL
+        * generated by an older primary.
+        */
+       MultiXactAdvanceOldest(checkPoint.oldestMulti,
+                              checkPoint.oldestMultiDB);
        SetTransactionIdLimit(checkPoint.oldestXid, checkPoint.oldestXidDB);
-       SetMultiXactIdLimit(checkPoint.oldestMulti, checkPoint.oldestMultiDB);
-       MultiXactSetSafeTruncate(checkPoint.oldestMulti);
 
        /*
         * If we see a shutdown checkpoint while waiting for an end-of-backup
@@ -9310,14 +9285,16 @@ xlog_redo(XLogReaderState *record)
        LWLockRelease(OidGenLock);
        MultiXactAdvanceNextMXact(checkPoint.nextMulti,
                                  checkPoint.nextMultiOffset);
+       /*
+        * NB: This may perform multixact truncation when replaying WAL
+        * generated by an older primary.
+        */
+       MultiXactAdvanceOldest(checkPoint.oldestMulti,
+                              checkPoint.oldestMultiDB);
        if (TransactionIdPrecedes(ShmemVariableCache->oldestXid,
                                  checkPoint.oldestXid))
            SetTransactionIdLimit(checkPoint.oldestXid,
                                  checkPoint.oldestXidDB);
-       MultiXactAdvanceOldest(checkPoint.oldestMulti,
-                              checkPoint.oldestMultiDB);
-       MultiXactSetSafeTruncate(checkPoint.oldestMulti);
-
        /* ControlFile->checkPointCopy always tracks the latest ckpt XID */
        ControlFile->checkPointCopy.nextXidEpoch = checkPoint.nextXidEpoch;
        ControlFile->checkPointCopy.nextXid = checkPoint.nextXid;
index baf66f1e6c01e6db1ae4b9b06eda85f424545b12..c1433e96f0ea6af48ef0e59971a4cbe6ce7b1143 100644 (file)
@@ -1134,11 +1134,11 @@ vac_truncate_clog(TransactionId frozenXID,
        return;
 
    /*
-    * Truncate CLOG and CommitTs to the oldest computed value. Note we don't
-    * truncate multixacts; that will be done by the next checkpoint.
+    * Truncate CLOG, multixact and CommitTs to the oldest computed value.
     */
    TruncateCLOG(frozenXID);
    TruncateCommitTs(frozenXID, true);
+   TruncateMultiXact(minMulti, minmulti_datoid, false);
 
    /*
     * Update the wrap limit for GetNewTransactionId and creation of new
index 6213f8a0aebdcc940a3f88078c79e9eb0758420b..bfcbbc43ef98b871d7d0cc983aa1646693ae9032 100644 (file)
@@ -71,6 +71,7 @@ typedef struct MultiXactMember
 #define XLOG_MULTIXACT_ZERO_OFF_PAGE   0x00
 #define XLOG_MULTIXACT_ZERO_MEM_PAGE   0x10
 #define XLOG_MULTIXACT_CREATE_ID       0x20
+#define XLOG_MULTIXACT_TRUNCATE_ID     0x30
 
 typedef struct xl_multixact_create
 {
@@ -82,6 +83,16 @@ typedef struct xl_multixact_create
 
 #define SizeOfMultiXactCreate (offsetof(xl_multixact_create, members))
 
+typedef struct xl_multixact_truncate
+{
+   MultiXactOffset startOff;
+   MultiXactOffset endOff;
+
+   MultiXactOffset startMemb;
+   MultiXactOffset endMemb;
+} xl_multixact_truncate;
+#define SizeOfMultiXactTruncate (sizeof(xl_multixact_truncate))
+
 
 extern MultiXactId MultiXactIdCreate(TransactionId xid1,
                  MultiXactStatus status1, TransactionId xid2,
@@ -120,13 +131,12 @@ extern void MultiXactGetCheckptMulti(bool is_shutdown,
                         Oid *oldestMultiDB);
 extern void CheckPointMultiXact(void);
 extern MultiXactId GetOldestMultiXactId(void);
-extern void TruncateMultiXact(void);
+extern void TruncateMultiXact(MultiXactId oldestMulti, Oid oldestMultiDB, bool inRecovery);
 extern void MultiXactSetNextMXact(MultiXactId nextMulti,
                      MultiXactOffset nextMultiOffset);
 extern void MultiXactAdvanceNextMXact(MultiXactId minMulti,
                          MultiXactOffset minMultiOffset);
 extern void MultiXactAdvanceOldest(MultiXactId oldestMulti, Oid oldestMultiDB);
-extern void MultiXactSetSafeTruncate(MultiXactId safeTruncateMulti);
 extern int MultiXactMemberFreezeThreshold(void);
 
 extern void multixact_twophase_recover(TransactionId xid, uint16 info,
index 9c7f01933f7eb031ebbf2f1ec1b0a5b96a4feb73..f60e75b569a2cce391e30d3ffcebf8a9c804a1e4 100644 (file)
@@ -143,14 +143,14 @@ extern int SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok,
 extern int SimpleLruReadPage_ReadOnly(SlruCtl ctl, int pageno,
                           TransactionId xid);
 extern void SimpleLruWritePage(SlruCtl ctl, int slotno);
-extern void SimpleLruFlush(SlruCtl ctl, bool checkpoint);
+extern void SimpleLruFlush(SlruCtl ctl, bool allow_redirtied);
 extern void SimpleLruTruncate(SlruCtl ctl, int cutoffPage);
 extern bool SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno);
 
 typedef bool (*SlruScanCallback) (SlruCtl ctl, char *filename, int segpage,
                                              void *data);
 extern bool SlruScanDirectory(SlruCtl ctl, SlruScanCallback callback, void *data);
-extern void SlruDeleteSegment(SlruCtl ctl, char *filename);
+extern void SlruDeleteSegment(SlruCtl ctl, int segno);
 
 /* SlruScanDirectory public callbacks */
 extern bool SlruScanDirCbReportPresence(SlruCtl ctl, char *filename,