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 1w633e-003tdZ-2c for pgsql-hackers@arkaria.postgresql.org; Fri, 27 Mar 2026 09:00:46 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1w633d-008bgz-0x for pgsql-hackers@arkaria.postgresql.org; Fri, 27 Mar 2026 09:00:45 +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 1w633c-008bgr-2r for pgsql-hackers@lists.postgresql.org; Fri, 27 Mar 2026 09:00:45 +0000 Received: from mail-pg1-x533.google.com ([2607:f8b0:4864:20::533]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1w633b-00000001I3m-1AbJ for pgsql-hackers@postgresql.org; Fri, 27 Mar 2026 09:00:44 +0000 Received: by mail-pg1-x533.google.com with SMTP id 41be03b00d2f7-c742723c863so1170272a12.0 for ; Fri, 27 Mar 2026 02:00:43 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1774602042; cv=none; d=google.com; s=arc-20240605; b=lbwa7HB1W8bCt/RCQPo8QEh7dHfztYHf/t2AonNXRQknaHdlCufKrUrCo4WESpF3RK JvNm4X8gbXDZUP+Py1YdxzdXZz2CG9qv4XEHk7HdSucFQn8Vn5H+2fToWGQTODwaDHV6 BI+bSRlMIjBszJV8RJc9nAAvFN2MKcDwX0OE8qp73L4YqHwQbhVyxSVLWqIsL5GnqJNQ rgDU7Le4/dAV2Ft9TwAzOpbJC5yPItT4Xc00QiFbJM8BNp1SdW3hcSI7/9zmyw+11ufA sGacASyNSpcwJ0OPqUq9dN3gCNPMPgUqKvB8mRB3nRdOuW/0Ax+Ywy5cVG0plsgFnYE5 13mA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=3pSjdqo/5q2G/QAJ0mEZ3fdMdJenAHFI9Xk4Y7YVofw=; fh=zeYh2Q2VdkUM4kXZaifLuMLFZxgUE6rbq8eRWX4knlg=; b=ORwG9CQZnNFYUu/c9zu40JVlP6gqQ8OgGUkMTr12cquqGl2xlc0UpaNIx0zVV+qqf6 HZ27A0IZiYPx2pb1fft2uAWlCoDbyvC1sk+4d3AsTZ5jV9C/qmEUIOPopxD033oeSrt7 U+ZoEzSznmsUcuXZUtXkBBwvRrV6AocInoh6SsU6CLU17sOZqGrXRnha6XSkT0aYKr0Z q7EGZ6iy8kCnK1TU7VPqRSSSP2jpuZ1+2syKusqrzf+GRa12oK4XvPuls/4ZRTCVjDKw VyoWOedZdzkCUuyerXqVLxFoUpGXEBRFRCk9tX7pJDRGaKDu04EHteM4+0j9Z5Sts37f KTBg==; darn=postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1774602042; x=1775206842; darn=postgresql.org; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:from:to:cc:subject:date :message-id:reply-to; bh=3pSjdqo/5q2G/QAJ0mEZ3fdMdJenAHFI9Xk4Y7YVofw=; b=ZNxenj/C7CT3+jqyUuQYM3PNOxYjesr04iP4IpbdBwC26JqzUFzctGxT+waRWClUmg 80tEbWqWgaEbs1oNlbfQhDhAMKXTttzPsmDlK0fBnjjRFMM63tLdZHRuXBpwhJB5tZWr wS0bBns/BBIwKCkaOrrpXny/7Bb1tnBTPBfSfEzwq32/P41MrCaGRfl7BsuKFKu2kbN7 Arqerql6duQ09AryaTGG8Ejn5APuv7kI788iVqNM74u5bTD76Mz64p3D87h4Sg8dcGy7 hHaZQT9WDJv+99I9aDoSf9MkXZRNvkFbBd/yEIotzyFxZvs23CYxistL75oVyQoYmAqA 8A6w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1774602042; x=1775206842; h=content-transfer-encoding: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=3pSjdqo/5q2G/QAJ0mEZ3fdMdJenAHFI9Xk4Y7YVofw=; b=DhD62z88L1yfAYqHsfjZy+U8Tk+1E9RS4LiLPqedUQL5zkY9D24R8govZya5Ch6NAh X4fMS38dm9QKQnPF0YgAh6gIrsP4ae3KkPVcKNE94w71GfTmPFmfuwEvJbdhcRe2oMr7 7A8dw0RiCzc5SvGDljolc1qV7DsIWloR3qt7xTkm67ZfYhMomTd/bSVuzK15tt3aeweI ni80JwxymzAR0is7ks22yP36MspLt1xfoYTRUPEIa8BEosmbN92zCQtiKdMKFzwR26qI 1j8K97SIU37phgSAo5QcDaIlUyP/JtoI2C5i9wJN83jjnHSAzwc9HapHxe4ltspXJGR6 y73g== X-Forwarded-Encrypted: i=1; AJvYcCUJS79Is160AvkKM+w33rEDJoIpFFY/vKzUUR1vIcqTJQDnhL9Yd8VcFuZYEmJhKlvF220kYgdvbHGE649D@postgresql.org X-Gm-Message-State: AOJu0YwKeNE2xb0LWCs9vITqWo8ASt8T8mvURgmEh8c0nA0PbmigW6Lc I1Zy/F3j/wnGI9ocHq8KzXHHPStKJfeW9QdvMTdlZaYIFLDrj+0V1eDpxZ793UMmBCA++iSYiEB Q2K4zLyrBNrdxjMWL1+LNaszqLxN/qxQ= X-Gm-Gg: ATEYQzxY8uKYsszXDChTs9taQPy2AFdxoCYYhot0Sj/pDzRO3fFssIu6yBhu+S3jNA0 FLOQmVehFmAmQUevcGtP2mp3bUxYD/jEf/4aAvB8OJmnfiTeGXiZPXa9SdVun3QiHq65LLbZaeW 2QIE+80lkyz/mzI3RkAXQrOk7liTeptaCE7weJB/716wP+BnUouNzfKM29cnJkZYy/G7jkpBve9 +vOiU7ffLjrSCnFpMJ9VYA0vrPmrZTHv7hb5aOd4Ee/iJoywkDwxyVpRI0x5sBFexqv0pHVdiD1 mCQpD/12 X-Received: by 2002:a05:6a20:158e:b0:398:d6a9:fc9 with SMTP id adf61e73a8af0-39c878131e4mr2168117637.5.1774602037569; Fri, 27 Mar 2026 02:00:37 -0700 (PDT) MIME-Version: 1.0 References: <54c35fb9-da3a-4754-ab8c-46ed0b612465@vondra.me> <684c70d7-180e-461d-9377-600c2db581ba@vondra.me> <605328.1747710381@sss.pgh.pa.us> <691261.1747755511@sss.pgh.pa.us> In-Reply-To: From: Amit Langote Date: Fri, 27 Mar 2026 18:00:20 +0900 X-Gm-Features: AQROBzCcLMQewIRL9YMs0dzcRGf-TdiA5IiaLTZaG7yzlxfEmd5QG3n-w2MPa9Y Message-ID: Subject: Re: generic plans and "initial" pruning To: Chao Li Cc: Tom Lane , Tender Wang , Alexander Lakhin , Tomas Vondra , Robert Haas , Alvaro Herrera , Andres Freund , Daniel Gustafsson , David Rowley , PostgreSQL Hackers , Thom Brown Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk On Thu, Mar 26, 2026 at 6:24=E2=80=AFPM Amit Langote wrote: > On Wed, Mar 25, 2026 at 4:39=E2=80=AFPM Amit Langote wrote: > > On Fri, Mar 20, 2026 at 2:20=E2=80=AFAM Amit Langote wrote: > > > On Mon, Mar 9, 2026 at 1:41=E2=80=AFPM Amit Langote wrote: > > > Stepping back -- the core question is whether running executor logic > > > (pruning) inside GetCachedPlan() is acceptable at all. The plan cache > > > and executor have always had a clean boundary: plan cache locks > > > everything, executor runs. This optimization necessarily crosses that > > > line, because the information needed to decide which locks to skip > > > (pruning results) can only come from executor machinery. > > > > > > The proposed approach has GetCachedPlan() call ExecutorPrep() to do a > > > limited subset of executor work (range table init, permissions, > > > pruning), carry the results out through CachedPlanPrepData, and leave > > > the CachedPlan itself untouched. The executor already has a multi-ste= p > > > protocol: start/run/end. prep/start/run/end is just a finer > > > decomposition of what InitPlan() was already doing inside > > > ExecutorStart(). > > > > > > Of the attached patches, I'm targeting 0001-0003 for commit. 0004 (SQ= L > > > function support) and 0005 (parallel worker reuse) are useful > > > follow-ons but not essential. The optimization works without them fo= r > > > most cases, and they can be reviewed and committed separately. > > > > > > If there's a cleaner way to avoid locking pruned partitions without > > > the plumbing this patch adds, I haven't found it in the year since th= e > > > revert. I'd welcome a pointer if you see one. Failing that, I think > > > this is the right trade-off, but it's a judgment call about where to > > > hold your nose. > > > > > > Tom, I'd value your opinion on whether this approach is something > > > you'd be comfortable seeing in the tree. > > > > Attached is an updated set with some cleanup after another pass. > > > > - Removed ExecCreatePartitionPruneStates() from 0001. In 0001-0003, > > ExecDoInitialPruning() handles both setup and pruning internally; the > > split isn't needed yet. > > > > - Tightened commit messages to describe what each commit does now, not > > what later commits will use it for. In particular, 0002 is upfront > > that the portal/SPI/EXPLAIN plumbing is scaffolding that 0003 lights > > up. > > > > - Updated setrefs.c comment for firstResultRels to drop a blanket > > claim about one ModifyTable per query level. > > > > As before, 0001-0003 is the focus, maybe 0004 which teaches the new > > GetCachedPlan() pruning-aware contract to its relatively new user in > > function.c. > > While reviewing the patch more carefully, I realized there's a > correctness issue when rule rewriting causes a single statement to > expand into multiple PlannedStmts in one CachedPlan. > > PortalRunMulti() executes those statements sequentially, with > CommandCounterIncrement() between them, so Q2's ExecutorStart() > normally sees the effects of Q1. > > With the patch, though, AcquireExecutorLocksUnpruned() runs > ExecutorPrep() on all PlannedStmts in one pass during GetCachedPlan(), > before any statement executes. If a later statement has > initial-pruning expressions that read data modified by an earlier one, > pruning can see stale results. > > There's also a memory lifetime issue: PortalRunMulti() calls > MemoryContextDeleteChildren(portalContext) between statements, which > destroys EStates prepared for later statements. > > Here's a concrete case demonstrating the semantic issue: > > create table multistmt_pt (a int, b int) partition by list (a); > create table multistmt_pt_1 partition of multistmt_pt for values in (1)= ; > create table multistmt_pt_2 partition of multistmt_pt for values in (2)= ; > insert into multistmt_pt values (1, 0), (2, 0); > > create table prune_config (val int); > insert into prune_config values (1); > > create function get_prune_val() returns int as $$ > select val from prune_config; > $$ language sql stable; > > -- rule action runs first, updating prune_config before the > -- original statement's pruning would normally be evaluated > create rule config_upd_rule as on update to multistmt_pt > do also update prune_config set val =3D 2; > > set plan_cache_mode to force_generic_plan; > prepare multi_q as > update multistmt_pt set b =3D b + 1 where a =3D get_prune_val(); > execute multi_q; -- creates the generic plan > > -- reset for the real test > update prune_config set val =3D 1; > update multistmt_pt set b =3D 0; > > -- second execute reuses the plan > execute multi_q; > select * from multistmt_pt order by a; > > Without the patch: the rule action updates prune_config to val=3D2 > first, then after CCI the original statement's initial pruning calls > get_prune_val(), gets 2, prunes to multistmt_pt_2, and updates it > correctly: (1, 0), (2, 1). > > With the patch as it stood: both statements' pruning runs during > GetCachedPlan() before either executes. The original statement's > pruning sees val=3D1, prunes to multistmt_pt_1, and multistmt_pt_2 is > never touched. > > The fix is to skip pruning-aware locking for CachedPlans containing > multiple PlannedStmts, falling back to locking all partitions. > Single-statement plans are unchanged. For good measure, I also verified that Tom's test case from last May [1] that prompted the revert of the previous commit works correctly with this patch. When the DO ALSO rule is created mid-execution, the plan gets invalidated and rebuilt as a multi-statement CachedPlan, which triggers the fallback to locking all partitions. No assertions, no crashes. --=20 Thanks, Amit Langote [1] https://postgr.es/m/605328.1747710381@sss.pgh.pa.us