From 67704fede6a84a512db904c9b7cf6d48fe659b5e Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Thu, 16 Jul 2026 18:17:04 +0900 Subject: [PATCH v1 4/6] Give RI fast-path cached FmgrInfos a resettable fn_mcxt ri_populate_fastpath_metadata() copies the cast and equality FmgrInfos into the cached FastPathMeta with fn_mcxt set to TopMemoryContext (the context active at the copy). fn_mcxt is scratch space for the called function; record_eq() and other type-specific equality and cast functions allocate there. With fn_mcxt = TopMemoryContext, that scratch lives for the session, so repeated fast-path checks on composite or record key types leak memory. The cached metadata itself must persist, so give it a dedicated child context for the FmgrInfos' fn_mcxt and reset that context once per flush, alongside the existing per-flush reset of the entry's flush_cxt. Delete the context when the metadata is freed on cache invalidation. Reported-by: Noah Misch Discussion: https://postgr.es/m/20260705210533.ee.noahmisch@microsoft.com Backpatch-through: 19 --- src/backend/utils/adt/ri_triggers.c | 32 +++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index f95f72348e1..8805b283995 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -159,6 +159,15 @@ typedef struct FastPathMeta Oid subtypes[RI_MAX_NUMKEYS]; int strats[RI_MAX_NUMKEYS]; AttrNumber index_attnos[RI_MAX_NUMKEYS]; /* index column positions */ + + /* + * fn_mcxt for the cached FmgrInfos above. Cast and equality functions + * (e.g. record_eq()) use fn_mcxt as per-call scratch space; pointing it at + * a dedicated context that ri_populate_fastpath_metadata()'s caller resets + * keeps that scratch from accumulating for the session, as it would if + * fn_mcxt were TopMemoryContext. + */ + MemoryContext scratch_cxt; } FastPathMeta; /* @@ -2595,6 +2604,7 @@ InvalidateConstraintCacheCallBack(Datum arg, SysCacheIdentifier cacheid, riinfo->valid = false; if (riinfo->fpmeta) { + MemoryContextDelete(riinfo->fpmeta->scratch_cxt); pfree(riinfo->fpmeta); riinfo->fpmeta = NULL; } @@ -3032,6 +3042,13 @@ ri_FastPathBatchFlush(RI_FastPathEntry *fpentry, Relation fk_rel, } MemoryContextReset(fpentry->flush_cxt); + + /* + * Reset the fn_mcxt scratch used by the cached cast/equality FmgrInfos, so + * anything they left behind this flush does not accumulate. + */ + MemoryContextReset(riinfo->fpmeta->scratch_cxt); + MemoryContextSwitchTo(oldcxt); } @@ -3543,6 +3560,17 @@ ri_populate_fastpath_metadata(RI_ConstraintInfo *riinfo, Assert(riinfo != NULL && riinfo->valid); fpmeta = palloc_object(FastPathMeta); + + /* + * Dedicated scratch context for the cached FmgrInfos' fn_mcxt. It lives + * as long as fpmeta (a child of TopMemoryContext) but is reset once per + * flush by the caller, so scratch left behind by cast/equality functions + * does not accumulate for the session. + */ + fpmeta->scratch_cxt = AllocSetContextCreate(TopMemoryContext, + "RI fast-path finfo scratch", + ALLOCSET_SMALL_SIZES); + for (int i = 0; i < riinfo->nkeys; i++) { Oid eq_opr = riinfo->pf_eq_oprs[i]; @@ -3568,9 +3596,9 @@ ri_populate_fastpath_metadata(RI_ConstraintInfo *riinfo, fpmeta->index_attnos[i] = idx_col + 1; fmgr_info_copy(&fpmeta->cast_func_finfo[i], &entry->cast_func_finfo, - CurrentMemoryContext); + fpmeta->scratch_cxt); fmgr_info_copy(&fpmeta->eq_opr_finfo[i], &entry->eq_opr_finfo, - CurrentMemoryContext); + fpmeta->scratch_cxt); fpmeta->regops[i] = get_opcode(eq_opr); get_op_opfamily_properties(eq_opr, -- 2.47.3