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 1wd3kS-003rBO-0O for pgpool-hackers@arkaria.postgresql.org; Fri, 26 Jun 2026 10:25:24 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wd3kR-00AOSp-0E for pgpool-hackers@arkaria.postgresql.org; Fri, 26 Jun 2026 10:25:23 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wd3kQ-00AOSh-2N for pgpool-hackers@lists.postgresql.org; Fri, 26 Jun 2026 10:25:22 +0000 Received: from mail-yw1-x112c.google.com ([2607:f8b0:4864:20::112c]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wd3kM-00000000K1y-3SBE for pgpool-hackers@lists.postgresql.org; Fri, 26 Jun 2026 10:25:21 +0000 Received: by mail-yw1-x112c.google.com with SMTP id 00721157ae682-7dfe7712572so6729287b3.1 for ; Fri, 26 Jun 2026 03:25:18 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1782469518; cv=none; d=google.com; s=arc-20260327; b=MN98FQqBSKUYStXcvb5UmVlbWrK2vgtLkhuzlJk/YfWWR30jKU/FFyQC+UiTmpeQml oEa99ywmw1VxU7TPP7hZ60kGzNOt9zrNV/0Svn35LJLhCo2jQOnmBn+rRPgLxWqJJLgM HeMOfPnfunIlSaTncyDJ/Xl2CRCQZXxCx1wORU4HRg5FVX6hv9ExG5GTt69mVfpKko/u lPClOwRLcL/w4AUx1U1L/so5RVaDQeKl4lR09AZo7dGuAHqug1uo6+KPb8r3RC/cYrsz YtKtGHZsH6mM4PIgnw7nrYbVHg9MlKUnL7hsru9WU2yLc5nEgiHHTJH4WN6LQmBSFARz O4/Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=3mkzBUL1YLaj/NR+6n+DxOp7yXVELSsgVQiOvyyTUCk=; fh=m3oiE62fRN5J1XaE+5vpHZRIgCyFefxVI0mFw3d8yxA=; b=nlndJIeXXXmCSrrkkB/nlPyuZO77fa+ByrraTsUKTiuSYxDsUswJ8XuLq8Y5FrNThk 7+EX+n7qAxBIVdSnCUIQ7I1l5u3yrRPW5irEOfxbNqrZHdY2XZBH53C3wrLYhniMmi6r hTBxSFD6GzAUzW4EqMrVbGCtU+GzMlKRi+LZ6VnT9Efb1IXP9/gGqEfRihm3CHkoR1H/ Qqak1Qu+eDzMluu58tZpf1QWux9nu0B4+hmg3vUsW38LfOttvuCEK1ENMkYZ/MQ5zUWq bIsrqQSfrqAMuCxoxKBi3x7/NGAkI9GaWKPvfbjL6127hHTtkg+EinYXdexs2zZqIaAX 3GvQ==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=tailorbrands.com; s=google; t=1782469518; x=1783074318; darn=lists.postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=3mkzBUL1YLaj/NR+6n+DxOp7yXVELSsgVQiOvyyTUCk=; b=cv/rKVfy+n+9siRMtSRfmJN5pFyTR2fnYPlcF28+NaqatL0Dq30kxjT5bcdpV4q7S3 0tM+EC9h3WNFwGHLCnGvYSQuWHvjETMFa4Td1CYoopxSPqPJOAbrJQxur1HUxM15bGe9 GImQ1DL8AnoeeqjDr+AxNrDsX9tUoMlhHHAMhWZ4M9ee94IqsRhJltoc1rREdrmNty5G RlL7YOhDFRjWRzYesOG/fr7Th9DsaT4IZOAiM0iWwdURxtKX5LTa9mm0wUdlzh9xdagB dR1rWRTBYb5EmRFxN+Mu0rfubdNYk19yQ87SYUmiBDSJ1ptMLH9fwNZNGxJn5x7h2I9C inQw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782469518; x=1783074318; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=3mkzBUL1YLaj/NR+6n+DxOp7yXVELSsgVQiOvyyTUCk=; b=EFL6jhDjZv3dirCj+Fk6ZUaUxg8XAZcyYW77T32SL0ZOd66SmAxPz4G7D527vGVHaS R4JUNYjv4EajgdDk1IYcyPvIoPVfzrG7Axj5GwgVJsWqEpiX+xSVSURGqU7tzNrzWAg5 S5PFD+LZvLtmKb42P6nmfZUMeqAi2qF7A6RSaG+pcVQ5rRV9oFBP3IovEx/x78EHX6Tp xMxuCdW2TPrMb5jiO6H+ONIzWaAx9Ym0AWeBZACv/pM1+oEBrtI2ZTcclte3KppCjlua D/oys833TILBuXjxN8Odg4+ph//kxCToixr8Z1uhaaYXhMJnoALhDAlDExNjEETBZd0U vjLQ== X-Gm-Message-State: AOJu0YwSaH5GM843xtHitrParwfCLBifw2UTyQOVGLcdjqvE4jPDAF0/ uij+myiTQmSi8stKj+1A3MsDcr/6+LHtPWH7ErgDQar3o2FnnIDtv3oRobyxxBxJUAYJgx0izNA j/o3i2B+gQsP3Ezdp8nkwKgQwqWVlwYP1xQURY0L98oigKSgNovcSEhc2TZIx X-Gm-Gg: AfdE7ckWrdw+ZVvnfHhvYn5IME4YCZOb/Y795iLKnqgv8eW208wX6+Hhsh2mFKVK7FE 9hXA8JBWNVRaj3aBpFViw32k1VwY1lw3bcUr8YCJfwH6iKNV0lU2Rfp3xqd3tsdW6k+t4uYTnOG l3AJI0qOu4p80d1An9eZSn/WsYXT3ZmAuvfIAJoIObDhfn7uslE9DLg/E4KRo49HW//tQiOx0pN K5PTktFkc6HdUED32/erBgUW92jyQlHJaRKekdLoxGu8VQIBUnOM6uR8hLJdPBMfXiWmeqNUwu8 T/2WgTQhGoUd6b2tKzMnWC0XA6EGprHxCPX38x5rwIebFwDVNdjFvlm3qnTtj6OA09qsyGRwFhX xqhFAP0PHgOBplMzsN1WzdTVWfdREJx7qMoTbhjJr/vyaf0jkhEzDOfcVqeDpVJbstj5zKeeU X-Received: by 2002:a05:690c:67c5:b0:809:6b62:2f7a with SMTP id 00721157ae682-80a688e7d3fmr61950507b3.21.1782469517761; Fri, 26 Jun 2026 03:25:17 -0700 (PDT) MIME-Version: 1.0 References: <20260619.111037.159094338431818955.ishii@postgresql.org> <20260626.074028.806271918741516663.ishii@postgresql.org> In-Reply-To: <20260626.074028.806271918741516663.ishii@postgresql.org> From: Nadav Shatz Date: Fri, 26 Jun 2026 13:25:05 +0300 X-Gm-Features: AVVi8CfgYmx-f1993_si4GTm366x7o6lZES0vAL9bM4TwnvnAZxKzZ-y__pi74g Message-ID: Subject: Re: Proposal: Recent mutated table tracking in memory To: Tatsuo Ishii Cc: pgpool-hackers@lists.postgresql.org Content-Type: multipart/alternative; boundary="0000000000006e087d0655258657" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --0000000000006e087d0655258657 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Hi Tatsuo, Sorry -- my wording was misleading. No, unpatched pgpool does NOT have this problem, and you're right that your test shows no hang on master. The hang is entirely in the new code my patch adds, not in pgpool itself. To be precise: the marking code that hung is the dml_adaptive_global branch in handle_query_context() (CommandComplete.c) that my patch introduces. It is gated on disable_load_balance_on_write =3D dml_adaptive_global, which only exists with the patch applied, so master never runs it. It also doesn't fire for your exact sequence, for two reasons: 1. You ran it on master, which doesn't contain the branch at all. 2. Even on a patched build, your statements are inside an explicit transaction (BEGIN ... COMMIT), and that branch only runs for autocommit statements (the "!is_in_transaction" case). What actually hung, on a patched build with dml_adaptive_global set in streaming replication, was a *bare autocommit* statement (no surrounding transaction) whose table OID cannot be served from the relcache -- for example: DROP TABLE IF EXISTS non_existent_table; -- no BEGIN/COMMIT Because the name is unresolvable, pool_table_name_to_oid() does not cache it (no_cache_if_zero), so every lookup is a relcache miss. The marking branch calls pool_extract_table_oids() -> ... -> do_query() to resolve the OID, but handle_query_context() runs before the statement's CommandComplete response has been read from the backend, so injecting that query desyncs the protocol and the session blocks. Backtrace: #2 pool_read2 (blocked in read()) #3 do_query "SELECT COALESCE(pg_catalog.to_regclass('...')::oid, 0)" #4 pool_search_relcache (relcache miss) #5 pool_table_name_to_oid #6 pool_extract_table_oids query_cache/pool_memqcache.c #7 handle_query_context protocol/CommandComplete.c (!is_in_transaction branch) #8 CommandComplete This is the same class of issue as the one behind your original review comment: resolving OIDs at CommandComplete time means doing backend I/O while a response is in flight. v6-0001 fixes it for good by resolving the written tables' OIDs (and the database OID) at DML *routing* time -- when the connection is idle and do_query is safe, the same point the feature already resolves OIDs for the SELECT staleness check. handle_query_context() then only marks the already-resolved OIDs and does no backend I/O at all, for both the autocommit and the COMMIT paths. Verified on a PG16 streaming-replication cluster with dml_adaptive_global: the bare "DROP TABLE IF EXISTS non_existent_table;" and your "BEGIN; DROP TABLE IF EXISTS ...; COMMIT;" both return promptly, the deferred-constraint COMMIT failure leaves the table unmarked (reads keep load-balancing to the standby), and a successful autocommit or COMMIT write marks the table and forces reads to the primary. Sorry again for the confusing earlier phrasing. On Fri, Jun 26, 2026 at 1:40=E2=80=AFAM Tatsuo Ishii = wrote: > Hi Nadav, > > > Hi Tatsuo, > > > > Good catch -- that was a bug, and chasing it down turned up a second > > one in the same area. > > > > The original problem you described: the flush ran in dml_adaptive() > > while pgpool was *routing* the COMMIT, before the COMMIT was sent to > > the backend. So a COMMIT that later failed (your deferred-constraint > > example) had already marked the tables, even though the writes rolled > > back. > > > > My first instinct was to just move the flush to handle_query_context(), > > where reaching CommandComplete for the COMMIT means it succeeded. But > > that path resolves table/database OIDs via pool_table_name_to_oid() / > > the relcache, which on a cache miss issues a do_query() -- and at that > > point the COMMIT response is still being read from the backend, so > > injecting a query desyncs the protocol and hangs the session. Testing > > caught this immediately. > > > > It turned out the existing autocommit marking branch had the very same > > hazard: pool_extract_table_oids() in handle_query_context() also does a > > relcache do_query() on a miss, so e.g. "DROP TABLE IF EXISTS > " > > (an uncacheable name -> guaranteed miss) reliably hung the session too. > > Are you saying that the non patched pgpool has a problem? > I tried following queries with master branch and found no hang. > > BEGIN; > DROP TABLE IF EXISTS non_existent_table; > COMMIT; > > Regards, > -- > Tatsuo Ishii > SRA OSS K.K. > English: http://www.sraoss.co.jp/index_en/ > Japanese:http://www.sraoss.co.jp > --=20 Nadav Shatz Tailor Brands | CTO --0000000000006e087d0655258657 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hi Tatsuo,

Sorry -- my wording was misleading. No, = unpatched pgpool does NOT have
this problem, and you're right that y= our test shows no hang on master.
The hang is entirely in the new code m= y patch adds, not in pgpool
itself.

To be precise: the marking co= de that hung is the dml_adaptive_global
branch in handle_query_context()= (CommandComplete.c) that my patch
introduces. It is gated on disable_lo= ad_balance_on_write =3D
dml_adaptive_global, which only exists with the = patch applied, so master
never runs it.

It also doesn't fire = for your exact sequence, for two reasons:

=C2=A0 1. You ran it on ma= ster, which doesn't contain the branch at all.
=C2=A0 2. Even on a p= atched build, your statements are inside an explicit
=C2=A0 =C2=A0 =C2= =A0transaction (BEGIN ... COMMIT), and that branch only runs for
=C2=A0 = =C2=A0 =C2=A0autocommit statements (the "!is_in_transaction" case= ).

What actually hung, on a patched build with dml_adaptive_global s= et in
streaming replication, was a *bare autocommit* statement (no surro= unding
transaction) whose table OID cannot be served from the relcache -= - for
example:

=C2=A0 DROP TABLE IF EXISTS non_existent_table; = =C2=A0 =C2=A0 -- no BEGIN/COMMIT

Because the name is unresolvable, p= ool_table_name_to_oid() does not cache
it (no_cache_if_zero), so every l= ookup is a relcache miss. The marking
branch calls pool_extract_table_oi= ds() -> ... -> do_query() to resolve
the OID, but handle_query_con= text() runs before the statement's
CommandComplete response has been= read from the backend, so injecting
that query desyncs the protocol and= the session blocks. Backtrace:

=C2=A0 #2 =C2=A0pool_read2 =C2=A0 = =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0(blocked in read())
=C2= =A0 #3 =C2=A0do_query =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0"SELECT COALESCE(pg_catalog.to_regclass('...')::oid,= 0)"
=C2=A0 #4 =C2=A0pool_search_relcache =C2=A0 =C2=A0 =C2=A0(relc= ache miss)
=C2=A0 #5 =C2=A0pool_table_name_to_oid
=C2=A0 #6 =C2=A0poo= l_extract_table_oids =C2=A0 query_cache/pool_memqcache.c
=C2=A0 #7 =C2= =A0handle_query_context =C2=A0 =C2=A0 =C2=A0protocol/CommandComplete.c =C2= =A0(!is_in_transaction branch)
=C2=A0 #8 =C2=A0CommandComplete

Th= is is the same class of issue as the one behind your original review
com= ment: resolving OIDs at CommandComplete time means doing backend I/O
whi= le a response is in flight.

v6-0001 fixes it for good by resolving t= he written tables' OIDs (and the
database OID) at DML *routing* time= -- when the connection is idle and
do_query is safe, the same point the= feature already resolves OIDs for
the SELECT staleness check. handle_qu= ery_context() then only marks the
already-resolved OIDs and does no back= end I/O at all, for both the
autocommit and the COMMIT paths.

Ver= ified on a PG16 streaming-replication cluster with dml_adaptive_global:
= the bare "DROP TABLE IF EXISTS non_existent_table;" and your
&= quot;BEGIN; DROP TABLE IF EXISTS ...; COMMIT;" both return promptly, t= he
deferred-constraint COMMIT failure leaves the table unmarked (reads k= eep
load-balancing to the standby), and a successful autocommit or COMMI= T
write marks the table and forces reads to the primary.

Sorry ag= ain for the confusing earlier phrasing.

On Fri, Ju= n 26, 2026 at 1:40=E2=80=AFAM Tatsuo Ishii <ishii@postgresql.org> wrote:
Hi Nadav,

> Hi Tatsuo,
>
> Good catch -- that was a bug, and chasing it down turned up a second > one in the same area.
>
> The original problem you described: the flush ran in dml_adaptive() > while pgpool was *routing* the COMMIT, before the COMMIT was sent to > the backend.=C2=A0 So a COMMIT that later failed (your deferred-constr= aint
> example) had already marked the tables, even though the writes rolled<= br> > back.
>
> My first instinct was to just move the flush to handle_query_context()= ,
> where reaching CommandComplete for the COMMIT means it succeeded.=C2= =A0 But
> that path resolves table/database OIDs via pool_table_name_to_oid() /<= br> > the relcache, which on a cache miss issues a do_query() -- and at that=
> point the COMMIT response is still being read from the backend, so
> injecting a query desyncs the protocol and hangs the session.=C2=A0 Te= sting
> caught this immediately.
>
> It turned out the existing autocommit marking branch had the very same=
> hazard: pool_extract_table_oids() in handle_query_context() also does = a
> relcache do_query() on a miss, so e.g. "DROP TABLE IF EXISTS <= nonexistent>"
> (an uncacheable name -> guaranteed miss) reliably hung the session = too.

Are you saying that the non patched pgpool has a problem?
I tried following queries with master branch and found no hang.

BEGIN;
DROP TABLE IF EXISTS non_existent_table;
COMMIT;

Regards,
--
Tatsuo Ishii
SRA OSS K.K.
English: http://www.sraoss.co.jp/index_en/
Japanese:http://www.sraoss.co.jp


--
Nadav Shatz
<= font color=3D"#000000">Tailor Brands=C2=A0| CTO
--0000000000006e087d0655258657--