Re: Fix trigger memory leak

From: surya poondla <suryapoondla4(at)gmail(dot)com>
To: Paul A Jungwirth <pj(at)illuminatedcomputing(dot)com>
Cc: PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>, Andres Freund <andres(at)anarazel(dot)de>, Chao Li <li(dot)evan(dot)chao(at)gmail(dot)com>
Subject: Re: Fix trigger memory leak
Date: 2026-11-02 23:03:59
Message-ID: CAOVWO5oa3Pjat-BnxuJr_QkAAwpuBQt+HsJ_MHnXu3ogEAT4ng@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi Paul,

I tested v1 against master using PL/pgSQL triggers that return NEW
without assigning to it, with a single statement over 50k rows and a 1kB
text column.

For INSTEAD OF INSERT and UPDATE on a view, ExecutorState grows to about
104MB on master, and the same happens when the INSTEAD OF UPDATE trigger
returns NULL. With v1 it stays flat in all three cases. If the trigger
assigns to NEW at all (even NEW.id := NEW.id), the leak goes away, since
PL/pgSQL then returns a new tuple.

I also found that you don't need a view to hit this. A plain BEFORE
INSERT trigger on a partitioned table leaks just as much when the row is
routed to a partition with the same column layout as the parent. In that
case there's no conversion map, so ExecPrepareTupleRouting() passes the
root's virtual slot straight through and ExecBRInsertTriggers() ends up
with a copy it never frees. Inserting directly into the partition, or
routing to one with a different column order, doesn't leak. Maybe we can
put that in the commit message but not really needed.

v1 looks good to me.

Here's the partitioned case, in case it's useful. It prints about 105MB
on master and about 544kB with v1:

CREATE TABLE p (id int, pad text) PARTITION BY RANGE (id);
CREATE TABLE p1 PARTITION OF p FOR VALUES FROM (0) TO (100000);

CREATE FUNCTION trg() RETURNS trigger LANGUAGE plpgsql AS $$
BEGIN
IF NEW.id = 50000 THEN
RAISE NOTICE 'ExecutorState: %', (SELECT
pg_size_pretty(max(total_bytes))
FROM pg_backend_memory_contexts WHERE name = 'ExecutorState');
END IF;
RETURN NEW;
END $$;
CREATE TRIGGER t BEFORE INSERT ON p FOR EACH ROW EXECUTE FUNCTION trg();

INSERT INTO p SELECT g, repeat('x', 1000) FROM generate_series(1, 50000)
g;

Regards,
Surya Poondla

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Tomás Senart 2026-11-02 23:15:13 [PATCH v1] Free oversized decoded WAL records in XLogReaderFree()
Previous Message Tomas Vondra 2026-11-02 22:49:05 Re: Fix BRIN numeric_minmax_multi_ops out of range value range merging