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 1wgkKK-006N1o-0B for pgsql-hackers@arkaria.postgresql.org; Mon, 06 Jul 2026 14:29:40 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wgkKI-0018ST-21 for pgsql-hackers@arkaria.postgresql.org; Mon, 06 Jul 2026 14:29:38 +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 1wgkKI-0018SL-0h for pgsql-hackers@lists.postgresql.org; Mon, 06 Jul 2026 14:29:38 +0000 Received: from mail-pj1-x1034.google.com ([2607:f8b0:4864:20::1034]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wgkKF-00000001rLc-44Wo for pgsql-hackers@postgresql.org; Mon, 06 Jul 2026 14:29:37 +0000 Received: by mail-pj1-x1034.google.com with SMTP id 98e67ed59e1d1-37e0a189b0bso2117998a91.1 for ; Mon, 06 Jul 2026 07:29:35 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1783348175; cv=none; d=google.com; s=arc-20260327; b=oUvvug1m9v7Ux7nOgDM9khwxWw6jHJp8fXI0N2//DvrrDzH2BtxxdxKUsiCL6Ggv/U nvX7In/S0vmjTNhDPK4B8fUKs1A6KNluyzW89cwZ91BVQnM42ZFFuPZXo1gHOVEajAkx CIAgpWZ7MEx3Y63/aWgyjiHiNYXagpUOQP7ODQ/dhhCBqrNhuAN2t+QBAUmY6PryvIjb pm5uAZRfTezVQ2ysK5QxI7qgwjqjGQW7y1uioiiGgi2Du5WB46s+srPCphPXYtNHoEsM yMNpGcR2PcFkpfqszuJo3fiYwIOZ83Jnbx8Hn7LppTMKntLzuos3Oa9D7N4eDGHoZipV Xzng== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=PGeM17x+pUCtju33D916HjDTmhpibIHDPSuU62ccTSo=; fh=KQOhybCjdZQ3QYZpcbrK6VMLCmJejZZTpShVqfm0iBU=; b=ctaCaGbc/gFiQ16Kcy0/8cvFOvqczavK4qu54/7bdotnR5vp2a21VxteedEfPT7aOo INfEYJiViRO5aHMeK0MxVvoI9G4ecPQObR3nd9JH30yKW2Ji/8ckFuVFLz39v+03M/II KQSqyhSDl4kIJOSMb4X4ZNSh7IFd55jbj6JGN3xPDv0nDEXPFcl7L+Rvj2rkdn7N5mD7 oNAfl2ZbQHOV0OmRGE3RolroTe5PyVg8/jM8DdcQYI7oB0aWFVopavDY3WgsS+yuECBH 906FQoSyPTp0f38n4MPgOGAE2ylZ3ncfHOhNfxHRQLM946EPU2ifob3BgOeRqM5uCjdC w5eg==; 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=1783348175; x=1783952975; 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=PGeM17x+pUCtju33D916HjDTmhpibIHDPSuU62ccTSo=; b=FCv/6o5U9pIsjPzNngqIen6NF+oMjCg0fhiQ602kFIgXfZ+ACM+0Mb2g2qqmad39qG pCIOckGjbRgknjKu9l/KyltXi00RRS9DLCjGcHg1kHFT5H++VV6PVvvHV+o281a9yJD3 Eg4ngWfWE/gJ6EJWsf2kt1N21yDCjjKQeLvRIb720ae4NO7paQEHzmy5PI2tABR3u0jV PbFVClW8GrdzEA3FJY1wQewXW92o6v3UHnjcO7NQcgkLFgRr51X5ovFGnr+tq0dzvi/J /Z6Gzx7jRIUZ+92XDr6VW82AXo8vsy/8k9qQ6zkylvGxNzFTgxeEa6d4mjWmg1gvtrbC CWFA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783348175; x=1783952975; 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=PGeM17x+pUCtju33D916HjDTmhpibIHDPSuU62ccTSo=; b=TZFuXmYrk8ZVnKhJKtvK0LtsLAq1PwyzlVerwdAlnXcqTg5KQ4MSEwRyzhe0PxbSnt YreLpx0WEVJseMfDoXGpx7wOtM4IMs2lNY13Q63gVXkPF9KeqLoUUBmeFwg+MeD9lIST M30dPHIU/h7ymD0j7xI1CdY/m+5bkvauxRze8LHld6X5oMM2JVLqv9XuZxxpsQ52RGH9 Bu8Hr4GEqTVnxx4hIRW+UWsMbMxI74IbTdnn1gVyFQAP3AZOmMf3syPNpMk3c/6rn5wR 0js4CRTl6/9KJEL+XQ1oPZNEGhpa8h2OOzXZuelGX/Lby0zmYD1bCdzr6pA7WhG2QFqm LIaA== X-Forwarded-Encrypted: i=1; AHgh+RpMEJ0Mr7KLcY7Vj2LJnfSobwPrw/scvGDWO0HefQYzt6wkb96oiGlrglKBWzXM71w4xeOd/vuODR5EAeJc@postgresql.org X-Gm-Message-State: AOJu0YznRKneUpSy0AF2SruMlTrZXPmOj2LPGLQEuNIOWdMYUt49bCiZ RGUyCgCQXKgOOoBnnlHOrvnfnF07C4uXyf0N9tX04hJyT3dFxmnwDhZUMB5xKnSYJgz2jAgLMcg WFKccrd75qthjvKaXbzyUKjAhpFdN240= X-Gm-Gg: AfdE7cl2yFxYdZH5REhfHYWfy6FjNDVg6jhlqxq7bZ1DhBt1BSxfDtK+NPxWnrEijM+ S/uhFxtlD3wMgAN9zahGDutlGWII6D/ISnivxykU1G/OBb+lU6AbBfm7sM0+hr8rleWW5tzgcGp msWqaxzFScUC0IgylREklRA2MbDXS9xW18zV4F+BWdGNeF6H5Bhho4MpsWNzAAgDy3+dFg7+jJu ZwbieulkirYlCJ++eYXgr+TBcDA8mYZ37LhcKLhODle3z8JEHAwV0lCdgse1fFFa4Xpazfmno/l /qKbfQBFdTlG79GjXGWGPma4G2h7lnEAtz4faN/Gd9kBCySiT3SBSw== X-Received: by 2002:a17:90a:dfc6:b0:382:3dcc:1487 with SMTP id 98e67ed59e1d1-387572b3693mr713140a91.25.1783348174945; Mon, 06 Jul 2026 07:29:34 -0700 (PDT) MIME-Version: 1.0 References: <20260705222115.be.noahmisch@microsoft.com> In-Reply-To: <20260705222115.be.noahmisch@microsoft.com> From: Amit Langote Date: Mon, 6 Jul 2026 23:29:18 +0900 X-Gm-Features: AVVi8CdZmIDyj0MuD-PJ6sLKlBoejQHYqDqHimGHH_n_C2KzUA-xxVh8HiPNjlU Message-ID: Subject: Re: PG19 FK fast path: OOB write and missed FK checks during batched To: Noah Misch Cc: Junwang Zhao , Ayush Tiwari , Nikolay Samokhvalov , pgsql-hackers mailing list , Andrey Borodin , Kirk Wolak 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 Hi Noah, On Mon, Jul 6, 2026 at 7:21=E2=80=AFAM Noah Misch wrote= : > > On Fri, Jun 12, 2026 at 11:46:07AM +0900, Amit Langote wrote: > > I've pushed these now. Thank you everyone. > > commit 4113873 wrote: > > Confine RI fast-path batching to the top transaction level > > I discourage this fix strategy, for three reasons: > > 1. It slows "BEGIN; SAVEPOINT s; COPY table_with_fk FROM ..." by the ~1.6= x > batching benefit, compared to omitting the SAVEPOINT. That's a bad us= er > experience not seen elsewhere. Starting a high number of subtransacti= ons > is expensive, but wrapping a long-running transaction body in one > subtransaction hasn't been a performance reducer. Ok, I agree it's a wart. > 2. It departs from the PostgreSQL norm of tracking resources by > subtransaction. You can see normal handling in many AbortSubTransacti= on() > callees, e.g. AtEOSubXact_LargeObject(). This in turn makes the chang= e > harder to verify as correct. > > 3. It doesn't seem to have simplified code much, compared to our normal > subxact-based approach. Fair, I added a special case, which I can see now doesn't actually simplify things. I'll rework it to track batches per subtransaction the normal way. > > First, on subtransaction abort ri_FastPathSubXactCallback discarded= the > > entire cache. An entry's batch holds rows buffered by the enclosin= g > > transaction, not just the aborting subxact -- the cache is keyed by > > constraint, so a single entry can mix rows from multiple subxact le= vels. > > That would imply having started a subtransaction and then added to the ba= tch > without an intervening pair of CommandCounterIncrement() and > AfterTriggerBeginQuery(). That's an invalid thing for C code to do, so I= 'd > make it an error if a batch would contain rows from different subtransact= ions. Agreed. I confirmed a batch is flushed at AfterTriggerEndQuery and deferred checks don't populate a batch until they fire at top-level commit, so an entry is always at a single subxact level. The "mixes levels" justification in my commit message was a hypothesis I never verified, and it's wrong; a can't-happen assert is most likely the right thing. > (This relates to the reentrancy bug fixed in 0e47bb5. With an intervenin= g > CommandCounterIncrement() and AfterTriggerBeginQuery(), reusing the outer > batch is wrong even without subtransactions: it would check with the wron= g > snapshot. Each level of reentrancy needs to finish its FK checks separat= ely, > even if the batch buffer were unbounded.) > > > An internal subxact abort during after-trigger firing (e.g. a PL/pg= SQL > > BEGIN ... EXCEPTION block) therefore dropped buffered rows > > I suspect a subxact abort during after-trigger firing can still cause a > different problem via AfterTriggerEndQuery(): > > AfterTriggerEndQuery(EState *estate) > { > ... > afterTriggers.firing_depth++; > > AfterTriggerEndSubXact() doesn't undo this increment. Having identified = that > as suspect, I asked Opus 4.8 to try to confirm or refute bug reachability= . I > have not personally verified its finding, but it said: > > CLAUDE [CONFIRMED -- reachable bug; your instinct is right]: firing_dep= th is ++/-- in matched > pairs inside AfterTriggerEndQuery/FireDeferred/SetState with NO PG_TRY,= and AfterTriggerEndSubXact > resets firing_batch_callbacks but NOT firing_depth. So a trigger ERROR = caught by an outer > subtransaction (PL/pgSQL EXCEPTION) skips the -- and strands firing_dep= th>0 for the rest of the > xact. Its sole consumer is AfterTriggerIsActive() -> the RI_FKey_check = batching gate; the only > RI check that runs OUTSIDE genuine trigger firing is ALTER TABLE / VALI= DATE CONSTRAINT per-row > validation. With firing_depth stranded, that validation is wrongly rout= ed into ri_FastPathBatchAdd. > Repro (per-row forced via REFERENCES-only, no SELECT, so RI_Initial_Che= ck bails): > CONTROL: per-row-validated ALTER ADD FK with violating row 99 -> erro= rs AT the ALTER (correct). > BUG: run a caught FK violation first (DO/EXCEPTION), then the same AL= TER in the same xact -> > the ALTER does NOT error, marks convalidated=3Dt, emits "WARNING: r= esource was not closed: > relation pk2 / pk2_pkey / TupleDesc" (a resource-owner leak), and d= efers the violation to > COMMIT. Consequences: resource leak + FK validation deferred past t= he ALTER (documented > invariant broken). Fix: reset firing_depth in AfterTriggerEndSubXac= t, mirroring the > firing_batch_callbacks reset right below it. > > Even if that's a hallucination, it's an example of what I meant in (2) ab= out > making the change harder to verify. That looks like a real bug, likely of the same class as an earlier error-path reset I fixed. I'll verify the reproducer; the fix is likely resetting firing_depth in AfterTriggerEndSubXact alongside the firing_batch_callbacks reset. > Also, it's not clear to me why AfterTriggerEndSubXact() is right to reset > afterTriggers.firing_batch_callbacks. The outer xact may be firing. If > that's okay, can you expand the code comment to explain it? I'll go back and reconstruct why that reset is correct. If I can't, I'll treat it as suspect and address it in the rework, with a comment either way. > > Cleanly unwinding the cache on subxact abort would require tracking= the > > originating subxact of each buffered row, since rows from different > > levels share an entry (the cache is keyed by constraint) and deferr= ed > > constraints cannot be flushed early at a subxact boundary. > > It's true that they can't be flushed early, but I'm not seeing a need for > explicit code to avoid that. A subxact commit shall just confirm there's= no > batch of its subxact level. A subxact abort shall discard any batch of i= ts > subxact level, leaving higher-subxact batches untouched. A deferred trig= ger > doesn't start a batch until the end of the top-level transaction. Yes. I was confused about the relationship between deferred firing and subxacts, which is where that justification came from; deferred batches only exist at the top level, so there's nothing to unwind for them at a subxact boundary. > > The per-row fast path still bypasses SPI and stays well ahead of th= e > > pre-19 SPI-based check. A fuller fix that preserves batching acros= s > > subtransactions -- whether by tracking the originating subxact of e= ach > > buffered row or by per-subxact cache stacks merged into the parent = on > > commit -- is left for a future release. > > If the above suspicion corresponds to a live bug, I'd bet on the fuller f= ix > being cleaner than a surgical fix. That may not pan out, but I recommend > trying it first. Trying it in the direction you describe: subxact abort discards that level's batch, and subxact commit just checks that there's no batch left at that level (it was already flushed at statement end). The part that needs care is the resource-owner handling of the cached PK relation and index when a batch flush errors partway through inside a subxact that then aborts. That's more involved than a subid tag. I'll work through the details and try to post a patch tomorrow. Thanks for the deep review. -- Thanks, Amit Langote