zlacker

[parent] [thread] 13 comments
1. vlmuto+(OP)[view] [source] 2021-12-17 23:57:08
I had thought about using postgres as a job queue before, but I couldn't figure out in my head how to make sure two processes didn't both take the same job. The "FOR UPDATE" and "SKIP LOCKED" were the keys to make this work in the article. Essentially, as far as I can tell, "SELECT FOR UPDATE" locks the rows as they're selected (locks are apparently visible outside the transaction), and "SKIP LOCKED" skips over rows for the select that other transactions have locked. Cool stuff.

The below article goes over some downsides to using postgres for a job queue: namely that the data model isn't optimized to find and send new jobs to large numbers of subscribers. Actual messaging systems like Kafka/NATS are better at this.

https://www.2ndquadrant.com/en/blog/what-is-select-skip-lock...

Also, there are things that the dedicated messaging systems give you that postgres won't, such as transparent error handling and retry logic. If your worker grabs a job from postgres and fails (in some kind of transient way such that the task should be retried), you'll have to implement the retry logic yourself. A streaming platform like Kafka or NATS will notice that you haven't acknowledged the message and deliver it to someone else.

This is something you could pretty easily implement yourself with something like a process that just scans over the "processing" jobs and looks for timeouts, resetting them as it goes. But there are probably a few of these little papercuts that Kafka-like (…Kafkaesque?) systems would handle for you.

So, I guess if you already have postgres set up in a reliable way, and there's no one around whose job it is to set up new production systems like Kafka, and you don't already have something like Kafka, and you only need basic job queue requirements or you're okay with implementing whatever you need on top of postgres yourself, and your incoming job rate is fewer than maybe a thousand per second, and you have fewer than maybe tens of consumers… postgres is probably a decent job queue.

The above paragraph seems snide but it probably does describe many people's environments.

replies(2): >>seanp2+94 >>mdavid+r9
2. seanp2+94[view] [source] 2021-12-18 00:27:38
>>vlmuto+(OP)
This. The article seems like "one weird trick that message queue companies HATE" as it's utilizing, as far as I understand, some SQL semantics in a very specific way to cobble together a way of achieving what other software is designed to do out of the box. It seems fine for a toy system, but I wouldn't stake the success of a real company on this approach.

One could also use DNS TXT as an RDBMS with some interesting fault tolerance and distribution schemes. That doesn't mean it's a good idea or the best way to solve a problem.

If you haven't seen them already, the Jepsen analyses are really worth a read: https://aphyr.com/posts/293-jepsen-kafka https://aphyr.com/posts/282-jepsen-postgres https://aphyr.com/tags/jepsen

replies(3): >>gnulin+HH >>lightn+ZQ1 >>slashd+r24
3. mdavid+r9[view] [source] 2021-12-18 01:09:50
>>vlmuto+(OP)
If a job fails, the connection to the database will timeout. Postgres will rollback the transaction, which releases row locks, freeing a job to be retried.

Of course, the database client and server together form a distributed system. The client might continue processing a job under the mistaken impression that it still holds the lock. Jobs still need to be idempotent, as with the streaming platforms.

replies(2): >>static+Ib >>runeks+ae1
◧◩
4. static+Ib[view] [source] [discussion] 2021-12-18 01:27:52
>>mdavid+r9
This assumes that you're creating a transaction per message, which I think is not advisable.
replies(3): >>shoo+bk >>akvadr+RP >>mdavid+pU
◧◩◪
5. shoo+bk[view] [source] [discussion] 2021-12-18 02:46:01
>>static+Ib
if you care to elaborate, i'm curious -- what alternative(s) would you recommend instead of one transaction per message, and why?
replies(2): >>charci+2P >>foo_ba+jfd
◧◩
6. gnulin+HH[view] [source] [discussion] 2021-12-18 07:27:02
>>seanp2+94
If you want to carry messages across the internet SQS et al is fine. But within the same system, e.g. in the same computer, or cluster, it makes much more sense to use something like this rather than something like SQS. Different tool, different job.
◧◩◪◨
7. charci+2P[view] [source] [discussion] 2021-12-18 08:57:43
>>shoo+bk
The article's approach is to have a column that stores if he job has been claimed and then handles dead jobs by just having a timeout.
◧◩◪
8. akvadr+RP[view] [source] [discussion] 2021-12-18 09:07:20
>>static+Ib
You can also create transactions for batches of messages if you want. Same as acking batches of messages in a queue system.
◧◩◪
9. mdavid+pU[view] [source] [discussion] 2021-12-18 10:06:15
>>static+Ib
Postgres implicitly creates a transaction for any query modifying data outside of one.

Transactions in MVCC are relatively cheap. The main resource of contention is a global txid that can disastrously wrap around if autovacuum is disabled. That process is responsible for a few other important tasks, like updating statistics for the query planner and maintaining BRIN indexes.

replies(1): >>static+v92
◧◩
10. runeks+ae1[view] [source] [discussion] 2021-12-18 14:02:24
>>mdavid+r9
> If a job fails, the connection to the database will timeout. Postgres will rollback the transaction, which releases row locks, freeing a job to be retried.

Only if you begin a transaction when your job starts that isn’t committed until you job finishes. As I understand it, this is not a good idea for long-running jobs, since you’ll have a long-running Postgres transaction. Am I missing something? The linked article doesn’t seem to use this approach.

Does the “FOR UPDATE” decrease the lock to just a single row, thus making it unproblematic?

◧◩
11. lightn+ZQ1[view] [source] [discussion] 2021-12-18 18:18:46
>>seanp2+94
Thank you.
◧◩◪◨
12. static+v92[view] [source] [discussion] 2021-12-18 20:13:27
>>mdavid+pU
> Postgres implicitly creates a transaction for any query modifying data outside of one.

I should clarify. I meant holding a transaction for the duration of the message.

◧◩
13. slashd+r24[view] [source] [discussion] 2021-12-19 16:39:13
>>seanp2+94
It's strange to see you suggesting this can't work for a "real" company. That's demeaning to the OPs company, which seems quite real. It's also odd that you're proposing replacing this with a distributed system and citing the Jepsen articles as support for it, when they prove the opposite. Distributed systems are hard. If you can avoid them and stay in the happy ACID town with Postrgres, indeed why not?
◧◩◪◨
14. foo_ba+jfd[view] [source] [discussion] 2021-12-22 15:41:43
>>shoo+bk
What if you want to store an error message+status with the job if it fails? It means you have to commit something no? What if you want to mark the message as being worked on and what process locked it and when they did for some kind of job queue monitoring stats?

The times I have done this, I end up with a workflow where the "select for update ... skipped lock" is used only to initially mark the job as "taken" so that other processes do not start working on it. That update is committed in one transaction and then the "work" is done in second transaction.

[go to top]