Re: LISTEN/NOTIFY bug: VACUUM sets frozenxid past a xid in async queue
Jacques Combrink <jacques@quantsolutions.co.za>
From: Jacques Combrink <jacques@quantsolutions.co.za>
To: Matheus Alcantara <matheusssilv97@gmail.com>,
Masahiko Sawada <sawada.mshk@gmail.com>
Cc: Daniil Davydov <3danissimo@gmail.com>, Álvaro Herrera <alvherre@kurilemu.de>, Alexandra Wang <alexandra.wang.oss@gmail.com>, PostgreSQL Hackers <pgsql-hackers@postgresql.org>
Date: 2025-09-01T14:06:58Z
Lists: pgsql-hackers
Commits
Same data as JSON:
GET /api/v1/messages/:b64id/commits
the thread's linked commits as JSON, with link sources.
API reference →
-
Clear 'xid' in dummy async notify entries written to fill up pages
- 84f1bf4afa5e 14.21 landed
- 21a9014cf00a 15.16 landed
- 0e8eaa2181d4 16.12 landed
- d80d5f099502 17.8 landed
- 82fa6b78dba1 18.2 landed
- 0bdc777e8007 19 (unreleased) landed
-
Fix remaining race condition with CLOG truncation and LISTEN/NOTIFY
- c2e58c0711fe 14.21 landed
- 0c862646cf2a 15.16 landed
- 44e8c60be66c 16.12 landed
- c2682810ab7d 17.8 landed
- 7b069a1876e4 18.2 landed
- 797e9ea6e54b 19 (unreleased) landed
-
Fix bug where we truncated CLOG that was still needed by LISTEN/NOTIFY
- eba917d360e7 14.21 landed
- 1a469d7b5b7d 15.16 landed
- 053e1868b7ee 16.12 landed
- d02c03ddc5e3 17.8 landed
- 321ec54625fd 18.2 landed
- 8eeb4a0f7c06 19 (unreleased) landed
-
Escalate ERRORs during async notify processing to FATAL
- 7cb05dd2d198 14.21 landed
- b1da37de21d4 15.16 landed
- c1a5bde003b8 16.12 landed
- b821c92920f0 17.8 landed
- aab4a84bb070 18.2 landed
- 1b4699090eaf 19 (unreleased) landed
-
Limit the size of TID lists during parallel GIN build
- c98dffcb7c70 19 (unreleased) cited
I came across this discussion after we had this problem at least twice now at work. I read through the discussion to see if this is the problem we are experiencing. At first, I thought this was not exactly our problem, because of the way it was showed to be reproduced. With a long running transaction that is listening, and then doing all the other steps like like running through transactions until a second pg_xact file is created, and then vacuuming etc etc... But, I felt like we don't do this, so it cannot be the way we run into the problem. Specifically, having a listener as a long running transaction. So I set out to get it into the broken state, and here is how I can reproduce it without long running queries: 1. Create another database postgres=# CREATE DATABASE test; CREATE DATABASE 2. Create a listener on the postgres database. postgres=# LISTEN xx; LISTEN 3. Create notifies for the test database. Here I struggled to get a stuck notification in the queue (SELECT pg_notification_queue_usage();). It can happen with only a single notify from a psql connection, but I get a higher hit rate with the following: Create a notify.sql with the only contents being: ``` NOTIFY s, 'yappers'; ``` Then run this against the test database with pgbench. pgbench -n -c 80 -j 20 -t 1000 -f notify.sql test 4. Confirm that there is now something stuck in the queue: postgres=# SELECT pg_notification_queue_usage(); pg_notification_queue_usage ----------------------------- 9.5367431640625e-07 (1 row) If this still shows 0, then run step 3 again. 5. Consume xid's. I create a file consume.sql with the only contents being: ``` SELECT txid_current(); ``` Then: pgbench -n -c 80 -j 30 -t 100000 -f consume.sql postgres 6. Verify that a new file is created in the pg_xact folder. If not, just run the previous step again. 7. Run vacuum freeze. (Remember to allow connections to template0 beforehand with `ALTER DATABASE template0 WITH ALLOW_CONNECTIONS true;`) $ vacuumdb --all --freeze vacuumdb: vacuuming database "postgres" vacuumdb: vacuuming database "template0" vacuumdb: vacuuming database "template1" vacuumdb: vacuuming database "test" 8. Connect to the test database and try execute a listener. test=# LISTEN anything; ERROR: could not access status of transaction 81086 DETAIL: Could not open file "pg_xact/0000": No such file or directory. test=# Extra weirdness: In step 2 create a connection to the test database and LISTEN there on any channel, even the one you are notifying to: And you don't have to run anything on that connection, there is no backend_xmin on that connection, you don't start a transaction nothing, you just run `LISTEN something;` Then after step 7, as long as you don't close this connection, you will not get an error when you try to set up a listener on the test database, even on a new connection to the test database. What I'm saying is after step 7 you can have two connections. One to postgres database with active listener, and one to test database with active listener. As long as the postgres one is open the queue is stuck, ie `SELECT pg_notification_queue_usage();` returns non-zero. As long as the test database connection is open, new listeners do not experience the error. If you close the test database connection, but leave the postgres connection, from now on any listener you try to create on test database will error. If you close the postgres connection, the queue clears immediately and new LISTEN's on the test database will work. This means it is possible to get rid of the problem without restarting the postmaster if you can close connections until you close the one that "caused" the problem. Don't know if this is actually useful, but to me it seemed like everyone believed it to be impossible to recover without restarting, so it's something at least. TLDR: active listener on one database causes notify on another database to get stuck. At no point could I get a stuck notify if I don't have a listener on at least one other database than the one I am notifying on. See the Extra weirdness section. At no point do you need to have any other queries running, there is never an idle in transaction query needed for bad timing with the vacuum. I hope I explained everything well enough so that one of you smart people can find and fix the problem.