70 comments

[ 4.1 ms ] story [ 77.4 ms ] thread
Interesting. What if you just execute `NOTIFY` in its own connection outside of / after the transaction?
Rls and triggers dont scale either
I appreciate this post for two reasons:

* It gives an indication of how much you need to grow before this Postgres functionality starts being a blocker.

* Folks encountering this issue—and its confusing log line—in the future will be able to find this post and quickly understand the issue.

You had one problem with listen notify which was a fair one, but now you have a problem with http latency, network issues, DNS, retries, self-DDoS, etc.
Facebook’s wormhole seems like a better approach here - just tailing the MySQL bin log gets you commit safety for messages without running into this kind of locking behavior.
Sounds like one centralized Postgres instance, am I understanding that correctly? Wouldn’t meeting bots be very easy to parallelize across single-tenant instances?
LISTEN/NOTIFY isn’t just a lock-free trigger. It can jeopardize concurrency under load.

Features that seem harmless at small scale can break everything at large scale.

There’s lots of ways to invoke NOTIFY without doing it from with the transaction doing the work.

The post author is too focused on using NOTIFY in only one way.

This post fails to explain WHY they are sending a NOTIFY. Not much use telling us what doesn’t work without telling us the actual business goal.

It’s crazy to send a notify for every transaction, they should be debounced/grouped.

The point of a NOTIFY is to let some other system know something has changed. Don’t do it every transaction.

Postgres LISTEN/NOTIFY was a consistent pain point for Oban (background job processing framework for Elixir) for a while. The payload size limitations and connection pooler issues alone would cause subtle breakage.

It was particularly ironic because Elixir has a fantastic distribution and pubsub story thanks to distributed Erlang. That’s much more commonly used in apps now compared to 5 or so years ago when 40-50% of apps didn’t weren’t clustered. Thanks to the rise of platforms like Fly that made it easier, and the decline of Heroku that made it nearly impossible.

If I understood correctly, the global lock is so that notify events are emitted in order. Would it make sense to have a variant that doesn't make this ordering guarantee if you don't care about it, so that you can "notify" within transactions without locking the whole thing?
I'd be interested as to how dumb-ol' polling would compare here (the FOR UPDATE SKIP LOCKED method https://leontrolski.github.io/postgres-as-queue.html). One day I will set up some benchmarks as this is the kind of thing people argue about a lot without much evidence either way.

Wasn't aware of this AccessExclusiveLock behaviour - a reminder (and shameless plug 2) of how Postgres locks interact: https://leontrolski.github.io/pglockpy.html

Instead of LISTEN/NOTIFY you could listen to the wal / logical replication stream.

Or you could have a worker whose only job is to listen to the wal / logical replication stream and then NOTIFY. Being the only one to do so would not burden other transactions.

Or you could have a worker whose only job is to listen to the wal / logical replication stream and then publish on some non-PG pubsub system.

Out of curiosity: Would appreciate if others can share what other things like AccessExclusiveLock should postgres users beware of?

What I already know

- Unique indexes slow inserts since db has to acquire a full table lock

- Case statements in Where break query planner/optimizer and require full table scans

- Read only postgres functions should be marked as `STABLE PARALLEL SAFE`

It does scale. Just not to recall levels of traffic. Come on guys let's not rewrite everything in cassandra and rust now.
Transactional databases are not really the best tool for writing tons of (presumably) immutable records. Why are you using it for this? Why not Elastic?
Transactional databases are great, provided your write workload is low enough to fit on one server. If you have to scale up past that, you might have to use a different kind of database. But if transactions work for you, as they do for 99% of small-medium sites, they're amazing.

Multi-master transactional databases are an open area of research, as far as I'm aware, but read-only replication is a solved problem. Therefore your write traffic, including your transaction overhead, has to fit within one server's capacity, while your read traffic can scale horizontally as much as you like.

was hoping the solution was: we forked postgres.

cool writeup!

wow thanks for the heads up! no idea this was a thing.
RBDMS are not designed for write-heavy applications, they are designed for read-heavy analysis. Also, an RDBMS is not a message queue or an RPC transport.

I feel like somebody needs to write a book on system architecture for Gen Z that's just filled with memes. A funny cat pic telling people not to use the wrong tool will probably make more of an impact than an old fogey in a comment section wagging his finger.

RDBMS (you spelled it wrong) are good for many things. Postgres is a veritable swiss army knife - have a look through the manual, scroll through all the features you don't care about, be amazed it has so many.

RDBMS are an old fogey tool. It takes a really old fogey to suggest storing records at fixed byte intervals directly on the disk - is that your proposed alternative? Or perhaps you grew up in the microservices era and that's already become old fogey.

Got up to the TL;DR paragraph. This was a major red flag given the initial presentation of the discovery of a bottleneck:

''' When a NOTIFY query is issued during a transaction, it acquires a global lock on the entire database (ref) during the commit phase of the transaction, effectively serializing all commits. '''

Am I missing something - this seems like something the original authors of the system should have done due diligence on before implementing a write heavy work load.

My kneejerk reaction to the headline is ‘why would it?’.

It’s unsurprising to me that an AI company appears to have chosen exactly the wrong tool for the job.

Seriously people just layer shit with NATS for pubsub after persist and make sure there's a proper way to place a 'on restart recoonect' thing.
Am I supposed to be able to tell from these graphs that one was faster than the other? Because I sure can't.

What were the TPS numbers? What was the workload like? How big is the difference in %?

Honestly this article is ridiculous. Most people do not have tens of thousands of concurrent writers. And most applications out there are read heavy, not write. Which means you probably have read replicas distributing loads.

Use LISTEN/NOTIFY. You will get a lot of utility out of it before you’re anywhere close to these problems.

I think the title is stating this: "Postgres LISTEN/NOTIFY does not scale"

That means for moderate cases you do not even have to care about this. 99% of PostgreSQL instances out there are not big "scale".

As a sr. engineer is your responsibility to make a decision if you will build for "scale" from day zero or ignore this as you are mindful that this will not affect you until a certain point.

What a discovery , even Postgres itself doesn’t scale easy. There are so many solutions that are dedicated and cost you less.
LISTEN/NOTIFY was always a bit of a puzzler for me. Using it means you can't use things like pgbouncer/pgpool and there are so many other ways to do this, polling included. I guess it could be handy for an application where you know it won't scale and you just want a simple, one-dependency database.