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 1w3V5h-001Hek-0P for pgsql-hackers@arkaria.postgresql.org; Fri, 20 Mar 2026 08:20:21 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1w3V5f-0054E2-1n for pgsql-hackers@arkaria.postgresql.org; Fri, 20 Mar 2026 08:20:19 +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 1w3V5f-0054Ds-0K for pgsql-hackers@lists.postgresql.org; Fri, 20 Mar 2026 08:20:19 +0000 Received: from mail-pl1-x62e.google.com ([2607:f8b0:4864:20::62e]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1w3V5d-000000008Tk-1QVf for pgsql-hackers@postgresql.org; Fri, 20 Mar 2026 08:20:18 +0000 Received: by mail-pl1-x62e.google.com with SMTP id d9443c01a7336-2a871daa98fso2985635ad.1 for ; Fri, 20 Mar 2026 01:20:17 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1773994816; cv=none; d=google.com; s=arc-20240605; b=TBxS2a/wELALZZwWreU1OY/7BzLVdoe3YzZLelYftpdjSX5bNcDdUcMdL8mtksg5fT Cvf4J5WnBRs9eddOV6AcYU7K1SnKvmfAImc3CKNU3zLYY//7zh9oADEKk0cAxl/gp6HG K5zpXD0FMroaJLz2zfB32OgQUWm2MTqTGNs8Q6Ebkey9LRLg+8AKdkekA3V5ioY5jtUc h27qjlaRbxDyn4xVC8SzxBJZwKzQrjN4nnjE0EnJEu0/hioqk0RaPErEmLD4SPTJLf99 baKBop7hLFYUvnFZzQtFVcxPvJe5wwA/yDFZqLnXtvq8OKfnsW321onvRWmIawKyjaxA yUdA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20240605; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:dkim-signature; bh=69JnVGcNWXLKtUGUOHSPoE2ejZ20CdhrtIsLDy4OY+Q=; fh=hZxkHbt8Q85aEXGAtrQI0CNQSnSj9lZeu1TTgmptq3E=; b=g3aux2fll9YnBSLEB5y0EfoyadCY+28gaszu/fZCzs9bGv8IEMwb5eoI6oGtr8PKtJ CnPJw5SvqYeJd8p/IYVZ0Jt4s6VvnlIhLfBGTz2NwO4rNe/e2Y90guSkEnK4vuQ7RacV m9s6Y6d3qtYbgrwmLIucx6EmMWOz9G8LGhLIbjcaO+EDUn2xbh74wj8r7EEsPTuVtZyf Dc93fT6rWDCO4C1Rovh6/j84u8mWcijh7aXyD/3rKyzwhEn7rpRD/9+vd/sayfQx2mcP 2qQtmMlNrD/uvYEziM5UaLBkjaxCg0SxC4Xfp25nV1WoZDB69nyPFxLbTM2p7vMEF6XR uWnw==; 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=20230601; t=1773994816; x=1774599616; darn=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=69JnVGcNWXLKtUGUOHSPoE2ejZ20CdhrtIsLDy4OY+Q=; b=iz0kCQ2BHtXSVgtOIMDzT+SfLeQCgmFfE/CWPyFL1YLj9Yg7N+DF/tlq6nPPw9O0y6 515R3luJauWB56VKza1fSthNd0vYoCVhD28iu2WB1Sye7iTG7py01J49s9R3BFgbfF4d ASUBQzEsTq5xavMaESsTCmTMLVstquy5vYSeYvo3ETLhG54INgpRLkVembhNtSiSyyBI X5Amgb1KL2kJZaOuXfo4Qb1/xsNzMy0U5j74oD74hF73nH8taYCtdRiEXbzDM2MqkpMN KUn4R6uLJk8kOEApZ12OV6htVRWN5LznLTq5BFgpxa75jEkK3dm1dxfblYO1F9EOY5Wh OWpA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1773994816; x=1774599616; 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=69JnVGcNWXLKtUGUOHSPoE2ejZ20CdhrtIsLDy4OY+Q=; b=cN6lFcAU5jKoc2G3fLSS6SgRCa0KqWXC5bhL7AvLCDcroF1MARdEslPfT075KiXmoj yhhxoe/TqCdJjLZJprNAB+4cuLl6/HTOQggGMu8/fdYh/CvDN9y40A0AOuSZz9DjjkCs zP6yyKBUHOD01NYCFdT65HMsJgYNDUJDV91h+ALItszUisKUVJfV9hzhHauDOVBNyPcR BMhy/jorn0L6qPtlTZgVodOOuhZNnY6cHrCUG2oSX8bDju4cQ9YeyHVvUr4lafc1tX7v gTu8oJhLJc2SUPeyunK1mwXIfXxoGJrwtB851ZHt3+LZxBoRbpiwnG2oLtPJge/dTzhS BvmA== X-Forwarded-Encrypted: i=1; AJvYcCWdE+4MyJLdvSDqN57Ht45+w42+Jsf1P7k8qjus7zceU389ZsfELK8LfN+fpWJRhMVOxdsnt9sK0z9u17UD@postgresql.org X-Gm-Message-State: AOJu0YzqOCqQV52lCmfBLUbFqWtjJEiciiBCTeUjtEYyr6C+/6vDlKUc k5fSWAn6E9FQwuj2CZuzwtWEOfosioPmGdoj5hZFCFsSvlAQDXaua/+5LCH9PP9aO6ph8IdjY5i ZBPsU7UdxuXNtf9AD+PDpzQB7xaGz2Us= X-Gm-Gg: ATEYQzzSsoFkyVdFxFoG7uwmYTLHqoeoGEqxsk9xo5gW+mSDekSOqqN6NNr+j1st68I TpKviwjz3jLajbKF6bY8mSHTMg/Q8Dmi7RYdSJ8WiF97Uj+tKtof7RRz82yNXPzWYFE//XYp5Vl R+57R5VAJvCBWy9RjR3SV65g/+ll12xCcplMI/dRpjUw+WguRBPCMo998gZ51ACluBeyCzO/gAO fy/+DPa3JVLEJ6Qo7Nr9K53EfOLOOQip3KOYaIaXkmVRbXXeSXtOsw2Qz1cy2vFUxku2xRnSvk4 ZNy8aIe/ X-Received: by 2002:a17:903:198c:b0:2b0:774b:d89e with SMTP id d9443c01a7336-2b0827b413bmr20980165ad.37.1773994816406; Fri, 20 Mar 2026 01:20:16 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: Amit Langote Date: Fri, 20 Mar 2026 17:20:04 +0900 X-Gm-Features: AaiRm52lN2GWZmzAWssrexk8aJSwiJ8V5gLdoDBum5Twxz7pJz9RJJdD9YTlihM Message-ID: Subject: Re: Eliminating SPI / SQL from some RI triggers - take 3 To: Junwang Zhao Cc: Pavel Stehule , PostgreSQL-development , Tomas Vondra Content-Type: multipart/alternative; boundary="000000000000dd8742064d705ab2" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000dd8742064d705ab2 Content-Type: text/plain; charset="UTF-8" On Mon, Mar 16, 2026 at 23:03 Amit Langote wrote: > Hi Junwang, > > Thanks for sending the new version. > > On Tue, Mar 10, 2026 at ... Junwang Zhao wrote: > > 1. > > Move ri_ReportViolation into ri_FastPathCheck, so table_open is no > > longer needed, and ri_FastPathCheck now returns void. > > Good, kept. > > > 2. > > After adding the batch fast path, the original ri_FastPathCheck is only > > used by the ALTER TABLE validation path. This path cannot use the > > cache because the registered AfterTriggerBatch callback will never run. > > Therefore, the use_cache branch can be removed. > > Agreed. I went a step further and restructured 0002 to avoid the > use_cache branching entirely. Instead of adding if/else blocks to > ri_FastPathCheck, 0002 now adds a separate ri_FastPathCheckCached() > function with its own resource lifecycle. 0003 then replaces it with > ri_FastPathBatchAdd() -- a clean swap rather than completely undoing > what 0002 added. This also removes the use_cache parameter from > ri_FastPathProbeOne; the memory context switch to > TopTransactionContext is now the caller's responsibility. > > > 3. > > ri_FastPathBatchFlush creates a new fk_slot but does not cache it in > > RI_FastPathEntry. I tried caching it in v5-0006 and ran some benchmarks, > > it didn't show much improvement. > > I put the fk_slot in the cache entry since it's a small change. > > > 4. > > ri_FastPathFlushArray currently uses SK_SEARCHARRAY only for > > single-column checks. [...] my current understanding is that > > SK_SEARCHARRAY may not work for multi-column checks. > > Right, I haven't investigated this deeply either. The FlushLoop > fallback is the right approach for now. If we want to explore a > SEARCHARRAY approach for multi-column keys in a follow-up, it would be > worth checking with Peter Geoghegan or someone else familiar with the > btree SAOP internals on how multiple array keys across columns > are iterated and whether that's usable at all for this use case. > > Attached is v6, three patches -- combined the old 0003 (buffering) and > 0004 (SK_SEARCHARRAY) into a single 0003, since the buffering alone > has no performance benefit (or at least only minor) and the split > added unnecessary diff/rebase churn. > > The biggest change in this version is the snapshot handling. Looking > more carefully at what the SPI path actually does for RI_FKey_check > (non-partitioned PK, detectNewRows = false), I found that > ri_PerformCheck passes InvalidSnapshot to SPI_execute_snapshot, and > _SPI_execute_plan ends up doing > PushActiveSnapshot(GetTransactionSnapshot()). So the SPI path scans > with the transaction snapshot, not the latest > snapshot. > > So I've changed the fast path to match: ri_FastPathCheck now uses > GetTransactionSnapshot() directly. Under READ COMMITTED this is a > fresh snapshot; under REPEATABLE READ it's the frozen > transaction-start snapshot, so PK rows committed after the transaction > started are simply not visible. This means the second index probe > (the IsolationUsesXactSnapshot crosscheck block) is no longer needed > and is removed. The existing fk-snapshot isolation test confirms this > is the correct behavior. > > Other changes since v5: > > * Fixed the batch callback firing during nested SPI: another AFTER > trigger doing DML via SPI would call AfterTriggerEndQuery at a nested > level, tearing down our cache mid-batch. Fixed by checking > query_depth inside FireAfterTriggerBatchCallbacks. Added a test case > with a trigger that auto-provisions PK rows via SPI. > > * Security context is now restored before ri_ReportViolation, not > after (the ereport doesn't return). > > * search_vals[] and matched[] moved from RI_FastPathEntry to stack > locals in ri_FastPathFlushArray -- they're rewritten from scratch on > every flush with no state carried between calls. > > * Various comment fixes. > > I think this is getting close to committable shape. That said, another > pair of eyes would be reassuring before I pull the trigger. Tomas, if > you've had a chance to look, would welcome your thoughts. Adding Tomas to cc -- I thought he was already on the thread when I sent this. Tomas, context is in the quoted email below. Junwang has posted a couple of updated versions since then addressing review feedback from Haibo Yan (memory context handling for the flush path, batch size rationale). Latest patches are in the thread. If you have time, would appreciate a look. Thanks, Amit > --000000000000dd8742064d705ab2 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
On Mon, Mar 16, 2026 at 23:03 Amit Langote <amitlangote09@gmail.com> wrote:
=
Hi Junwang,

Thanks for sending the new version.

On Tue, Mar 10, 2026 at ... Junwang Zhao <zhjwpku@gmail.com> wrote:
> 1.
> Move ri_ReportViolation into ri_FastPathCheck, so table_open is no
> longer needed, and ri_FastPathCheck now returns void.

Good, kept.

> 2.
> After adding the batch fast path, the original ri_FastPathCheck is onl= y
> used by the ALTER TABLE validation path. This path cannot use the
> cache because the registered AfterTriggerBatch callback will never run= .
> Therefore, the use_cache branch can be removed.

Agreed.=C2=A0 I went a step further and restructured 0002 to avoid the
use_cache branching entirely.=C2=A0 Instead of adding if/else blocks to
ri_FastPathCheck, 0002 now adds a separate ri_FastPathCheckCached()
function with its own resource lifecycle.=C2=A0 0003 then replaces it with<= br> ri_FastPathBatchAdd() -- a clean swap rather than completely undoing
what 0002 added.=C2=A0 This also removes the use_cache parameter from
ri_FastPathProbeOne; the memory context switch to
TopTransactionContext is now the caller's responsibility.

> 3.
> ri_FastPathBatchFlush creates a new fk_slot but does not cache it in > RI_FastPathEntry. I tried caching it in v5-0006 and ran some benchmark= s,
> it didn't show much improvement.

I put the fk_slot in the cache entry since it's a small change.

> 4.
> ri_FastPathFlushArray currently uses SK_SEARCHARRAY only for
> single-column checks. [...] my current understanding is that
> SK_SEARCHARRAY may not work for multi-column checks.

Right, I haven't investigated this deeply either.=C2=A0 The FlushLoop fallback is the right approach for now.=C2=A0 If we want to explore a
SEARCHARRAY approach for multi-column keys in a follow-up, it would be
worth checking with Peter Geoghegan or someone else familiar with the
btree SAOP internals on how multiple array keys across columns
are iterated and whether that's usable at all for this use case.

Attached is v6, three patches -- combined the old 0003 (buffering) and
0004 (SK_SEARCHARRAY) into a single 0003, since the buffering alone
has no performance benefit (or at least only minor) and the split
added unnecessary diff/rebase churn.

The biggest change in this version is the snapshot handling.=C2=A0 Looking<= br> more carefully at what the SPI path actually does for RI_FKey_check
(non-partitioned PK, detectNewRows =3D false), I found that
ri_PerformCheck passes InvalidSnapshot to SPI_execute_snapshot, and
_SPI_execute_plan ends up doing
PushActiveSnapshot(GetTransactionSnapshot()).=C2=A0 So the SPI path scans with the transaction snapshot, not the latest
snapshot.

So I've changed the fast path to match: ri_FastPathCheck now uses
GetTransactionSnapshot() directly.=C2=A0 Under READ COMMITTED this is a
fresh snapshot; under REPEATABLE READ it's the frozen
transaction-start snapshot, so PK rows committed after the transaction
started are simply not visible.=C2=A0 This means the second index probe
(the IsolationUsesXactSnapshot crosscheck block) is no longer needed
and is removed.=C2=A0 The existing fk-snapshot isolation test confirms this=
is the correct behavior.

Other changes since v5:

* Fixed the batch callback firing during nested SPI: another AFTER
trigger doing DML via SPI would call AfterTriggerEndQuery at a nested
level, tearing down our cache mid-batch.=C2=A0 Fixed by checking
query_depth inside FireAfterTriggerBatchCallbacks.=C2=A0 Added a test case<= br> with a trigger that auto-provisions PK rows via SPI.

* Security context is now restored before ri_ReportViolation, not
after (the ereport doesn't return).

* search_vals[] and matched[] moved from RI_FastPathEntry to stack
locals in ri_FastPathFlushArray -- they're rewritten from scratch on every flush with no state carried between calls.

* Various comment fixes.

I think this is getting close to committable shape. That said, another
pair of eyes would be reassuring before I pull the trigger. Tomas, if
you've had a chance to look, would welcome your thoughts.<= div dir=3D"auto">
Adding Tomas to cc -- I th= ought he was already on the thread when I sent this. Tomas, context is in t= he quoted email below. Junwang has posted a couple of updated versions sinc= e then addressing review feedback from Haibo Yan (memory context handling f= or the flush path, batch size rationale). Latest patches are in the thread.= If you have time, would appreciate a look.
=

Thanks, Amit=C2=A0
--000000000000dd8742064d705ab2--