Bulk operations like CREATE INDEX, ALTER TABLE, or bulk loads can create
a lot of WAL.  A lot of WAL at once can cause delays in replication.
For synchronous replication, this can make seemingly unrelated sessions
hang.  But also for asynchronous replication, it will increase latency.

One idea to address this is to slow down WAL-generating maintenance
operations.  This is similar to the vacuum delay.  Where the vacuum
delay counts notional I/O cost before sleeping, here we would count how
much WAL has been generated and sleep after some amount.

I attach an example patch for this functionality.  It introduces three
settings:

wal_insert_delay_enabled
wal_insert_delay
wal_insert_delay_size

When you turn on wal_insert_delay_enabled, then it will sleep for
wal_insert_delay after the session has produced wal_insert_delay_size of
WAL data.

The idea is that you would tune wal_insert_delay and
wal_insert_delay_size to your required performance characteristics and
then turn on wal_insert_delay_enabled individually in maintenance jobs
or similar.

To test, for example, set up pgbench with synchronous replication and
run an unrelated large index build in a separate session.  With the
settings, you can make it as fast or as slow as you want.

Tuning these settings, however, is quite mysterious I fear.  You have to
play around a lot to get settings that achieve the right balance.

So, some questions:

Is this useful?

Any other thoughts on how to configure this or do this?

Should we aim for a more general delay system, possibly including vacuum
delay and perhaps something else?

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
From d025e57adf6f018e615cca113294bf4c3133ebe6 Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <pe...@eisentraut.org>
Date: Mon, 11 Feb 2019 11:07:40 +0100
Subject: [PATCH] wal_insert_delay configuration settings

---
 doc/src/sgml/config.sgml                      | 52 +++++++++++++++++++
 src/backend/access/transam/xlog.c             | 19 +++++++
 src/backend/utils/misc/guc.c                  | 32 ++++++++++++
 src/backend/utils/misc/postgresql.conf.sample |  4 ++
 src/include/access/xlog.h                     |  3 ++
 5 files changed, 110 insertions(+)

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 7e208a4b81..a4a1525453 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -2829,6 +2829,58 @@ <title>Settings</title>
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-wal-insert-delay-enabled" 
xreflabel="wal_insert_delay_enabled">
+      <term><varname>wal_insert_delay_enabled</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>wal_insert_delay_enabled</varname> configuration 
parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        When this setting is turned on, a session sleeps a time of <xref
+        linkend="guc-wal-insert-delay"/> after WAL data of size at least <xref
+        linkend="guc-wal-insert-delay-size"/> has been generated.  The default
+        is off.
+       </para>
+
+       <para>
+        The purpose of this setting is to slow down maintenance operations, in
+        particular to avoid that maintenance operations cause replication lag
+        by writing WAL too quickly.  It is not sensible to enable this setting
+        globally.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="guc-wal-insert-delay" xreflabel="wal_insert_delay">
+      <term><varname>wal_insert_delay</varname> (<type>integer</type>)
+      <indexterm>
+       <primary><varname>wal_insert_delay</varname> configuration 
parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Sets the length of the sleep described under <xref
+        linkend="guc-wal-insert-delay-enabled"/>.  The default is 100
+        milliseconds (but the overall feature is off by default).
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="guc-wal-insert-delay-size" 
xreflabel="wal_insert_delay_size">
+      <term><varname>wal_insert_delay_size</varname> (<type>integer</type>)
+      <indexterm>
+       <primary><varname>wal_insert_delay_size</varname> configuration 
parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Sets the size of WAL inserted to trigger the sleep described under
+        <xref linkend="guc-wal-insert-delay-enabled"/>.  The default is 1 MB
+        (but the overall feature is off by default).
+       </para>
+      </listitem>
+     </varlistentry>
      </variablelist>
      </sect2>
      <sect2 id="runtime-config-wal-checkpoints">
diff --git a/src/backend/access/transam/xlog.c 
b/src/backend/access/transam/xlog.c
index a9f3272849..3298e258c7 100644
--- a/src/backend/access/transam/xlog.c
+++ b/src/backend/access/transam/xlog.c
@@ -100,6 +100,9 @@ int                 wal_level = WAL_LEVEL_MINIMAL;
 int                    CommitDelay = 0;        /* precommit delay in 
microseconds */
 int                    CommitSiblings = 5; /* # concurrent xacts needed to 
sleep */
 int                    wal_retrieve_retry_interval = 5000;
+bool           wal_insert_delay_enabled = false;
+int                    wal_insert_delay;
+int                    wal_insert_delay_size;
 
 #ifdef WAL_DEBUG
 bool           XLOG_DEBUG = false;
@@ -1220,6 +1223,22 @@ XLogInsertRecord(XLogRecData *rdata,
        }
 #endif
 
+       /*
+        * Delay if requested
+        */
+       if (wal_insert_delay_enabled)
+       {
+               static int64 wal_insert_balance = 0;
+
+               wal_insert_balance += (EndPos - StartPos);
+
+               while (wal_insert_balance > wal_insert_delay_size)
+               {
+                       pg_usleep(wal_insert_delay * 1000L);
+                       wal_insert_balance -= wal_insert_delay_size;
+               }
+       }
+
        /*
         * Update our global variables
         */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index ea5444c6f1..ef8b1fd0e2 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1176,6 +1176,16 @@ static struct config_bool ConfigureNamesBool[] =
                NULL, NULL, NULL
        },
 
+       {
+               {"wal_insert_delay_enabled", PGC_USERSET, WAL_SETTINGS,
+                       gettext_noop("Sleeps after inserting a certain amount 
of WAL."),
+                       NULL
+               },
+               &wal_insert_delay_enabled,
+               false,
+               NULL, NULL, NULL
+       },
+
        {
                {"log_checkpoints", PGC_SIGHUP, LOGGING_WHAT,
                        gettext_noop("Logs each checkpoint."),
@@ -2079,6 +2089,28 @@ static struct config_int ConfigureNamesInt[] =
                NULL, NULL, NULL
        },
 
+       {
+               {"wal_insert_delay", PGC_USERSET, WAL_SETTINGS,
+                       gettext_noop("Sets the sleep time after inserting a 
certain amount of WAL."),
+                       NULL,
+                       GUC_UNIT_MS
+               },
+               &wal_insert_delay,
+               100, 0, INT_MAX,
+               NULL, NULL, NULL
+       },
+
+       {
+               {"wal_insert_delay_size", PGC_USERSET, WAL_SETTINGS,
+                       gettext_noop("Sets the amount of WAL after which to 
sleep."),
+                       NULL,
+                       GUC_UNIT_BYTE
+               },
+               &wal_insert_delay_size,
+               1024 * 1024, 0, INT_MAX,
+               NULL, NULL, NULL
+       },
+
        {
                {"max_connections", PGC_POSTMASTER, CONN_AUTH_SETTINGS,
                        gettext_noop("Sets the maximum number of concurrent 
connections."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample 
b/src/backend/utils/misc/postgresql.conf.sample
index ad6c436f93..62d75da3c7 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -214,6 +214,10 @@
 #commit_delay = 0                      # range 0-100000, in microseconds
 #commit_siblings = 5                   # range 1-1000
 
+#wal_insert_delay_enabled = off
+#wal_insert_delay = 100ms
+#wal_insert_delay_size = 1MB
+
 # - Checkpoints -
 
 #checkpoint_timeout = 5min             # range 30s-1d
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index f90a6a9139..c66a77b18e 100644
--- a/src/include/access/xlog.h
+++ b/src/include/access/xlog.h
@@ -127,6 +127,9 @@ extern int  recoveryTargetAction;
 extern int     recovery_min_apply_delay;
 extern char *PrimaryConnInfo;
 extern char *PrimarySlotName;
+extern bool    wal_insert_delay_enabled;
+extern int     wal_insert_delay;
+extern int     wal_insert_delay_size;
 
 /* indirectly set via GUC system */
 extern TransactionId recoveryTargetXid;
-- 
2.20.1

Reply via email to