Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vZ38b-002Eym-1F for pgsql-hackers@arkaria.postgresql.org; Fri, 26 Dec 2025 08:25:30 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1vZ38a-008u6l-0J for pgsql-hackers@arkaria.postgresql.org; Fri, 26 Dec 2025 08:25:28 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1vZ38Z-008u6d-2S for pgsql-hackers@lists.postgresql.org; Fri, 26 Dec 2025 08:25:28 +0000 Received: from mail-pf1-x432.google.com ([2607:f8b0:4864:20::432]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.96) (envelope-from ) id 1vZ38X-002tsT-1s for pgsql-hackers@lists.postgresql.org; Fri, 26 Dec 2025 08:25:28 +0000 Received: by mail-pf1-x432.google.com with SMTP id d2e1a72fcca58-8035e31d834so4446229b3a.2 for ; Fri, 26 Dec 2025 00:25:25 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1766737522; x=1767342322; darn=lists.postgresql.org; h=to:references:message-id:content-transfer-encoding:cc:date :in-reply-to:from:subject:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=9ktTcr3djwCOZSeOLnyoCAO2lfx7Hij+FU9Hsl2/x8c=; b=Hh8TfQNIGDEkTrl3JXimPoIt3VkKDtXDpXtfMH6HYEjVVzY98lO56SUR/nZDTaSTjI rmhyJ+Z9J9ljNrK4tqRy4fDMP1UmNBRB684mZqLswYcPnfHegPoCgrHW7GrkSlCrFoQO EzIf2WNDL7jV/b9mTB1nNI7FZ7CNz5fpaSOr0U7W05pV0mh02vNWvhq+JSXlbZ+pZinL MawNTNZL33Kh6V4YhM4UOfH0xpyYio93a80eR2s2fzeh4Fvwj3RFlD4+AneBw4Crtw2J Bj5s2GAZ3xSl+7NLUD6Jw4Zsq/kMXRpLL8p4/CpHCSrvveDQvfjMGIqoPdMG3EWjyedo nKRw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1766737522; x=1767342322; h=to:references:message-id:content-transfer-encoding:cc:date :in-reply-to:from:subject:mime-version:x-gm-gg:x-gm-message-state :from:to:cc:subject:date:message-id:reply-to; bh=9ktTcr3djwCOZSeOLnyoCAO2lfx7Hij+FU9Hsl2/x8c=; b=mnkKuSSourF/QvDOW99B3cYL5uA1NE1f9UkgzF0BEMK7pVTniePf9iFHEO/HORG4O9 sbOkSm7ympFwipPahJVs7hulMYp79wNZTZjSWAnKzpIXihT2p7GA0IsQ6Dbg2eE8uQKy U+/xo25aR/rQ4ieuLQglaVe0Et2YUGl2QFAQwsSnTzd6sXhY1rFnak84VDUZcesszgUb u121ZrPArTnld/x0u+wiZlQQlamCWcXAQj+T/qeWWgX+eBjpWIpRNmznhbGZiPP8Jxa8 blcHKCIpwBoqkGZEpxmWAbK62L0KNRQ2xpDpXs4q6D3glPoCUttqODjmydYiIJlwNP11 8jaQ== X-Forwarded-Encrypted: i=1; AJvYcCWcRXaL7mIehi1eS6hoYItbSRpJ+K27h+flluxtPjaUr8DfS21xKV7wQkYrkI2+uvgWKwRlWNt2rB70j3/Z@lists.postgresql.org X-Gm-Message-State: AOJu0YwvHeOnsf5LK4EK/pm4t+/9cWANFQGriBVUrruzeFi9J6P1WKac rSItmn5hpAiiVICsRkUNh5d8VyO3PMtTv08Pn8Qj7nYGNunUK9WhulIB X-Gm-Gg: AY/fxX6lA5cTT7+uwPChoUQpqBEKeBmxFqirv1Ow7llndVckwG6OY0ICfsZG5DRWD5K hnj1H+tHNlAjm6+WIO70ojZRvN4RzeCtBggDH0c/0V70Gynd30JzUU4QqAcUJa3JpQQ4/EfTlKf sVt9eXilGTrT4MkyhbVgZWbk6tYONXSHxH7j7nC7nyxewugtKIIr4zJsLxIyPF7OBKKMNT/vu55 O31bRYD2nyKsMJZjwDtEl2pdsIRb4bXIeXEvHK75kEQhz9qXwMPL/jkySHs3sHINe/hTb1H9TZT y6o1u9Mk1lBxPv9qMfiVen9q0/oSDeY97VVxcHaY7OOQ0bWCB84gTXdBLpsEXDJSg2XKyAgyP8t rWQ+ScN7uVHKMVni8YX/nhsNaui4ZqIsJ7nSAL4dJCbz5un6KX/eCyMHZ+sDEBJICsBUY4EHO79 WJ2AkIhZsj1BbTBxcRMpsBlTaEmDdvuQ== X-Google-Smtp-Source: AGHT+IFP2lndcUNHlP+yWIAPXObtMWwzY5PdzEqN9ILPo9uHXI8RmHhnGQV9/QAXPgfZNYYWFkTD3A== X-Received: by 2002:a05:6a00:4104:b0:7e8:43f5:bd2f with SMTP id d2e1a72fcca58-7ff6795c5d1mr20380168b3a.68.1766737522079; Fri, 26 Dec 2025 00:25:22 -0800 (PST) Received: from smtpclient.apple ([45.32.121.103]) by smtp.gmail.com with ESMTPSA id d2e1a72fcca58-7ff7e88c8f0sm21670900b3a.62.2025.12.26.00.25.17 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Fri, 26 Dec 2025 00:25:21 -0800 (PST) Content-Type: text/plain; charset=utf-8 Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3826.700.81\)) Subject: Re: Implement waiting for wal lsn replay: reloaded From: Chao Li In-Reply-To: Date: Fri, 26 Dec 2025 16:24:39 +0800 Cc: Alexander Korotkov , pgsql-hackers , Andres Freund , =?utf-8?Q?=C3=81lvaro_Herrera?= , Michael Paquier , jian he , Tomas Vondra , Yura Sokolov Content-Transfer-Encoding: quoted-printable Message-Id: References: <202511031242.5avrewkjqxyp@alvherre.pgsql> To: Xuneng Zhou X-Mailer: Apple Mail (2.3826.700.81) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk > On Dec 19, 2025, at 10:49, Xuneng Zhou wrote: >=20 > Hi, >=20 > On Thu, Dec 18, 2025 at 8:25=E2=80=AFPM Alexander Korotkov = wrote: >>=20 >> On Thu, Dec 18, 2025 at 2:24=E2=80=AFPM Xuneng Zhou = wrote: >>> On Thu, Dec 18, 2025 at 6:38=E2=80=AFPM Alexander Korotkov = wrote: >>>>=20 >>>> Hi, Xuneng! >>>>=20 >>>> On Tue, Dec 16, 2025 at 6:46=E2=80=AFAM Xuneng Zhou = wrote: >>>>> Remove the erroneous WAIT_LSN_TYPE_COUNT case from the switch >>>>> statement in v5 patch 1. >>>>=20 >>>> Thank you for your work on this patchset. Generally, it looks like >>>> good and quite straightforward extension of the current = functionality. >>>> But this patch adds 4 new unreserved keywords to our grammar. Do = you >>>> think we can put mode into with options clause? >>>>=20 >>>=20 >>> Thanks for pointing this out. Yeah, 4 unreserved keywords add >>> complexity to the parser and it may not be worthwhile since replay = is >>> expected to be the common use scenario. Maybe we can do something = like >>> this: >>>=20 >>> -- Default (REPLAY mode) >>> WAIT FOR LSN '0/306EE20' WITH (TIMEOUT '1s'); >>>=20 >>> -- Explicit REPLAY mode >>> WAIT FOR LSN '0/306EE20' WITH (MODE 'replay', TIMEOUT '1s'); >>>=20 >>> -- WRITE mode >>> WAIT FOR LSN '0/306EE20' WITH (MODE 'write', TIMEOUT '1s'); >>>=20 >>> If no mode is set explicitly in the options clause, it defaults to >>> replay. I'll update the patch per your suggestion. >>=20 >> This is exactly what I meant. Please, go ahead. >>=20 >=20 > Here is the updated patch set (v7). Please check. >=20 > --=20 > Best, > Xuneng > = Hi Xuneng, A solid patch! Just a few small comments: 1 - 0001 ``` +XLogRecPtr +GetCurrentLSNForWaitType(WaitLSNType lsnType) +{ + switch (lsnType) + { + case WAIT_LSN_TYPE_STANDBY_REPLAY: + return GetXLogReplayRecPtr(NULL); + + case WAIT_LSN_TYPE_STANDBY_WRITE: + return GetWalRcvWriteRecPtr(); + + case WAIT_LSN_TYPE_STANDBY_FLUSH: + return GetWalRcvFlushRecPtr(NULL, NULL); + + case WAIT_LSN_TYPE_PRIMARY_FLUSH: + return GetFlushRecPtr(NULL); + } + + elog(ERROR, "invalid LSN wait type: %d", lsnType); + pg_unreachable(); +} ``` As you add pg_unreachable() in the new function = GetCurrentLSNForWaitType(), I=E2=80=99m thinking if we should just do an = Assert(), I saw every existing related function has done such an assert, = for example addLSNWaiter(), it does =E2=80=9CAssert(i >=3D 0 && i < = WAIT_LSN_TYPE_COUNT);=E2=80=9D. I guess we can just following the = current mechanism to verify lsnType. So, for GetCurrentLSNForWaitType(), = we can just add a default clause and Assert(false). 2 - 0002 ``` + else + ereport(ERROR, + = (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("unrecognized = value for WAIT option \"%s\": \"%s\"", + "MODE", = mode_str), ``` I wonder why don=E2=80=99t we directly put MODE into the error message? 3 - 0002 ``` case WAIT_LSN_RESULT_NOT_IN_RECOVERY: if (throw) { + const WaitLSNTypeDesc *desc =3D = &WaitLSNTypeDescs[lsnType]; + XLogRecPtr currentLSN =3D = GetCurrentLSNForWaitType(lsnType); + if (PromoteIsTriggered()) { ereport(ERROR, = errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery = is not in progress"), - = errdetail("Recovery ended before replaying target LSN %X/%08X; last = replay LSN %X/%08X.", + = errdetail("Recovery ended before target LSN %X/%08X was %s; last %s LSN = %X/%08X.", = LSN_FORMAT_ARGS(lsn), - = LSN_FORMAT_ARGS(GetXLogReplayRecPtr(NULL)))); + = desc->verb, + = desc->noun, + = LSN_FORMAT_ARGS(currentLSN))); } else ereport(ERROR, = errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery = is not in progress"), - errhint("Waiting = for the replay LSN can only be executed during recovery.")); + errhint("Waiting = for the %s LSN can only be executed during recovery.", + = desc->noun)); } ``` currentLSN is only used in the if clause, thus it can be defined inside = the if clause. 3 - 0002 ``` + /* + * If we wrote an LSN that someone was waiting for then walk = over the + * shared memory array and set latches to notify the waiters. + */ + if (waitLSNState && + (LogstreamResult.Write >=3D + = pg_atomic_read_u64(&waitLSNState->minWaitedLSN[WAIT_LSN_TYPE_STANDBY_WRITE= ]))) + WaitLSNWakeup(WAIT_LSN_TYPE_STANDBY_WRITE, = LogstreamResult.Write); ``` Do we need to mention "walk over the shared memory array and set = latches=E2=80=9D in the comment? The logic belongs to WaitLSNWakeup(). = What about if the wake up logic changes in future, then this comment = would become stale. So I think we only need to mention =E2=80=9Cnotify = the waiters=E2=80=9D. 4 - 0003 ``` + /* + * Handle parenthesized option list. This fires when we're in = an + * unfinished parenthesized option list. get_previous_words = treats a + * completed parenthesized option list as one word, so the above = test is + * correct. mode takes a string value ('replay', 'write', = 'flush'), + * timeout takes a string value, no_throw takes no value. + */ else if (HeadMatches("WAIT", "FOR", "LSN", MatchAny, "WITH", = "(*") && !HeadMatches("WAIT", "FOR", "LSN", MatchAny, = "WITH", "(*)")) { - /* - * This fires if we're in an unfinished parenthesized = option list. - * get_previous_words treats a completed parenthesized = option list as - * one word, so the above test is correct. - */ if (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')) - COMPLETE_WITH("timeout", "no_throw"); - - /* - * timeout takes a string value, no_throw takes no = value. We don't - * offer completions for these values. - */ ``` The new comment has lost the meaning of =E2=80=9CWe don=E2=80=99t offer = completions for these values (timeout and no_throw)=E2=80=9D, to be = explicit, I feel we can retain the sentence. 5 - 0004 ``` + my $isrecovery =3D + $self->safe_psql('postgres', "SELECT pg_is_in_recovery()"); + chomp($isrecovery); croak "unknown mode $mode for 'wait_for_catchup', valid modes = are " . join(', ', keys(%valid_modes)) unless exists($valid_modes{$mode}); @@ -3347,9 +3350,6 @@ sub wait_for_catchup } if (!defined($target_lsn)) { - my $isrecovery =3D - $self->safe_psql('postgres', "SELECT = pg_is_in_recovery()"); - chomp($isrecovery); ``` I wonder why pull up pg_is_in_recovery to an early place and = unconditionally call it? Best regards, -- Chao Li (Evan) HighGo Software Co., Ltd. https://www.highgo.com/