]> granicus.if.org Git - postgresql/commitdiff
Make bgwriter sleep longer when it has no work to do, to save electricity.
authorHeikki Linnakangas <heikki.linnakangas@iki.fi>
Thu, 26 Jan 2012 16:19:48 +0000 (18:19 +0200)
committerHeikki Linnakangas <heikki.linnakangas@iki.fi>
Thu, 26 Jan 2012 16:39:13 +0000 (18:39 +0200)
To make it wake up promptly when activity starts again, backends nudge it
by setting a latch in MarkBufferDirty(). The latch is kept set while
bgwriter is active, so there is very little overhead from that when the
system is busy. It is only armed before going into longer sleep.

Peter Geoghegan, with some changes by me.

src/backend/postmaster/bgwriter.c
src/backend/storage/buffer/bufmgr.c
src/include/storage/bufmgr.h
src/include/storage/proc.h

index 1f8d2d6bdbb569bf309da36f88b5dca5810ad6dd..59c539a4e0e532c10b89bed1b6153d9247be634a 100644 (file)
@@ -51,6 +51,7 @@
 #include "storage/ipc.h"
 #include "storage/lwlock.h"
 #include "storage/pmsignal.h"
+#include "storage/proc.h"
 #include "storage/shmem.h"
 #include "storage/smgr.h"
 #include "storage/spin.h"
  */
 int                    BgWriterDelay = 200;
 
+/*
+ * Time to sleep between bgwriter rounds, when it has no work to do.
+ */
+#define BGWRITER_HIBERNATE_MS                  10000
+
 /*
  * Flags set by interrupt handlers for later service in the main loop.
  */
@@ -77,13 +83,14 @@ static bool am_bg_writer = false;
 
 /* Prototypes for private functions */
 
-static void BgWriterNap(void);
+static void BgWriterNap(bool hibernating);
 
 /* Signal handlers */
 
 static void bg_quickdie(SIGNAL_ARGS);
 static void BgSigHupHandler(SIGNAL_ARGS);
 static void ReqShutdownHandler(SIGNAL_ARGS);
+static void bgwriter_sigusr1_handler(SIGNAL_ARGS);
 
 
 /*
@@ -97,6 +104,7 @@ BackgroundWriterMain(void)
 {
        sigjmp_buf      local_sigjmp_buf;
        MemoryContext bgwriter_context;
+       bool            hibernating;
 
        am_bg_writer = true;
 
@@ -112,10 +120,10 @@ BackgroundWriterMain(void)
 #endif
 
        /*
-        * Properly accept or ignore signals the postmaster might send us
+        * Properly accept or ignore signals the postmaster might send us.
         *
-        * SIGUSR1 is presently unused; keep it spare in case someday we want this
-        * process to participate in ProcSignal signalling.
+        * bgwriter doesn't participate in ProcSignal signalling, but a SIGUSR1
+        * handler is still needed for latch wakeups.
         */
        pqsignal(SIGHUP, BgSigHupHandler);      /* set flag to read config file */
        pqsignal(SIGINT, SIG_IGN);                      /* as of 9.2 no longer requests checkpoint */
@@ -123,7 +131,7 @@ BackgroundWriterMain(void)
        pqsignal(SIGQUIT, bg_quickdie);         /* hard crash time */
        pqsignal(SIGALRM, SIG_IGN);
        pqsignal(SIGPIPE, SIG_IGN);
-       pqsignal(SIGUSR1, SIG_IGN);                     /* reserve for ProcSignal */
+       pqsignal(SIGUSR1, bgwriter_sigusr1_handler);
        pqsignal(SIGUSR2, SIG_IGN);
 
        /*
@@ -138,6 +146,12 @@ BackgroundWriterMain(void)
        /* We allow SIGQUIT (quickdie) at all times */
        sigdelset(&BlockSig, SIGQUIT);
 
+       /*
+        * Advertise our latch that backends can use to wake us up while we're
+        * sleeping.
+        */
+       ProcGlobal->bgwriterLatch = &MyProc->procLatch;
+
        /*
         * Create a resource owner to keep track of our resources (currently only
         * buffer pins).
@@ -235,8 +249,11 @@ BackgroundWriterMain(void)
        /*
         * Loop forever
         */
+       hibernating = false;
        for (;;)
        {
+               bool            lapped;
+
                /*
                 * Emergency bailout if postmaster has died.  This is to avoid the
                 * necessity for manual cleanup of all postmaster children.
@@ -264,18 +281,66 @@ BackgroundWriterMain(void)
                /*
                 * Do one cycle of dirty-buffer writing.
                 */
-               BgBufferSync();
+               if (hibernating && bgwriter_lru_maxpages > 0)
+                       ResetLatch(&MyProc->procLatch);
+               lapped = BgBufferSync();
+
+               if (lapped && !hibernating)
+               {
+                       /*
+                        * BgBufferSync did nothing. Since there doesn't seem to be any
+                        * work for the bgwriter to do, go into slower-paced
+                        * "hibernation" mode, where we sleep for much longer times than
+                        * bgwriter_delay says. Fewer wakeups saves electricity. If a
+                        * backend starts dirtying pages again, it will wake us up by
+                        * setting our latch.
+                        *
+                        * The latch is kept set during productive cycles where buffers
+                        * are written, and only reset before going into a longer sleep.
+                        * That ensures that when there's a constant trickle of activity,
+                        * the SetLatch() calls that backends have to do will see the
+                        * latch as already set, and are not slowed down by having to
+                        * actually set the latch and signal us.
+                        */
+                       hibernating = true;
 
-               /* Nap for the configured time. */
-               BgWriterNap();
+                       /*
+                        * Take one more short nap and perform one more bgwriter cycle -
+                        * someone might've dirtied a buffer just after we finished the
+                        * previous bgwriter cycle, while the latch was still set. If
+                        * we still find nothing to do after this cycle, the next sleep
+                        * will be longer.
+                        */
+                       BgWriterNap(false);
+                       continue;
+               }
+               else if (!lapped && hibernating)
+               {
+                       /*
+                        * Woken up from hibernation. Set the latch just in case it's
+                        * not set yet (usually we wake up from hibernation because a
+                        * backend already set the latch, but not necessarily).
+                        */
+                       SetLatch(&MyProc->procLatch);
+                       hibernating = false;
+               }
+
+               /*
+                * Take a short or long nap, depending on whether there was any work
+                * to do.
+                */
+               BgWriterNap(hibernating);
        }
 }
 
 /*
  * BgWriterNap -- Nap for the configured time or until a signal is received.
+ *
+ * If 'hibernating' is false, sleeps for bgwriter_delay milliseconds.
+ * Otherwise sleeps longer, but also wakes up if the process latch is set.
  */
 static void
-BgWriterNap(void)
+BgWriterNap(bool hibernating)
 {
        long            udelay;
 
@@ -285,18 +350,44 @@ BgWriterNap(void)
        pgstat_send_bgwriter();
 
        /*
-        * Nap for the configured time, or sleep for 10 seconds if there is no
-        * bgwriter activity configured.
+        * If there was no work to do in the previous bgwriter cycle, take a
+        * longer nap.
+        */
+       if (hibernating)
+       {
+               /*
+                * We wake on a buffer being dirtied. It's possible that some
+                * useful work will become available for the bgwriter to do without
+                * a buffer actually being dirtied, like when a dirty buffer's usage
+                * count is decremented to zero or it's unpinned. This corner case
+                * is judged as too marginal to justify adding additional SetLatch()
+                * calls in very hot code paths, cheap though those calls may be.
+                *
+                * We still wake up periodically, so that BufferAlloc stats are
+                * updated reasonably promptly.
+                */
+               int res = WaitLatch(&MyProc->procLatch,
+                                                       WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
+                                                       BGWRITER_HIBERNATE_MS);
+
+               /*
+                * Only do a quick return if timeout was reached (or postmaster died)
+                * to ensure that no less than BgWriterDelay ms has passed between
+                * BgBufferSyncs - WaitLatch() might have returned instantaneously.
+                */
+               if (res & (WL_TIMEOUT | WL_POSTMASTER_DEATH))
+                       return;
+       }
+
+       /*
+        * Nap for the configured time.
         *
         * On some platforms, signals won't interrupt the sleep.  To ensure we
         * respond reasonably promptly when someone signals us, break down the
         * sleep into 1-second increments, and check for interrupts after each
         * nap.
         */
-       if (bgwriter_lru_maxpages > 0)
-               udelay = BgWriterDelay * 1000L;
-       else
-               udelay = 10000000L;             /* Ten seconds */
+       udelay = BgWriterDelay * 1000L;
 
        while (udelay > 999999L)
        {
@@ -351,12 +442,35 @@ bg_quickdie(SIGNAL_ARGS)
 static void
 BgSigHupHandler(SIGNAL_ARGS)
 {
+       int                     save_errno = errno;
+
        got_SIGHUP = true;
+       if (MyProc)
+               SetLatch(&MyProc->procLatch);
+
+       errno = save_errno;
 }
 
 /* SIGTERM: set flag to shutdown and exit */
 static void
 ReqShutdownHandler(SIGNAL_ARGS)
 {
+       int                     save_errno = errno;
+
        shutdown_requested = true;
+       if (MyProc)
+               SetLatch(&MyProc->procLatch);
+
+       errno = save_errno;
+}
+
+/* SIGUSR1: used for latch wakeups */
+static void
+bgwriter_sigusr1_handler(SIGNAL_ARGS)
+{
+       int                     save_errno = errno;
+
+       latch_sigusr1_handler();
+
+       errno = save_errno;
 }
index 8f68bcc66d95fe9c4300d4e0070bae9557202272..1adb6d360dd5e6f7622b26a403bac387596122de 100644 (file)
@@ -953,6 +953,7 @@ void
 MarkBufferDirty(Buffer buffer)
 {
        volatile BufferDesc *bufHdr;
+       bool    dirtied = false;
 
        if (!BufferIsValid(buffer))
                elog(ERROR, "bad buffer ID: %d", buffer);
@@ -973,19 +974,25 @@ MarkBufferDirty(Buffer buffer)
 
        Assert(bufHdr->refcount > 0);
 
+       if (!(bufHdr->flags & BM_DIRTY))
+               dirtied = true;
+
+       bufHdr->flags |= (BM_DIRTY | BM_JUST_DIRTIED);
+
+       UnlockBufHdr(bufHdr);
+
        /*
-        * If the buffer was not dirty already, do vacuum accounting.
+        * If the buffer was not dirty already, do vacuum accounting, and
+        * nudge bgwriter.
         */
-       if (!(bufHdr->flags & BM_DIRTY))
+       if (dirtied)
        {
                VacuumPageDirty++;
                if (VacuumCostActive)
                        VacuumCostBalance += VacuumCostPageDirty;
+               if (ProcGlobal->bgwriterLatch)
+                       SetLatch(ProcGlobal->bgwriterLatch);
        }
-
-       bufHdr->flags |= (BM_DIRTY | BM_JUST_DIRTIED);
-
-       UnlockBufHdr(bufHdr);
 }
 
 /*
@@ -1307,8 +1314,12 @@ BufferSync(int flags)
  * BgBufferSync -- Write out some dirty buffers in the pool.
  *
  * This is called periodically by the background writer process.
+ *
+ * Returns true if the clocksweep has been "lapped", so that there's nothing
+ * to do. Also returns true if there's nothing to do because bgwriter was
+ * effectively disabled by setting bgwriter_lru_maxpages to 0.
  */
-void
+bool
 BgBufferSync(void)
 {
        /* info obtained from freelist.c */
@@ -1365,7 +1376,7 @@ BgBufferSync(void)
        if (bgwriter_lru_maxpages <= 0)
        {
                saved_info_valid = false;
-               return;
+               return true;
        }
 
        /*
@@ -1584,6 +1595,8 @@ BgBufferSync(void)
                         recent_alloc, strategy_delta, scans_per_alloc, smoothed_density);
 #endif
        }
+
+       return (bufs_to_lap == 0);
 }
 
 /*
@@ -2341,16 +2354,24 @@ SetBufferCommitInfoNeedsSave(Buffer buffer)
        if ((bufHdr->flags & (BM_DIRTY | BM_JUST_DIRTIED)) !=
                (BM_DIRTY | BM_JUST_DIRTIED))
        {
+               bool            dirtied = false;
+
                LockBufHdr(bufHdr);
                Assert(bufHdr->refcount > 0);
                if (!(bufHdr->flags & BM_DIRTY))
+                       dirtied = true;
+               bufHdr->flags |= (BM_DIRTY | BM_JUST_DIRTIED);
+               UnlockBufHdr(bufHdr);
+
+               if (dirtied)
                {
                        VacuumPageDirty++;
                        if (VacuumCostActive)
                                VacuumCostBalance += VacuumCostPageDirty;
+                       /* The bgwriter may need to be woken. */
+                       if (ProcGlobal->bgwriterLatch)
+                               SetLatch(ProcGlobal->bgwriterLatch);
                }
-               bufHdr->flags |= (BM_DIRTY | BM_JUST_DIRTIED);
-               UnlockBufHdr(bufHdr);
        }
 }
 
index a03c06874b7b9a2c33f5b731fb8327d90edfc57a..de1bbd01d839db3cdbf9dab2b466a67225d4cc8c 100644 (file)
@@ -213,7 +213,7 @@ extern bool HoldingBufferPinThatDelaysRecovery(void);
 extern void AbortBufferIO(void);
 
 extern void BufmgrCommit(void);
-extern void BgBufferSync(void);
+extern bool BgBufferSync(void);
 
 extern void AtProcExit_LocalBuffers(void);
 
index 358d1a456cbf826e226475102adb71d3ec078bf9..b68ae39268ed666c1e878a9ff89584a5f98b645a 100644 (file)
@@ -188,6 +188,8 @@ typedef struct PROC_HDR
        PGPROC     *freeProcs;
        /* Head of list of autovacuum's free PGPROC structures */
        PGPROC     *autovacFreeProcs;
+       /* BGWriter process latch */
+       Latch      *bgwriterLatch;
        /* Current shared estimate of appropriate spins_per_delay value */
        int                     spins_per_delay;
        /* The proc of the Startup process, since not in ProcArray */