Received: from malur.postgresql.org ([2a02:16a8:dc51::56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.89) (envelope-from ) id 1ftm0I-0000J7-4x for pgsql-hackers@arkaria.postgresql.org; Sun, 26 Aug 2018 03:46:18 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1ftm0G-0003Kg-DE for pgsql-hackers@arkaria.postgresql.org; Sun, 26 Aug 2018 03:46:16 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA384:256) (Exim 4.89) (envelope-from ) id 1ftm0F-0003KZ-TV for pgsql-hackers@lists.postgresql.org; Sun, 26 Aug 2018 03:46:16 +0000 Received: from mail-pl1-x62c.google.com ([2607:f8b0:4864:20::62c]) by magus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1ftm06-0007C3-VV for pgsql-hackers@postgresql.org; Sun, 26 Aug 2018 03:46:14 +0000 Received: by mail-pl1-x62c.google.com with SMTP id u11-v6so2672642plq.5 for ; Sat, 25 Aug 2018 20:46:06 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=leadboat.com; s=google; h=date:from:to:subject:message-id:mime-version:content-disposition :user-agent; bh=XjgregI21Fvp65aQ2nIByi6JNTgbdR/2JzMUXvsoX3k=; b=KLh/uV008yi66JdTMXRqtDrxAg6m9eB0JI54X2sl1vg6s9DWkMYUW3Mi7JcZXRMG+F Tkombe+9jrr8n50AXE5J0DvbaDfypDyML2FKdLIV69a2rIQXniL6M/EsJxZ51XQ4RYmT hGksk8gcr578MHpdV491E+YiS7r53RRBNsGa0= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:subject:message-id:mime-version :content-disposition:user-agent; bh=XjgregI21Fvp65aQ2nIByi6JNTgbdR/2JzMUXvsoX3k=; b=KR6Pomc7E4JlRK6X9R4RSxGlJm9Y/ulOYseFiLoeL/VhrbEXmrn8tg/bRCoAwzrVAL GCYj98KCZMcX1bwAQ+xzXIrLR3Zzvza6VQ/GoKDWwV4dRFjEvUCPWYmVzPpG1sKx2Ocw gvUG9DZ1rUy+2aYT7/9sd9dUcm1L3d+htRtXXdxWsWtZCvD72YTAcEQHiQuaapGbDNFt hWwjsLOsY7kVgrncrzl1BHSRdgDuP8XLJEmRE96WN6Lp4CSK0vYcDBg8uvvQn+B1YY8Q +lruvJ3SRNTA8RAbgWefubpdlf/pu7yDwhs4Obk+iqaNfHizDYUQSl2JFeW4W49dY1XH PZfw== X-Gm-Message-State: APzg51BtYG8KLeO8zM+HKIP6Hgk1urgA9R9RKgISqtuNs0m88jKC8iSg NV7cJgoao6EasYayBdyf1GGqBuoE5oM= X-Google-Smtp-Source: ANB0Vda77iXkH8/6fDBamyowH8mk3UJjaWyG3kXYIilh63yuRBXc3ugZVTqWecEK8s8Gq2zuKzvCFw== X-Received: by 2002:a17:902:42a3:: with SMTP id h32-v6mr7878833pld.105.1535255163821; Sat, 25 Aug 2018 20:46:03 -0700 (PDT) Received: from rfd.leadboat.com (c-73-158-248-138.hsd1.ca.comcast.net. [73.158.248.138]) by smtp.gmail.com with ESMTPSA id d10-v6sm12737885pgo.2.2018.08.25.20.46.01 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sat, 25 Aug 2018 20:46:02 -0700 (PDT) Date: Sat, 25 Aug 2018 20:46:00 -0700 From: Noah Misch To: pgsql-hackers@postgresql.org Subject: wal_sender_timeout should ignore server-side latency Message-ID: <20180826034600.GA1105084@rfd.leadboat.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="tThc/1wpZn/ma/RB" Content-Disposition: inline User-Agent: Mutt/1.5.24 (2015-08-30) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk --tThc/1wpZn/ma/RB Content-Type: text/plain; charset=us-ascii Content-Disposition: inline WalSndLoop() does this, simplifying considerably: for (;;) { /* does: last_reply_timestamp = GetCurrentTimestamp() */ ProcessRepliesIfAny(); send_data(); /* e.g. XLogSendPhysical(), which calls XLogRead() */ WalSndCheckTimeOut(GetCurrentTimestamp()); } A consequence is that any time spent in the send_data() callback counts against the timeout. In particular, if a single send_data() takes longer than wal_sender_timeout, the client is powerless to prevent a timeout. This disagrees with the wal_sender_timeout documentation ("Terminate replication connections that are inactive longer than the specified number of milliseconds. This is useful for the sending server to detect a standby crash or network outage"). I find it undesirable. The fix, attached, is to interpret the timeout relative to a timestamp taken before ProcessRepliesIfAny() polls the socket. If that timestamp is wal_sender_timeout later than the last reply, we can terminate with confidence. This adds one gettimeofday() per ProcessRepliesIfAny() finding no replies, which feels cheap enough. We've seen a number of wal_sender_timeout buildfarm failures on systems with I/O performance trouble: https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=tern&dt=2018-08-16%2020:55:57 https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=tern&dt=2018-06-30%2020:38:10 https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=hornet&dt=2018-04-12%2018:12:36 https://buildfarm.postgresql.org/cgi-bin/show_log.pl?nm=mandrill&dt=2018-01-13%2005:01:17 https://postgr.es/m/flat/20170604211229.GA1528911@rfd.leadboat.com Fixing $SUBJECT won't necessarily cure that, because an I/O stall on the client side can still cause a failure. We'd need something like threads or async I/O to avoid that. I mention a less-important corner case in the WalSndCheckTimeOut() header comment. You can simulate slow XLogSendPhysical() to explore these problems on any system: --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -65,2 +65,3 @@ #include "libpq/pqformat.h" +#include "libpq/pqsignal.h" #include "miscadmin.h" @@ -2731,2 +2732,5 @@ XLogSendPhysical(void) enlargeStringInfo(&output_message, nbytes); + PG_SETMASK(&BlockSig); + pg_usleep(65 * 1000 * 1000); + PG_SETMASK(&UnBlockSig); XLogRead(&output_message.data[output_message.len], startptr, nbytes); --tThc/1wpZn/ma/RB Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="wal_sender_timeout-server-independent-v1.patch" diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index c83ff3b..3e51cf3 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -161,9 +161,12 @@ static StringInfoData output_message; static StringInfoData reply_message; static StringInfoData tmpbuf; +/* Timestamp of last ProcessRepliesIfAny(). */ +static TimestampTz last_processing = 0; + /* - * Timestamp of the last receipt of the reply from the standby. Set to 0 if - * wal_sender_timeout doesn't need to be active. + * Timestamp of last ProcessRepliesIfAny() that saw a reply from the + * standby. Set to 0 if wal_sender_timeout doesn't need to be active. */ static TimestampTz last_reply_timestamp = 0; @@ -240,8 +243,8 @@ static void ProcessStandbyReplyMessage(void); static void ProcessStandbyHSFeedbackMessage(void); static void ProcessRepliesIfAny(void); static void WalSndKeepalive(bool requestReply); -static void WalSndKeepaliveIfNecessary(TimestampTz now); -static void WalSndCheckTimeOut(TimestampTz now); +static void WalSndKeepaliveIfNecessary(void); +static void WalSndCheckTimeOut(void); static long WalSndComputeSleeptime(TimestampTz now); static void WalSndPrepareWrite(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write); static void WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, bool last_write); @@ -1202,18 +1205,16 @@ WalSndWriteData(LogicalDecodingContext *ctx, XLogRecPtr lsn, TransactionId xid, /* Check for input from the client */ ProcessRepliesIfAny(); - now = GetCurrentTimestamp(); - /* die if timeout was reached */ - WalSndCheckTimeOut(now); + WalSndCheckTimeOut(); /* Send keepalive if the time has come */ - WalSndKeepaliveIfNecessary(now); + WalSndKeepaliveIfNecessary(); if (!pq_is_send_pending()) break; - sleeptime = WalSndComputeSleeptime(now); + sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp()); wakeEvents = WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_SOCKET_WRITEABLE | WL_SOCKET_READABLE | WL_TIMEOUT; @@ -1308,7 +1309,6 @@ WalSndWaitForWal(XLogRecPtr loc) for (;;) { long sleeptime; - TimestampTz now; /* * Emergency bailout if postmaster has died. This is to avoid the @@ -1393,13 +1393,11 @@ WalSndWaitForWal(XLogRecPtr loc) !pq_is_send_pending()) break; - now = GetCurrentTimestamp(); - /* die if timeout was reached */ - WalSndCheckTimeOut(now); + WalSndCheckTimeOut(); /* Send keepalive if the time has come */ - WalSndKeepaliveIfNecessary(now); + WalSndKeepaliveIfNecessary(); /* * Sleep until something happens or we time out. Also wait for the @@ -1408,7 +1406,7 @@ WalSndWaitForWal(XLogRecPtr loc) * new WAL to be generated. (But if we have nothing to send, we don't * want to wake on socket-writable.) */ - sleeptime = WalSndComputeSleeptime(now); + sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp()); wakeEvents = WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_SOCKET_READABLE | WL_TIMEOUT; @@ -1605,6 +1603,8 @@ ProcessRepliesIfAny(void) int r; bool received = false; + last_processing = GetCurrentTimestamp(); + for (;;) { pq_startmsgread(); @@ -1692,7 +1692,7 @@ ProcessRepliesIfAny(void) */ if (received) { - last_reply_timestamp = GetCurrentTimestamp(); + last_reply_timestamp = last_processing; waiting_for_ping_response = false; } } @@ -2071,10 +2071,18 @@ WalSndComputeSleeptime(TimestampTz now) /* * Check whether there have been responses by the client within - * wal_sender_timeout and shutdown if not. + * wal_sender_timeout and shutdown if not. Using last_processing as the + * reference point avoids counting server-side stalls against the client. + * However, a long server-side stall can make WalSndKeepaliveIfNecessary() + * postdate last_processing by more than wal_sender_timeout. If that happens, + * the client must reply almost immediately to avoid a timeout. This rarely + * affects the default configuration, under which clients spontaneously send a + * message every standby_message_timeout = wal_sender_timeout/6 = 10s. We + * could eliminate that problem by recognizing timeout expiration at + * wal_sender_timeout/2 after the keepalive. */ static void -WalSndCheckTimeOut(TimestampTz now) +WalSndCheckTimeOut(void) { TimestampTz timeout; @@ -2085,7 +2093,7 @@ WalSndCheckTimeOut(TimestampTz now) timeout = TimestampTzPlusMilliseconds(last_reply_timestamp, wal_sender_timeout); - if (wal_sender_timeout > 0 && now >= timeout) + if (wal_sender_timeout > 0 && last_processing >= timeout) { /* * Since typically expiration of replication timeout means @@ -2116,8 +2124,6 @@ WalSndLoop(WalSndSendDataCallback send_data) */ for (;;) { - TimestampTz now; - /* * Emergency bailout if postmaster has died. This is to avoid the * necessity for manual cleanup of all postmaster children. @@ -2195,13 +2201,11 @@ WalSndLoop(WalSndSendDataCallback send_data) WalSndDone(send_data); } - now = GetCurrentTimestamp(); - /* Check for replication timeout. */ - WalSndCheckTimeOut(now); + WalSndCheckTimeOut(); /* Send keepalive if the time has come */ - WalSndKeepaliveIfNecessary(now); + WalSndKeepaliveIfNecessary(); /* * We don't block if not caught up, unless there is unsent data @@ -2219,7 +2223,11 @@ WalSndLoop(WalSndSendDataCallback send_data) wakeEvents = WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_TIMEOUT | WL_SOCKET_READABLE; - sleeptime = WalSndComputeSleeptime(now); + /* + * Use fresh timestamp, not last_processed, to reduce the chance + * of reaching wal_sender_timeout before sending a keepalive. + */ + sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp()); if (pq_is_send_pending()) wakeEvents |= WL_SOCKET_WRITEABLE; @@ -3379,7 +3387,7 @@ WalSndKeepalive(bool requestReply) * Send keepalive message if too much time has elapsed. */ static void -WalSndKeepaliveIfNecessary(TimestampTz now) +WalSndKeepaliveIfNecessary(void) { TimestampTz ping_time; @@ -3400,7 +3408,7 @@ WalSndKeepaliveIfNecessary(TimestampTz now) */ ping_time = TimestampTzPlusMilliseconds(last_reply_timestamp, wal_sender_timeout / 2); - if (now >= ping_time) + if (last_processing >= ping_time) { WalSndKeepalive(true); waiting_for_ping_response = true; --tThc/1wpZn/ma/RB--