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 1pP4Wl-000324-BA for pgsql-hackers@arkaria.postgresql.org; Mon, 06 Feb 2023 16:39:35 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1pP4Wj-0003DY-B0 for pgsql-hackers@arkaria.postgresql.org; Mon, 06 Feb 2023 16:39:33 +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 1pP4Wi-0003DP-PG for pgsql-hackers@lists.postgresql.org; Mon, 06 Feb 2023 16:39:33 +0000 Received: from mail-ej1-x631.google.com ([2a00:1450:4864:20::631]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1pP4Wf-0007op-KN for pgsql-hackers@postgresql.org; Mon, 06 Feb 2023 16:39:31 +0000 Received: by mail-ej1-x631.google.com with SMTP id m2so35877959ejb.8 for ; Mon, 06 Feb 2023 08:39:29 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=cybertec-at.20210112.gappssmtp.com; s=20210112; h=message-id:date:content-transfer-encoding:content-id:mime-version :comments:references:in-reply-to:subject:cc:to:from:from:to:cc :subject:date:message-id:reply-to; bh=aX+3wFqelbWkxWFBk6KDI2njQv9ePZ++M42BegauVXE=; b=iGyKV2/iDqOrs1mQWXKfQZ3zYirkVNB3PjxtSoHUlSfVY06/VghaE16AWidpaUGrlA B3s5UAZVbs9zh38zIDNJY5/lWG8YJy6lAgplGQZCa1n2nd5Le1bWflUm6FhrioJNlWKw yHHZhLMtp1l7roM8vV65KTkv2w8t4+ETBzBtxQIGmfrQwaTOCpQ6xsrq4jth7a6cRaIU uvnD0QT67gjXQMUBUYKlkQndzdufpR6ilo44o6wsfjszLH2QEYUK0KG884szRN22a0iM 51arCjuAccBCAFDoNvdcaq+Zqg5h5me7Vb6cWrPByEZFjPsnFLQYnXPTSM6zqt4XA2t8 J5TA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=message-id:date:content-transfer-encoding:content-id:mime-version :comments:references:in-reply-to:subject:cc:to:from :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=aX+3wFqelbWkxWFBk6KDI2njQv9ePZ++M42BegauVXE=; b=pBncW11OaD4pMmAjSPulm1BXrAlCuYSPbbtvBwGJ13EiZSrMYajM//UdzY0/2JR+FQ gZKT5UiSWd3FYu1JYTxPieif4byVnBUf8KEvLssz/NmZRi0YsKt+8FffxXYts6Ps5N3a 1HJTQjgW+2ctCqgjOujqclOseKCSZGJz1PKLDYU0vavCshLbfiLSitdkMStoxQIYSclC OEFYp+1KMmP5JJubwslfHXaUwNuNk+q4QzF3bqKRnSCQkaiBN2GE+vX8RqCwGjTLtXag 6ar2CqzZoqbSeeXo1/nXEf4z//MWi0bSlLQ7LgfeleuYAPFM7mGq2C3Rva9dQKf/FLVr 64pg== X-Gm-Message-State: AO0yUKUu5aMM/TD7s6H9XsAXiTekMoz2HilGR977RS/h4/+2k2ZFeS1V lOIVSLa+ukWD7owEXuMOC/KUEQ== X-Google-Smtp-Source: AK7set+0TbS86oSjH9r3Ktda5BzEFoWNMunKzSug9w62bgjHEKhj4Cv9bp5ZDMlfBJVaVYssJSVSZw== X-Received: by 2002:a17:906:8153:b0:879:ab3:93d1 with SMTP id z19-20020a170906815300b008790ab393d1mr22925910ejw.4.1675701567041; Mon, 06 Feb 2023 08:39:27 -0800 (PST) Received: from antos ([88.103.134.12]) by smtp.gmail.com with ESMTPSA id cw20-20020a170906c79400b008787c8427c1sm5617802ejb.214.2023.02.06.08.39.26 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 06 Feb 2023 08:39:26 -0800 (PST) From: Antonin Houska To: James Coleman cc: Tomas Vondra , vignesh C , Andres Freund , Robert Haas , pgsql-hackers , Amit Kapila , Tom Lane Subject: Re: Parallelize correlated subqueries that execute within each worker In-reply-to: References: <20220322004805.vwmgjp62tq3mj2oq@alap3.anarazel.de> <1818a285-59b9-e028-c5a8-539f715b9371@enterprisedb.com> Comments: In-reply-to James Coleman message dated "Mon, 23 Jan 2023 09:00:31 -0500." X-Mailer: MH-E 8.6+git; nmh 1.7+dev; GNU Emacs 28.2.50 MIME-Version: 1.0 Content-Type: text/plain; charset="us-ascii" Content-ID: <20282.1675701654.1@antos> Content-Transfer-Encoding: quoted-printable Date: Mon, 06 Feb 2023 17:40:55 +0100 Message-ID: <20283.1675701655@antos> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk James Coleman wrote: > On Sat, Jan 21, 2023 at 10:07 PM James Coleman wrote: > > ... > > While working through Tomas's comment about a conditional in the > > max_parallel_hazard_waker being guaranteed true I realized that in the > > current version of the patch the safe_param_ids tracking in > > is_parallel_safe isn't actually needed any longer. That seemed > > suspicious, and so I started digging, and I found out that in the > > current approach all of the tests pass with only the changes in > > clauses.c. I don't believe that the other changes aren't needed; > > rather I believe there isn't yet a test case exercising them, but I > > realize that means I can't prove they're needed. I spent some time > > poking at this, but at least with my current level of imagination I > > haven't stumbled across a query that would exercise these checks. > = > I played with this a good bit more yesterday, I'm now a good bit more > confident this is correct. I've cleaned up the patch; see attached for > v7. > = > Here's some of my thought process: > The comments in src/include/nodes/pathnodes.h:2953 tell us that > PARAM_EXEC params are used to pass values around from one plan node to > another in the following ways: > 1. Values down into subqueries (for outer references in subqueries) > 2. Up out of subqueries (for the results of a subplan) > 3. From a NestLoop plan node into its inner relation (when the inner > scan is parameterized with values from the outer relation) > = > Case (2) is already known to be safe (we currently add these params to > safe_param_ids in max_parallel_hazard_walker when we encounter a > SubPlan node). > = > I also believe case (3) is already handled. We don't build partial > paths for joins when joinrel->lateral_relids is non-empty, and join > order calculations already require that parameterization here go the > correct way (i.e., inner depends on outer rather than the other way > around). > = > That leaves us with only case (1) to consider in this patch. Another > way of saying this is that this is really the only thing the > safe_param_ids tracking is guarding against. For params passed down > into subqueries we can further distinguish between init plans and > "regular" subplans. We already know that params from init plans are > safe (at the right level). So we're concerned here with a way to know > if the params passed to subplans are safe. We already track required > rels in ParamPathInfo, so it's fairly simple to do this test. > = > Which this patch we do in fact now see (as expected) rels with > non-empty lateral_relids showing up in generate_[useful_]gather_paths. > And the partial paths can now have non-empty required outer rels. I'm > not able to come up with a plan that would actually be caught by those > checks; I theorize that because of the few places we actually call > generate_[useful_]gather_paths we are in practice already excluding > those, but for now I've left these as a conditional rather than an > assertion because it seems like the kind of guard we'd want to ensure > those methods are safe. Maybe we can later (in separate patches) relax the restrictions imposed on partial path creation a little bit, so that more parameterized partial pat= hs are created. One particular case that should be rejected by your checks is a partial in= dex path, which can be parameterized, but I couldn't construct a query that ma= kes your checks fire. Maybe the reason is that a parameterized index path is mostly used on the inner side of a NL join, however no partial path can be used there. (The problem is that each worker evaluating the NL join would = only see a subset of the inner relation, which whould lead to incorrect results= .) So I'd also choose conditions rather than assert statements. Following are my (minor) findings: In generate_gather_paths() you added this test /* * Delay gather path creation until the level in the join tree whe= re all * params used in a worker are generated within that worker. */ if (!bms_is_subset(required_outer, rel->relids)) return; but I'm not sure if required_outer can contain anything of rel->relids. Ho= w about using bms_is_empty(required) outer, or even this? if (required_outer) return; Similarly, /* We can't pass params to workers. */ if (!bms_is_subset(PATH_REQ_OUTER(cheapest_partial_path), rel->rel= ids)) might look like if (!bms_is_empty(PATH_REQ_OUTER(cheapest_partial_path))) or if (PATH_REQ_OUTER(cheapest_partial_path)) In particular, build_index_paths() does the following when setting outer_relids (which eventually becomes (path->param_info->ppi_req_outer): /* Enforce convention that outer_relids is exactly NULL if empty *= / if (bms_is_empty(outer_relids)) outer_relids =3D NULL; Another question is whether in this call simple_gather_path =3D (Path *) create_gather_path(root, rel, cheapest_partial_path, rel->= reltarget, required_outer, rowsp); required_outer should be passed to create_gather_path(). Shouldn't it rath= er be PATH_REQ_OUTER(cheapest_partial_path) that you test just above? Again, build_index_paths() initializes outer_relids this way outer_relids =3D bms_copy(rel->lateral_relids); but then it may add some more relations: /* OK to include this clause */ index_clauses =3D lappend(index_clauses, iclause); outer_relids =3D bms_add_members(outer_relids, rinfo->clause_r= elids); So I think that PATH_REQ_OUTER(cheapest_partial_path) in generate_gather_paths() can eventually contain more relations than required_outer, and therefore it's safer to check the first. Similar comments might apply to generate_useful_gather_paths(). Here I als= o suggest to move this test /* We can't pass params to workers. */ if (!bms_is_subset(PATH_REQ_OUTER(subpath), rel->relids)) continue; to the top of the loop because it's relatively cheap. -- = Antonin Houska Web: https://www.cybertec-postgresql.com