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.94.2) (envelope-from ) id 1s1lBW-00Eeu6-Vy for pgsql-general@arkaria.postgresql.org; Tue, 30 Apr 2024 10:58:06 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1s1lBT-005oZz-N8 for pgsql-general@arkaria.postgresql.org; Tue, 30 Apr 2024 10:58:04 +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.94.2) (envelope-from ) id 1s1lBT-005oZq-6e for pgsql-general@lists.postgresql.org; Tue, 30 Apr 2024 10:58:04 +0000 Received: from mail-lf1-x12e.google.com ([2a00:1450:4864:20::12e]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1s1lBN-000lJT-2S for pgsql-general@lists.postgresql.org; Tue, 30 Apr 2024 10:58:02 +0000 Received: by mail-lf1-x12e.google.com with SMTP id 2adb3069b0e04-51d62b0ecb7so4134556e87.3 for ; Tue, 30 Apr 2024 03:57:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1714474675; x=1715079475; darn=lists.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=w1t2DFMkLyZs/Mi6nT1XXe0ou/QgqBo+d6p5AgIF9EA=; b=GL2RLgTvkotMv8YJFF5r3blnNPt50yVZQXAER1ybxm0ssEWrIwTf81qWjjsud4SKR9 36Ju7UoMm3EBd/jE4wbgrjOv6AU25O+5vlLIIwDefwSOwUb8j+hK/6LW+JCKRj5DxBfa NgExal58fiBDwfayjltkUQxocthD7nCDC7rVgl9uI9B/vpZ6lM78ovinJABV4oYIkVJi +R9CebvRej2Yq5134B6khpTp2DuXX+QufsUdOXnUhcEBolsgzXtdcQjjK8hI27INRKPp asDS4CCcM+y9gAgVDjRm/eOOMmEhdWzY8N64tFWtfq+Tx5PUoj/y9RkJi/LSwhExD974 HPlA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1714474675; x=1715079475; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=w1t2DFMkLyZs/Mi6nT1XXe0ou/QgqBo+d6p5AgIF9EA=; b=XvFZpES7IgHRC+UQfCULsZ0i8gK/weFLkVcWAXsOZZc6Zp7MlWssTWjkZutkishUtR N2xinpnEeevHbxz0RcNzJxiHTrfe47hLykd82poQspO+b1/K8sztjaolX839989PRXqd BFCJfWLR/JvnOElXA//SjCsr4TUZmEnJEtff9C1w+DnMnWZrMvqODnEcFh/RC70LbEuA 1i5DfjVX95tUMaWY41AbxXZGBcoKWx7AaECFfrw6RE3dFizHF0GXEiP/biZYkJnltik8 FuboCN5sjhXuCbsHrS8HkErUKDf0duA5jQNJDAQ1ldn/3M2BaqWm6wg3TtWDF/GB0phO ajHA== X-Gm-Message-State: AOJu0Yy2+R07zI95R1DU+UL/PRUeOc/qj9WJG2QSU8il6UYqEA5to44F JlqK9OAaI43hYjyScobNhGGRy+NhclS6/1NEE4Rjv3GxLCsjaPbT5BBIbLOECEDtx+uz7nWouwt vsVJYjq8DRz5OXY7OG/pvDHPpf5hAPBs= X-Google-Smtp-Source: AGHT+IG41dTN501pIzJuiy+OSvvj1ww03hIBG+MIYsbUuy+t0ROGiZOe5z6ZgJLdnVIlZW998uUvlCykTzMwSyntmsQ= X-Received: by 2002:a05:6512:6c9:b0:51d:9291:6945 with SMTP id u9-20020a05651206c900b0051d92916945mr6992049lff.44.1714474674336; Tue, 30 Apr 2024 03:57:54 -0700 (PDT) MIME-Version: 1.0 References: In-Reply-To: From: =?UTF-8?Q?Torsten_F=C3=B6rtsch?= Date: Tue, 30 Apr 2024 12:57:43 +0200 Message-ID: Subject: Re: pg_notify contention To: Dimitris Zenios Cc: pgsql-general@lists.postgresql.org Content-Type: multipart/alternative; boundary="000000000000f0bc5a06174e3ddd" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000f0bc5a06174e3ddd Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable As part of the commit operation, Postgres inserts the notification into a queue. Naturally, that insert is guarded by a lock and that lock is released only at the very end of the commit operation. This effect gets much worse if you also configure synchronous replication because commit finishes only when the desired sync level has been achieved. An alternative solution could be to receive notification through the WAL stream. Use pg_revclogical to connect to a replication slot configured with wal2json. Then you can send notifications with pg_logical_emit_message() for instance which even allows you to send notifications from aborted transactions. Setup: SELECT * FROM pg_create_logical_replication_slot('w2j', 'wal2json'); Here is a sender: CREATE SCHEMA notify; CREATE OR REPLACE FUNCTION notify.send( transactional BOOLEAN, channel TEXT, message TEXT ) RETURNS PG_LSN AS $$ SELECT pg_logical_emit_message( transactional, 'NOTIFY_'||channel, jsonb_build_object( 'txn_start', now()::TEXT, 'pid', pg_backend_pid(), 'message', message )::TEXT ); $$ LANGUAGE sql VOLATILE; And something like this as a receiver / distributor: sudo -iu postgres /usr/lib/postgresql/$VERSION/bin/pg_recvlogical \ -d 'port=3D5432 dbname=3Dyour_database' \ -S w2j \ -f - \ -F 0 \ --start \ -o include-xids=3D1 \ -o include-timestamp=3D1 \ -o add-tables=3Dnotify.\* \ -o format-version=3D2 | jq 'select(.action=3D=3D"M" and (.prefix | test("^NOTIFY_"))) | . as $x | .content | fromjson + { "channel": ($x.prefix | .[7:]), "xid": $x.xid, "txn_commit": $x.timestamp, "transactional": $x.transactional }' Note, this is not a 100% replacement. The semantics are slightly different. You also need a replication slot which comes with its own maintenance cost. But maybe it's worth a try. Have fun, Torsten On Tue, Apr 30, 2024 at 12:05=E2=80=AFPM Dimitris Zenios wrote: > Hi, > > I am measuring a very simple case of pg_notify in an after update trigger= . > The trigger is the following: > > CREATE > OR REPLACE FUNCTION audit_event() RETURNS TRIGGER AS > $$ > BEGIN > PERFORM pg_notify('user', 'hello world'); > RETURN NULL; > END; > $$ > LANGUAGE plpgsql; > > and configured on the table with the following sql > > CREATE CONSTRAINT TRIGGER trigger_test AFTER UPDATE ON pgbench_accounts > FOR EACH ROW EXECUTE PROCEDURE audit_event() > > I am running two benchmarks. One with the pg_notify on the trigger enable= d > and one with the notify commented out. > > The command is the following: > pgbench -f /tmp/update_bench.sql -c 10 -j 10 -t 100 benchdb > > And the content of the update_bench.sql are the following > > \set aid random(1, 100000 * :scale) > \set delta random(-5000, 5000) > BEGIN; > UPDATE pgbench_accounts SET abalance =3D abalance + :delta WHERE aid =3D = :aid; > END; > > Results are > > - With pg_notify disabled > > pgbench (16.1) > starting vacuum...end. > transaction type: /tmp/update_bench.sql > scaling factor: 1 > query mode: simple > number of clients: 10 > number of threads: 10 > maximum number of tries: 1 > number of transactions per client: 100 > number of transactions actually processed: 1000/1000 > number of failed transactions: 0 (0.000%) > latency average =3D 11.744 ms > initial connection time =3D 15.616 ms > tps =3D 851.531991 (without initial connection time) > > - With pg_notify enabled > > pgbench (16.1) > starting vacuum...end. > transaction type: /tmp/update_bench.sql > scaling factor: 1 > query mode: simple > number of clients: 10 > number of threads: 10 > maximum number of tries: 1 > number of transactions per client: 100 > number of transactions actually processed: 1000/1000 > number of failed transactions: 0 (0.000%) > latency average =3D 56.927 ms > initial connection time =3D 11.182 ms > tps =3D 175.664989 (without initial connection time) > > There is a huge drop in TPS from 851 to 175. > > I also noticed that if I run the test with a single connection -c 1 then > the results are nearly identical which makes me assume that this is a > contention that occurs between multiple connections. > > Thanks > > --000000000000f0bc5a06174e3ddd Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
As part of the commit operation, Postgres inserts the noti= fication into a queue. Naturally, that insert is guarded by a lock and that= lock is released only at the very end of the commit operation. This effect= gets much worse if you also configure synchronous replication because comm= it finishes only when the desired sync level has been achieved.

An alternative solution could be to receive notification through th= e WAL stream. Use pg_revclogical to connect to a replication slot configure= d with wal2json. Then you can send notifications with=C2=A0pg_logical_emit_= message() for instance which even allows you to send notifications from abo= rted transactions.

Setup:
SELECT * FROM = pg_create_logical_replication_slot('w2j', 'wal2json');
<= /div>

Here is a sender:
CREATE SCHEMA notify;<= br>CREATE OR REPLACE FUNCTION notify.send(
=C2=A0 =C2=A0 transactional B= OOLEAN,
=C2=A0 =C2=A0 channel TEXT,
=C2=A0 =C2=A0 message TEXT
) R= ETURNS PG_LSN AS $$
=C2=A0 =C2=A0 SELECT pg_logical_emit_message(
=C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0transactional,
=C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'NOTIFY_'||chan= nel,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0jsonb_build_= object(
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0'txn_start', now()::TEXT,
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'pid', pg_backend_pid(),
= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0'm= essage', message
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0)::TEXT
=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0);
$$ LANGUAGE= sql VOLATILE;

And something like this as a re= ceiver / distributor:
sudo -iu postgres /usr/lib/postgresql/$VERS= ION/bin/pg_recvlogical \
=C2=A0 =C2=A0 =C2=A0-d 'port=3D5432 dbname= =3Dyour_database' \
=C2=A0 =C2=A0 =C2=A0-S w2j \
=C2=A0 =C2=A0 = =C2=A0-f - \
=C2=A0 =C2=A0 =C2=A0-F 0 \
=C2=A0 =C2=A0 =C2=A0--start \=
=C2=A0 =C2=A0 =C2=A0-o include-xids=3D1 \
=C2=A0 =C2=A0 =C2=A0-o inc= lude-timestamp=3D1 \
=C2=A0 =C2=A0 =C2=A0-o add-tables=3Dnotify.\* \
= =C2=A0 =C2=A0 =C2=A0-o format-version=3D2 |
jq 'select(.action=3D=3D= "M" and (.prefix | test("^NOTIFY_"))) |
=C2=A0 =C2= =A0 . as $x |
=C2=A0 =C2=A0 .content |
=C2=A0 =C2=A0 fromjson + {
= =C2=A0 =C2=A0 =C2=A0 =C2=A0 "channel": ($x.prefix | .[7:]),
= =C2=A0 =C2=A0 =C2=A0 =C2=A0 "xid": $x.xid,
=C2=A0 =C2=A0 =C2= =A0 =C2=A0 "txn_commit": $x.timestamp,
=C2=A0 =C2=A0 =C2=A0 = =C2=A0 "transactional": $x.transactional
=C2=A0 =C2=A0 }'<= br>

Note, this is not a 100% replacement. The sema= ntics are slightly different. You also need a replication slot which comes = with its own maintenance cost. But maybe it's worth a try.
Have fun,
Torsten

On Tue, Apr 30, 2024 at 12:05= =E2=80=AFPM Dimitris Zenios <dimitris.zenios@gmail.com> wrote:
Hi,=C2=A0

I = am measuring a very simple case of pg_notify in an after update trigger. Th= e trigger is the following:

CREATE
=C2=A0 =C2=A0 OR REPLACE FUNCT= ION audit_event() RETURNS TRIGGER AS
$$
BEGIN
=C2=A0 =C2=A0 PERFOR= M pg_notify('user', 'hello world');
=C2=A0 =C2=A0 RETURN= NULL;
END;
$$
=C2=A0 =C2=A0 LANGUAGE plpgsql;

and configured on the table with the following sql

CREATE CONSTRAINT TRIGGER trigger_test AFTER UPDATE ON pgbench_accou= nts FOR EACH ROW EXECUTE PROCEDURE audit_event()

I am running two be= nchmarks. One with the pg_notify on the trigger enabled and one with the no= tify commented out.

The command is the following:
pgbench -f /tm= p/update_bench.sql =C2=A0-c 10 -j 10 -t 100 benchdb

And the content = of the update_bench.sql are the following

\set aid= random(1, 100000 * :scale)
\set delta random(-5000, 5000)
BEGIN;
= UPDATE pgbench_accounts SET abalance =3D abalance + :delta WHERE aid =3D :a= id;
END;

Results are

- W= ith pg_notify disabled

pgbench (16.1)
starting = vacuum...end.
transaction type: /tmp/update_bench.sql
scaling factor:= 1
query mode: simple
number of clients: 10
number of threads: 10<= br>maximum number of tries: 1
number of transactions per client: 100
= number of transactions actually processed: 1000/1000
number of failed tr= ansactions: 0 (0.000%)
latency average =3D 11.744 ms
initial connecti= on time =3D 15.616 ms
tps =3D 851.531991 (without initial connection tim= e)

- With pg_notify enabled

pgbench (16.1)
starting vacuum...end.
transaction type: /tmp/u= pdate_bench.sql
scaling factor: 1
query mode: simple
number of cli= ents: 10
number of threads: 10
maximum number of tries: 1
number o= f transactions per client: 100
number of transactions actually processed= : 1000/1000
number of failed transactions: 0 (0.000%)
latency average= =3D 56.927 ms
initial connection time =3D 11.182 ms
tps =3D 175.6649= 89 (without initial connection time)

There is = a huge drop in TPS from 851 to 175.

I also noticed= that if I run the test with a single connection -c 1 then the results are = nearly identical which makes me assume that this is a contention that occur= s between multiple connections.

Thanks
<= br>
--000000000000f0bc5a06174e3ddd--