Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1jLLNx-0006ir-Fr for pgsql-hackers@arkaria.postgresql.org; Mon, 06 Apr 2020 06:37:29 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1jLLNQ-0001xm-Pl for pgsql-hackers@arkaria.postgresql.org; Mon, 06 Apr 2020 06:36:56 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1jLLNQ-0001vz-6K for pgsql-hackers@lists.postgresql.org; Mon, 06 Apr 2020 06:36:56 +0000 Received: from mail-pj1-x1043.google.com ([2607:f8b0:4864:20::1043]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1jLLNN-0004zG-Ep for pgsql-hackers@postgresql.org; Mon, 06 Apr 2020 06:36:54 +0000 Received: by mail-pj1-x1043.google.com with SMTP id cp9so2598528pjb.0 for ; Sun, 05 Apr 2020 23:36:53 -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=heqoSUJ24c31zI2zPR+uHb2x3h4OH1pcJh0znfHSWHc=; b=GAg2l8HwSPliKoXGz0pAe/+4+Jj9riX3hkE5CxG4flfKdzP0Otakk40cAAZ2+scLMH 9TmmFYCGJhMYjnkwfMzcMm6Wnm75tKCFMHpq+uj2eg7eroXUG6L6jxRIXCbyb8/MyooG FdCqHF0M2J3Bw98iarAQViyp2lZtYNubHsSQk= 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=heqoSUJ24c31zI2zPR+uHb2x3h4OH1pcJh0znfHSWHc=; b=jGXRRyWsIomHf+XKVs4vKhyB7sn/RNNcU1KKWvBfzUXjAQwdxSfqUDPB3WGZn0QjII WJ1nrmMtMLcj+mzKw6uia7xZrwchZUtxzn8z9c1znzTFzUNi+5P7/XnbyiKGS++Gj+5W aP+anTJMLyufDCwG3HUyMY+Pg4+JWBl4C80ZAa2UWnEcj5/F2fFmaob+NNRQeobGVRz3 +6IvevukdyUIkIqbpaU38jhOdxcDCxPJi6P8h60j2NwdJ/7a6xam0n8/yGeWdmiOlG67 RtgxMS8OLp76pN3izTHFY2gIeliysArpDJbkQ0tZtmL6vbzqLK2IeRt7uXimT8qNhkhD G5gA== X-Gm-Message-State: AGi0PuakFFpcwOD8cNrzim8wlRrBSpHBbdEkLOxw+MlBDOEoz1aElcbP d5adrpSJTzMfBWvEJI6HvokvVAOBiYc= X-Google-Smtp-Source: APiQypLPqWMzBuoaraNXUBvsO88srGwjdFf00nBoBF1qRNxJglT7h47nmrEIuC1OuNMAgyDGrBRc2w== X-Received: by 2002:a17:90a:354e:: with SMTP id q72mr23769631pjb.174.1586155011829; Sun, 05 Apr 2020 23:36:51 -0700 (PDT) Received: from rfd.leadboat.com (108-233-125-46.lightspeed.sntcca.sbcglobal.net. [108.233.125.46]) by smtp.gmail.com with ESMTPSA id f45sm11142378pjg.29.2020.04.05.23.36.50 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 05 Apr 2020 23:36:51 -0700 (PDT) Date: Sun, 5 Apr 2020 23:36:49 -0700 From: Noah Misch To: pgsql-hackers@postgresql.org Subject: 001_rep_changes.pl stalls Message-ID: <20200406063649.GA3738151@rfd.leadboat.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="cNdxnHkX5QqsyA0e" 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 --cNdxnHkX5QqsyA0e Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Executive summary: the "MyWalSnd->write < sentPtr" in WalSndWaitForWal() is important for promptly updating pg_stat_replication. When caught up, we should impose that logic before every sleep. The one-line fix is to sleep in WalSndLoop() only when pq_is_send_pending(), not when caught up. On my regular development machine, src/test/subscription/t/001_rep_changes.pl stalls for ~10s at this wait_for_catchup: $node_publisher->safe_psql('postgres', "DELETE FROM tab_rep"); # Restart the publisher and check the state of the subscriber which # should be in a streaming state after catching up. $node_publisher->stop('fast'); $node_publisher->start; $node_publisher->wait_for_catchup('tap_sub'); That snippet emits three notable physical WAL records. There's a Transaction/COMMIT at the end of the DELETE, an XLOG/CHECKPOINT_SHUTDOWN, and an XLOG/FPI_FOR_HINT. The buildfarm has stalled there, but it happens probably less than half the time. Examples[1] showing the stall: https://buildfarm.postgresql.org/cgi-bin/show_stage_log.pl?nm=mandrill&dt=2020-03-20%2017%3A09%3A53&stg=subscription-check https://buildfarm.postgresql.org/cgi-bin/show_stage_log.pl?nm=thorntail&dt=2020-03-22%2019%3A51%3A38&stg=subscription-check https://buildfarm.postgresql.org/cgi-bin/show_stage_log.pl?nm=hoverfly&dt=2020-03-19%2003%3A35%3A01&stg=subscription-check https://buildfarm.postgresql.org/cgi-bin/show_stage_log.pl?nm=hoverfly&dt=2020-03-20%2015%3A15%3A01&stg=subscription-check Here's the most-relevant walsender call tree: WalSndLoop XLogSendLogical (caller invokes once per loop iteration, via send_data callback) XLogReadRecord (caller invokes once) ReadPageInternal (caller invokes twice in this test; more calls are possible) logical_read_xlog_page (caller skips when page is same as last call, else invokes 1-2 times via state->read_page() callback, registered in StartLogicalReplication) WalSndWaitForWal (caller invokes once; has fast path) The cause is a race involving the flow of reply messages (send_feedback() messages) from logical apply worker to walsender. Here are two sequencing patterns; the more-indented parts are what differ. Stalling pattern: sender reads Transaction/COMMIT and sends the changes receiver applies the changes receiver send_feedback() reports progress up to Transaction/COMMIT sender accepts the report sender reads XLOG/CHECKPOINT_SHUTDOWN and/or XLOG/FPI_FOR_HINT, which are no-ops for logical rep sender WalSndCaughtUp becomes true; sender sleeps in WalSndLoop() receiver wal_receiver_status_interval elapses; receiver reports progress up to Transaction/COMMIT sender wakes up, accepts the report sender calls WalSndWaitForWal(), which sends a keepalive due to "MyWalSnd->write < sentPtr" receiver gets keepalive, send_feedback() reports progress up to XLOG/FPI_FOR_HINT Non-stalling pattern (more prevalent with lower machine performance): sender reads Transaction/COMMIT and sends the changes sender reads XLOG/CHECKPOINT_SHUTDOWN and/or XLOG/FPI_FOR_HINT, which are no-ops for logical rep sender WalSndCaughtUp becomes true; sender sleeps in WalSndLoop() receiver applies the changes receiver send_feedback() reports progress up to Transaction/COMMIT sender wakes up, accepts the report sender calls WalSndWaitForWal(), which sends a keepalive due to "MyWalSnd->write < sentPtr" receiver gets keepalive, send_feedback() reports progress up to XLOG/FPI_FOR_HINT The fix is to test "MyWalSnd->write < sentPtr" before more sleeps. The test is unnecessary when sleeping due to pq_is_send_pending(); in that case, the receiver is not idle and will reply before idling. I changed WalSndLoop() to sleep only for pq_is_send_pending(). For all other sleep reasons, the sleep will happen in WalSndWaitForWal(). Attached. I don't know whether this is important outside of testing scenarios. I lean against back-patching, but I will back-patch if someone thinks this qualifies as a performance bug. Thanks, nm [1] I spot-checked only my animals, since I wanted to experiment on an affected animal. --cNdxnHkX5QqsyA0e Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename="WalSndLoop-stall-v1.patch" Author: Noah Misch Commit: Noah Misch When WalSndCaughtUp, sleep only in WalSndWaitForWal(). Before sleeping, WalSndWaitForWal() sends a keepalive if MyWalSnd->write < sentPtr. That is important in logical replication. When the latest physical LSN yields no logical replication messages (a common case), that keepalive elicits a reply, and processing the reply updates pg_stat_replication.replay_lsn. WalSndLoop() lacks that; when WalSndLoop() slept, replay_lsn advancement could stall until wal_receiver_status_interval elapsed. This sometimes stalled src/test/subscription/t/001_rep_changes.pl for up to 10s. Reviewed by FIXME. Discussion: https://postgr.es/m/FIXME diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c index 9e56115..d9c6359 100644 --- a/src/backend/replication/walsender.c +++ b/src/backend/replication/walsender.c @@ -1428,8 +1428,10 @@ WalSndWaitForWal(XLogRecPtr loc) /* * We only send regular messages to the client for full decoded * transactions, but a synchronous replication and walsender shutdown - * possibly are waiting for a later location. So we send pings - * containing the flush location every now and then. + * possibly are waiting for a later location. So, before sleeping, we + * send a ping containing the flush location. If the receiver is + * otherwise idle, this keepalive will trigger a reply. Processing the + * reply will update these MyWalSnd locations. */ if (MyWalSnd->flush < sentPtr && MyWalSnd->write < sentPtr && @@ -2314,20 +2316,16 @@ WalSndLoop(WalSndSendDataCallback send_data) WalSndKeepaliveIfNecessary(); /* - * We don't block if not caught up, unless there is unsent data - * pending in which case we'd better block until the socket is - * write-ready. This test is only needed for the case where the - * send_data callback handled a subset of the available data but then - * pq_flush_if_writable flushed it all --- we should immediately try - * to send more. + * Block if we have unsent data. Let WalSndWaitForWal() handle any + * other blocking; idle receivers need its additional actions. */ - if ((WalSndCaughtUp && !streamingDoneSending) || pq_is_send_pending()) + if (pq_is_send_pending()) { long sleeptime; int wakeEvents; wakeEvents = WL_LATCH_SET | WL_EXIT_ON_PM_DEATH | WL_TIMEOUT | - WL_SOCKET_READABLE; + WL_SOCKET_READABLE | WL_SOCKET_WRITEABLE; /* * Use fresh timestamp, not last_processing, to reduce the chance @@ -2335,9 +2333,6 @@ WalSndLoop(WalSndSendDataCallback send_data) */ sleeptime = WalSndComputeSleeptime(GetCurrentTimestamp()); - if (pq_is_send_pending()) - wakeEvents |= WL_SOCKET_WRITEABLE; - /* Sleep until something happens or we time out */ (void) WaitLatchOrSocket(MyLatch, wakeEvents, MyProcPort->sock, sleeptime, --cNdxnHkX5QqsyA0e--