18 ms·
Choose Postgres queue technology
- xpe 3y ago> I hope to disimbue anyone of the notion that Postgres is an inferior queue technology. I offer this correction to the author: it is "disabuse" not "disimbue".
- mlyle 3y agoDisimbue is also a word with an appropriate meaning-- even if it's archaic (and no longer appears in most dictionaries) and pretentious.
- xpe 3y agoNo, I won't believe it until I see evidence. Did you find it in a credible source? Which one(s)? I've found nothing credible in Merriam Webster, Etymology Online, nor _any_ other I've searched. There is at least one low-quality ad-serving site that credits ChatGPT with a definition. I'm happy to be enlightened.
- deleted 3y ago[deleted]
- mlyle 3y agoe.g. Fallow's 1898 dictionary of synonyms and antonyms: https://imgur.com/a/65yLkw7 https://imgur.com/a/65yLkw7 https://archive.org/download/completedictiona00falluoft https://archive.org/download/completedictiona00falluoft There's many uses in British literature of the 1800's, and a whole lot of uses in academic literature of the 70's to 80's. https://i.imgur.com/BhMv2nF.png https://i.imgur.com/BhMv2nF.png "Disabuse" would fit into many of these slots, but not all. Only common use now is RPG jargon; imbuing something with an attribute is something role playing nerds talk about, and it really needs an antonym.
- xpe 3y agoWow. Thanks!
- zacksiri 3y agoDuring my tenure as CTO at a fintech company I built a banking engine using postgres backed queue system using Elixir / Phoenix. It's still in use today. The company processed large volumes of transactions and we were able to do things in real-time in terms of payments. Our system reached a point where I realized that we can scale almost infinitely just using a 2 tier architecture (Elixir / Phoenix / Oban and PostgreSQL) The industry standard for real-time was anything under 30s end-to-end. We hit many scaling limits as our platform grew. We worked on improving the system instead of looking at postgresql as a problem. We measured and measured and postgresql was never the problem when it came to scaling. Most of the problems that existed was mostly due to design decisions in the system or some technical debt that needed to be solved. When we solved those problems the system flew. There was one incident where our PostgreSQL instance CPU usage went up to 100% during peak hours and would cause problems in the system. We wondered why, there was a lot of debate around the topic, PostgreSQL doesn't scale, we need a larger instance, or move to a dedicated queue system etc... I didn't believe the hypothesis that PostgreSQL didn't scale. So I conducted a performance audit on the system myself I discovered one of the engineers forgot to add an index on one of the columns on a major table with > 60m records. There was a lot of back and forth between engineers that discussed whether we should add the index. I simply conducted a small experiment and PG analyze clearly showed a missing index in one of the key tables. After we added a single line of code and deployed the index, during peak hours DB CPU usage would not even exceed 20% we had to scale down our DB to save some $$. PostgreSQL backed queue system does work. I've been building distributed systems in large and small scales for over a decade. Before building systems with Elixir / Phoenix I also used to work with Rails where the default is as mentioned in this article is you just adopt Redis. While Redis does have additional overhead when it comes to management of the infrastructure, it also worked well. Having been through both I'd say a simple PostgreSQL backed solution is the clear winner when it came to not having to manage a 3rd dependency when you already have a complex system. There was no 3rd or 4th piece of the puzzle to theorize hypothesize or experiment with. You just had 2 pieces it is either your application or your database that's the problem. That simplicity saved us a lot of time for debate and doing experiments also squashed any dogmatic values and opinions that just didn't hold any water.
- ansc 3y ago
- dangoodmanUT 3y agoIt's interesting that Temporal is not addressed in this
- mianos 3y agoSkype used postgres as queue with a small plugin to process all their CDR many years ago. I have no idea if it used these days but it was 'web scale', 10 years ago. Just working, while people on the internet argued about using a database as a queue is an anti-pattern. Having transactions is quite handy. https://wiki.postgresql.org/wiki/SkyTools https://wiki.postgresql.org/wiki/SkyTools I did a few talks on this at Sydpy as I used it at work quite a bit. It's handy when you already have postgresql running well and supported. This said, I'd use a dedicated queue these days. Anything but RabbitMQ.
- reubenbond 3y ago> Anything but RabbitMQ. Would you mind elaborating on this? I'd be happy for others to chime in with their experiences/opinions, too.
- mianos 3y agoThe software works excellently in a development environment and performs well when running as a single instance. However, I encountered issues when scaling it up for high availability in a clustered setup. The system would fail inconsistently, with two masters consuming messages simultaneously, which wasn't ideal for my use case. Eventually, I switched to Kafka and haven't revisited the original solution since. It's worth noting that these issues might have been due to my improper configuration. Nevertheless, if the configuration process is fraught with pitfalls, that's problematic in itself. I've had these experiences more than once. Additionally, I found a critical race condition in the Python library, rendering it practically unusable for me. I submitted a bug report with a minimal example demonstrating the issue. I considered fixing it myself, but since using RabbitMQ wasn't crucial for my project, I switched to ZeroMQ, which didn't require a broker. The issue was acknowledged and fixed about a year later. At the time, I had to assume that nobody else was using the Python bindings. Three years ago, I worked on a project that used the software for a Celery queue. Messages would occasionally go missing, although this could have been a configuration issue on our part. Ultimately, we replaced it with a Redis queue (not the best practice, I admit) and didn't look back. This was for a lower-availability use case where a single instance of Redis sufficed.
- pyrophane 3y agoI don't have a problem with this approach at all, but I would argue that for many use cases a redis backed job library like Celery or Sidekiq might be even easier, especially if you are on a cloud provider that offers managed redis.
- jgavris 3y agoI wrote a hacky version of SKIP LOCKED using advisory locks and a recursive CTE before it was released for a job queue. It worked splendidly, along with the transactional semantics of a proper database. I’m surprised more systems don’t realize they need it.
- codeadict 3y agoThis is exactly what the Oban https://getoban.pro/ https://getoban.pro/ Elixir library uses and combining postgres plus actors for queues scales pretty great for 90% of the needs out there. I have used it at my last few jobs at pretty decent scale and would take it over 10 years using Celery to manage queues + supervisord, setting up RabbitMQ or Redis. Its so simple you only need Elixir and Postgres and not 3 or 4 infrastructure pieces to manage a queue.
- liminal 3y agoMy main issue with pretty much all queue approaches is that they don't work across platforms. They are built for one technology stack, be it Python/NodeJS/etc. This is fine if you've only got one stack, but in a microservices world it doesn't work where jobs can span multiple systems. You might be able to find some abandoned library that supports that queue tech on the other platforms you need, but now you've basically become a queue tech maintainer.
- jerrygenser 3y agoThere are queues like sqs and google pubsub where there is already official clients written for most popular languages. And they also have http API so you can use it in any language environment that can make http requests.
- mrkeen 3y ago> My main issue with pretty much all queue approaches is that they don't work across platforms. What technology stack are you working in which doesn't support postgres? > but in a microservices world it doesn't work where jobs can span multiple systems The point of the queue system is to be able to span said microservices. You can have an OCaml service picking up from one queue, processing, then writing into another queue. That queue could then be processed by a TypeScript service.
- Ericson2314 3y agoEven more fun than notify/listen is using the write-ahead log! :D
- Xeoncross 3y ago> As an industry, we’ve become absolutely obsessed with “scale”. I wish the industry was even half as concerned with efficiency as it was with scale. Bitcoin? Electron? 5MB web pages? 5/10/25GB downloads to run 20yr old CD-sized games on modern software?
- rconti 3y ago[flagged]
- menacingly 3y agoI've been thinking a lot lately about how much of tech, and life more broadly, is ruined by the pursuit of scale. Taking on problems you don't (and will never) have because some vanishingly small minority has experienced them is nuts. Over-engineering is as incorrect as under-engineering. The correctly sized, correctly complicated answer is what we're after.
- xpe 3y ago> Over-engineering is as incorrect as under-engineering. I know what you mean. The catch is figuring out 'the correctly sized' answer. Uncertainty is a huge factor here. I think a lot of projects that are called 'over-engineered' have the benefit of hindsight. At least some of such projects were rationally designed based on what people knew at the time. There is often significant uncertainty. When engineering resources are hard to acquire, the powers-that-be often want to 'get it right' the first time. Translated: over-engineer it, because we don't know when we'll get resources for the next iteration. I suppose my point is this: the next time somebody finds some 'over-engineered' code, it can be insightful to learn about the project history. (Example: some projects have overly optimistic prediction of usage. This perhaps suggests that pessimism is one antidote to over-engineering.)
- dikei 3y agoIIRC, LISTEN/NOTIFY needs to pin a PostgreSQL connection to the client, so you won't be able to use transaction-level pooling with it.
- rockwotj 3y agoI used a separate dedicated connection for listen that wasn’t in the pool. Notify is fine to be apart of a normal txn
- LennyWhiteJr 3y agoIn my Amazon team, we use PostgreSQL as a queue using skip-locked to implement transactional outbox pattern for our database inserts. People commenting 'just use a queue' are totally missing the need for transactional consistency. I agree with the author, it's an amazing tool and scales quite well.
- rockwotj 3y agoAll of shortwave.com is built on this concept. The super powerful bit here that is the mentioned is that enqueuing jobs is transactional with other normal transactions, so you don’t have to architect around using a different system for the queue and the rest of your data.
- TalktoCrystal 3y agoI think this may help. A control plane for PostGre and Kafka and Pulsar. https://github.com/apecloud/kubeblocks/blob/main/docs/release_notes/v0.6.0/v0.6.0.md https://github.com/apecloud/kubeblocks/blob/main/docs/releas...
- donatj 3y agoWe have our own queue, because it was easy, fun and has been exceedingly reliable above all else. Far moreso than other things we had tried. Cough Gearman cough SQS cough One endpoint accepts work to a named queue, writes it to a file in an XFS directory. Another locks a mutex, moves the file to an in progress directory and unlocks the mutex before passing the content to the reader. A third and final endpoint deletes the in progress job file. There is a configurable timeout, after which they end up at a dead letter box. I am simplifying only a little bit. It's a couple hundred lines of Go. The way this is set up means a message will only ever be handed to one worker. That simplifies things a lot. The workers ask for work when they want it, rather than being constantly listening. It took a little tuning but we process a couple billion events a day this way and it's been basically zero maintenance for almost 10 years. The wizards in devops even figured out a way to autoscale it.
- amerine 3y agoSounds neat. Do you have the go code anywhere for folks to poke at?
- donatj 3y agoNah, afraid not. I wish. We always wanted to open source it, but we got bought out by a big and very IP protective company before we got the chance.
- mr-karan 3y ago> The workers ask for work when they want it, rather than being constantly listening Can you elaborate more on this? How do the workers know when they have to process a new job? Also, am I right in assuming this is typically a single node setup only, as all the files are mounted on a non "share-able" XFS disk?
- donatj 3y agoThey ask for work after they finish the previous job (or jobs, they can ask for more than one). Each worker is a single process built just for one task. If there's no work for them there's a small timeout and they ask for more. Simple loop. It's all part of a library we built for building workers. For better or worse, it's all done over http. You are right, though, it is one XFS volume per queue instance. We just run multiple instances (EC2) on a load balancer. Each instance of the queue gets it's own set of workers though so the workers know the right server to report done to. We want a way to have a single pool of workers, rather than a pool per queue instance, and have them talk to the load balancer rather than directly, but we haven't come up with a reasonable way to do that.
- jasongi 3y agoI’m not against using Postgres for this. But I am against the rolling your own distributed task queue. It always seems like a simple task but snowballs in complexity. Any gains you get simplifying your stack will be wiped out by the fact that things like Celery (for example) don’t support using Postgres as a broker so now you have to do your own DIY Celery instead of say, just using Celery with the SQS broker (which… since we’ve established scale isn’t being considered here, SQS costs shouldn’t be an issue either). Anyone know if there are Celery or Celery-like tools that support Postgres as a broker? As a side-note, if you want a simple no-frills task scheduler ap-scheduler is a dead simple option. It’s even more limited than the solution described in OP (you can only run one worker so it’s not distributed at all) but often it is all you need especially for toy projects.
- welder 3y ago> I’m not against using Postgres for this. But I am against the rolling your own distributed task queue. Good thing I didn't listen to your advice... my DIY background task queue saved my website when Celery couldn't scale. Why are you against rolling your own task queue besides it seeming complicated? https://wakatime.com/blog/56-building-a-background-task-queue-in-python https://wakatime.com/blog/56-building-a-background-task-queu...
- Rapzid 3y agoNot sure this particular article brings anything new to the table, but it's nice to spread the word. Been meaning to build an Orleans stream provider for Postgres.. I believe that's the main missing component that would allow everything to "JustWork" with Postgres until you outgrow it.
- AlexITC 3y agoI have been involved in a few projects using postgres-bakend queues for a few years, scale hasn't been a problem so far. On the other hand, I have done a few experiments with postgres LISTEN/NOTIFY, while the feature seems nice at first glance, I concluded that it wasn't worth it for our use cases, maybe it is different in other languages but in the JVM, you have to allocate 1-thread for polling these results, which also keeps a connection busy. What I ended up doing is leveraging akka-stream to stream the queue data directly from the db, which makes it simple to define throttling rules, this is super simple and effective.
- debuggerpk 3y agoon a sidenote, what is the theme on this blog?
- s17n 3y agoI don't usually downvote posts, but this article is just garbage - a rant about "the cargo cult of scale" but no actual arguments as to why Postgres is better than redis or sqs or anything else. The main reason that people don't use postgres for this kind of thing isn't some kind of misguided obsession with scalability, it's because postgres is way more complicated to deploy / manage and harder to use for application developers than any of the other options.
- qaq 3y agoYep but in many apps postgres is already there so redis or sqs has to be managed in addition to postgres.
- mlyle 3y agoThe argument is stated-- you shouldn't adopt too many technologies. The author merely says that if you're running postgres already, you should seriously consider it for your queue. They also point out that many of the arguments against psql-as-queue are flawed.
- figassis 3y agoThe way I implement my queues (usually as part of my monolith application) is as go routines. Each instance of the app launches with a unique id, and also a role. It can be a worker or the app itself. So when the app generates a queue item, it simply adds it to a table as pending. A worker will then, via transaction, update a set of items to add its instance id as well as an expiration for this lock. If that succeeds, no other worker will pull a queue item with a non null id or with an id different that it’s instance id that is not expired. Worker can then start processing and update item status accordingly. If it crashes, another worker will just repeat the process after the lock expires. The code that does this is maybe 100 lines at most. It’s very effective especially if you deploy your app in kubernetes where you can expect instances to be ephemeral. It’s one of the components of my apps that has never needed any updates since I first wrote it circa 2017.
- perfmode 3y agoHow are workers notified of new work? Or do they poll and sleep?
- figassis 3y agoThey poll. It's easy however to add pub/sub via redis. Queues are categorized in frequency. So for example a notification queue might process every minute or 5 minutes, a reconciliation queue might run every 12 hours, or have a fixed time, like daily at 2am and 6pm, etc. Each queue runs on its own goroutine on its onwn schedule, and every worker runs all queues. I can also adjust how many items a worker can pull each time so queues do not get backed up. Say there is some criteria an account needs to meet to be eligible for some feature. If that criteria requires some expensive db queries, I can run a daily job that pulls 100 accounts each time to check that they meet the criteria. But if the instances can process more, I can configure it to pull 1000 accounts. Or I can add 9 workers and each pulls 100 accounts. Usually what I do is pull the records that were updated least recently (as in they should be ahead of the queue). So if a previous worker locked the oldest X records, the second worker will pull the next batch bc the condition will exclude the previously updated (locked) records. There's a lot of flexibility you can add with just these controls: schedule, frequency, batch size, number of workers.
- ezekiel68 3y agoI hate to point out something uncomfortable, but this guy's combative writing style and penchant for abstract art reminds me of the articles by a certain infamous creator of the ReiserFS file system for linux.
- foreigner 3y agoI'm in the market for a Postgres-backed queue system with client libraries in NodeJS _and_ Python. Clients in both languages need to be able to read and write from the queue. Can anybody suggest one?
- chuckhend 3y agoPGMQ does not require a client library, https://github.com/tembo-io/pgmq https://github.com/tembo-io/pgmq so long as your language of choice can run SQL. All the functions live in Postgres, and you just call them with SQL statement. Very similar feel and semantics to SQS.
- foreigner 3y agoThank you!
- fasteo 3y agoOver the years I have used beanstalkd for all things queue. Working flawlessly every single time. This is an output for our oldest instance (legacy system running Ubuntu 12) Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. stats OK 952 --- current-jobs-urgent: 0 current-jobs-ready: 765 current-jobs-reserved: 2 current-jobs-delayed: 946 current-jobs-buried: 0 cmd-put: 1188640739 cmd-peek: 2718986 cmd-peek-ready: 5052 cmd-peek-delayed: 797 cmd-peek-buried: 797 cmd-reserve: 0 cmd-reserve-with-timeout: 3245006799 cmd-delete: 1188639093 cmd-release: 43276760 cmd-use: 1137988211 cmd-watch: 166122 cmd-ignore: 166077 cmd-bury: 0 cmd-kick: 1 cmd-touch: 0 cmd-stats: 1260 cmd-stats-job: 43134828 cmd-stats-tube: 53942209 cmd-list-tubes: 3251625 cmd-list-tube-used: 0 cmd-list-tubes-watched: 0 cmd-pause-tube: 988 job-timeouts: 14084 total-jobs: 1188640739 max-job-size: 1048576 current-tubes: 44 current-connections: 63 current-producers: 20 current-workers: 47 current-waiting: 41 total-connections: 14996583 pid: 3959 version: 1.4.6 rusage-utime: 170303.331293 rusage-stime: 399435.543161 uptime: 321658179 binlog-oldest-index: 90539 binlog-current-index: 90983 binlog-max-size: 10485760
- deleted 3y ago[deleted]
- alexbezhan 3y agoI'm using SQLite as my main database. Is it a bad idea to build queues on top of SQLite?
- dunno7456 3y agoNo
- andrewstuart 3y agoSKIP LOCKED guarantees that a row can only be read once. This is critical for queueing systems. SQLite does not support SKIP LOCKED.
- winternewt 3y agoThe official JDBC driver for PostgreSQL provides no way for the database to push events to the client. So you need a dedicated connection to continuously poll the database to see if there are any events available. This seems half-baked and does not give me the warm and fuzzy feeling I crave when making architectural choices. Not to mention, it causes undesirable latency in event delivery unless I flood the database with queries.
- afhammad 3y agoThere are a few mentions of Oban [1] here. Most people don't realise that Oban in fact uses SKIP LOCKED [2] as well. Oban's been great, especially if you pay for Web UI and Pro for the extra features [3] The main issue we've noticed though is that due to its simple fetching mechanism using locks, jobs aren't distributed evenly across your workers due to the greedy `SELECT...LIMIT X` [2] If you have long running and/or resource intensive jobs, this can be problematic. Lets say you have 3 workers with a local limit of 10 per node. If there are only 10 jobs in the queue, the first node to fetch available jobs will grab and lock all 10, with the other 2 nodes sitting idle. [1] https://github.com/sorentwo/oban https://github.com/sorentwo/oban [2] https://github.com/sorentwo/oban/blob/main/lib/oban/engines/basic.ex#L86-L96 https://github.com/sorentwo/oban/blob/main/lib/oban/engines/... [3] https://getoban.pro/#feature-comparison https://getoban.pro/#feature-comparison
- ritzaco 3y agoI often see the "engineers copy FAANG infrastructure because they want to be cool, even though their needs are completely different" take as a kind of attack on engineers. But I think a lot of it is also about knowledge and documentation. If I want to copy FAANG or another startup, and set up an infinitely scalable queue-based architecture, I can find dozens of high quality guides, tutorials, white papers etc, showing me exactly how to do it. Yes maintenance is higher, but I can get set up with redis, SQS, any of the 'scalable' solutions within a few hours of copy-pasting commands and code and configuration from a reputable source. If I want to use NOTIFY in postgres? I googled "SQLALchemy notify listen postgres" and I find a few unanswered stackoverflow questions and a github gist that has some code but no context. I would honestly love to use this approach for a side project, but I don't have 2-3 days to figure it out on my own. The direct choice for me might seem to be * simple, but not scalable (ie just use postgres) * complex, but scalable (ie redis, sqs, whatever) and then it's a tradeoff, and the argument goes that I am blinded by cool tech and FAANG and I'm choosing complex but scalable, even though I don't need scalable. But taking into account guides and other resources, the choice for me is actually * complex and not scalable (this, because I don't know how to implement it and I can't predict what pitfalls I might face if I try) * simple and scalable (what everyone actually does) and that makes the engineer's choice to follow faang look a lot more reasonable.
- natmaka 3y agoAnother point is: you don't need scalable now, but may (or even hope) to need it later, and you know that when you will need it you probably won't have time to invest into migrating this component. Also: you may think that you may one day want to be hired by a FAANG.
- mkl95 3y agoHow relevant is it to be hired by a FAANG? I have some experience with "web scale" systems, but I tend to reject FAANG recruiters because Leetcode makes me want to become an apple farmer (no pun).
- 3y ago
- polyrand 3y agoSome time ago, I wrote a queue using SQLite[0]. Instead of SKIP LOCKED, you can use RETURNING to lock-and-read a message and ensure only one worker is going to pick it up: UPDATE ... SET status = 'locked' ... RETURNING message_id Or you can just use an IMMEDIATE transaction, SELECT the next message ID to retrieve, and UPDATE the row. On top of that, if you want to be extra safe, you can do: UPDATE Queue SET status = 'locked' WHERE status = 'ready' AND message_id = '....' To make sure you that the message you are trying to retrieve hasn't been locked already by another worker. [0]: https://github.com/litements/litequeue/ https://github.com/litements/litequeue/ [1]: https://github.com/litements/litequeue/blob/3fece7aa9e9a31e4bb8c332c214231198cfe7a74/litequeue.py#L264-L344 https://github.com/litements/litequeue/blob/3fece7aa9e9a31e4...
- sgarland 3y agoI just want to commend OP - if they’re here - for choosing an int64 for job IDs, and MD5 for hashing the payload in Neoq, the job library linked [0] from the article. Especially given the emphasis on YAGNI, you don’t need a UUID primary key, and all of its problems they bring for B+trees (that thing RDBMS is built on), nor do you need the collision resistance of SHA256 - the odds of you creating a dupe job hash with MD5 are vanishingly small. As to the actual topic, it’s fine IFF you carefully monitor for accumulating dead tuples, and adjust auto-vacuum for that table as necessary. While not something you’d run into at the start, at a modest scale you may start to see issues. May. You may also opt to switch to Redis or something else before that point anyway. EDIT: if you choose ULID, UUIDv7, or some other k-sortable key, the problem isn’t nearly as bad, but you still don’t need it in this situation. Save yourself 8 bytes per key. [0]: https://github.com/acaloiaro/neoq https://github.com/acaloiaro/neoq
- acaloiar 3y agoThanks! I'd like to believe that those decisions were incredibly thoughtful, but they're largely Bob Rossian happy accidents.
- wholesomepotato 3y agomd5? blake3 is both faster and cryptographically secure, and not a novelty at this point.
- gazpacho 3y agoI feel like one of the problems with using Postgres as a queue is that it’s hard to get started. There’s a lot you need to know. Getting started with something like Pub/Sun on GCP is much easier for many developers. I’ve experimented with making this easier via libraries that provide high-level APIs for using Postgres as a queue and manage the schemas, listen/notify, etc for you: https://github.com/adriangb/pgjobq https://github.com/adriangb/pgjobq
- KronisLV 3y ago> For example, this Hacker News comment stated that using Postgres this way is “hacky” and the commenter received no pushback. I found the comment to be load of BS and straw man arguments. This thinking seems to be “the prevailing wisdom” of the industry – if you want to talk about queue technology in public, it better not be a relational database. I don't think that there's anything wrong with using a database as a queue, however, I think that there probably could have been better ways to get across the idea, rather than just dismissing an honest opinion as BS. I don't necessarily agree with all of what was said there, but at the same time I can see why those arguments would be reasonable: https://news.ycombinator.com/item?id=20022572 https://news.ycombinator.com/item?id=20022572 For example: > Because it is hacky from the perspective of a distributed system architecture. It's coupling 2 components that probably ought not be coupled because it's perceived as "convenient" to do so. The idea that your system's control and data planes are tightly coupled is a dangerous one if your system grows quickly. To me, this makes perfect sense, if you're using the same database instance for the typical RDBMS use case AND also for the queue. Then again, that could be avoided by having separate database instances/clusters and treating those as separate services: prod-app-database and prod-queue-database. That said, using something like RabbitMQ or another specialized queue solution might also have the additional benefit of bunches of tutorials and libraries, as well as other resources available, which is pretty much the case whenever you have a well known and a more niche technology, even when the latter might be in some ways better! After all, there is a reason why many would use Sidekiq, resque, rq, Hangfire, asynq and other libraries that were mentioned and already have lots of content around them. Though whether the inherent complexity of the system or the complexity of your code that's needed to integrate with it is more important, is probably highly situational.
- oooyay 3y agoA couple counter points: 1. Many queueing solutions struggle with at most once delivery. I've used a queue I built on top of RDBMS to solve this before. Ironically, when I was a junior we used a key/value store to lock certain messages for at most once delivery, which doubly compounds my next point. 2. A queue is more infrastructure to manage, and hard to troubleshoot infrastructure at that. 3. Many queueing softwares struggle with deserialization into native types which makes the code on either end unnecessarily obtuse. Using RDBMS queues makes your serialization as good as your drivers.
- ekidd 3y agoI've built three distributed job systems at this point. A handy rule of thumb which I have promoted for years is "build for 10x your current scale." If you need to handle 70 requests/second, design for 700. If you need to handle 20 servers running batch jobs, design for 200 servers. If you're in a startup that grows 100% per year, you'll be at 8x scale in 3 years. So you'll have time to rewrite as you grow! Out of three job systems I built, the first one tried to avoid SQL for "scalability." Then we hit a bunch of edge cases that needed transactional integrity, and we were in a world of pain. My two more recent distributed job systems use PostgreSQL as a coordinator. They're literally built around SELECT FOR UPDATE SKIP LOCKED. One of them routinely controls 350 workers and the other does elaborate prioritization for thousands of jobs. Both of them will continue to run just fine until they're earning millions of dollars a year—for example, the one controlling 350 workers should scale to about 2,000 CPUs with a little work. Hyperscale technologies are fairly cheap right up until you discover you need transactions. At that point, faking transactional semantics on top of an eventually consistent data store becomes an engineering nightmare. So sit down, and do the math. If your company was earning $100 million/year, how big would your distributed system need to be? Can you easily get a PostgreSQL instance big enough to handle that load? (Or could you just shard per client?) If so, strongly consider using PostgreSQL. It makes a hundred things trivial.
- nerpderp82 3y agoDesign (and test) for 10x your current scale, build for what you need now. And the system has to be able to handle peak loads, and if you don't know what those are build in a safety margin or a way to shed or defer work if you need to. Everything is a tradeoff, optimize for the things that need optimizing, and determining what those are is the hallmark of a good engineer.
- insanitybit 3y agoI've found the same. You should understand what your 10x / 100x growth solution would look like (assuming that that's relevant - obviously if you have no intent to hit that scale, don't bother). Build your system to handle your 1-1.5x, maybe 10x scale, and make sure you're not blocking the 10-100x solutions by doing so.
- n_time 3y agoCould it be people are choosing over-engineered solutions because AWS has bad documentation and that's what the solutions architects tell them to do? Could it be because microservices and so-called "server-less" have been sold as cost-saving measures that increase the business' flexibility and decrease capital investment? When... in reality a single deployed Docker container is way more manageable than a distributed system constructed with "lambdas" and requires fewer engineers in the long run? What I'm trying to say is that FAANG cargo-culting is only part of why developers choose to build solutions that scale larger than they need. Another large part is the cloud development ecosystem writ-large and the consulting culture that has built up around it.
- vb-8448 3y agoDid you ever hear about "no one got fired because he bought IBM/Oracle/Microsoft" ? It's the same exact thing: most of the people do what is considered a "best practice" just to avoid any type of annoyance when something will break.
- welder 3y agoWhen I wrote my own background task queue I looked at Postgres, because it was already in use in the stack. Postgres would work for a simple queue, but supporting queue priorities, delayed/eta tasks, and broadcast tasks was too complicated. I decided on Redis, and it's scaled very well over the last year: https://github.com/wakatime/wakaq https://github.com/wakatime/wakaq We currently process ~20 million tasks per day, and I don't have to worry about running VACUUM on my queue ;)
- jack_squat 3y agoI've done the Postgres skip locked thing at least three times and I'm currently doing it, but IMO it is actually more maintenance and overhead, not less -- at least when compared with the queues made available by the major cloud providers. Compared with Pubsub or SQS you need to handle, * Metrics, monitoring, alarming on depth, message age * Autoscaling on your custom metrics * Managing retries, dead lettering, backoff * Managing the DB workload: it's update-heavy and may easily be more intensive than the rest of your app. You may need to repeatedly scale your tiny, startup-scale DB, causing customer disruptions, because of your homemade queue. The arguments for it are either avoidance of lock-in, or the hand-wavy one in this article/many comments: "we're a small startup, we need to minimize complexity, and make things as simple as possible, maintenance will kill us!". Lock-in makes sense (though other queue alternatives should still be considered), but the latter argument gets used to justify all kinds of harebrained, superficially-simple but actually-complicated engineering schemes. I generally put the PG skip locked approach in that bucket, particularly when the alternative on hand is Pubsub or SQS. If it's between a Postgres table and ActiveMQ I might feel more conflicted.
- 5Qn8mNbc2FNCiVV 3y agoThat's what Temporal is for
- insanitybit 3y agoSomething that always bothers me about "Use Postgres as a queue" (something I would suggest, even) is that there are shockingly few people publishing numbers around this. How am I supposed to know what scale I can handle with this solution? I've seen so, so few benchmarks, and maybe no recent benchmarks - a problem since performance has significantly changed across versions.
- nijave 3y agoIf you're on a cloud provider, I'd say just use their offering. For small/medium amounts of messages (single digit millions a day) the cost will be trivial. A lot of frameworks already have queue/job libraries with adapters (so you're not really locked in) and cloud providers are highly scalable and fault tolerant. It seems silly to try to build into Postgres something that is already cheap and readily available unless you find yourself in a situation where standing up additional infra is hard (embedded, certain on premise)
- vb-8448 3y ago> If you're on a cloud provider, I'd say just use their offering. For small/medium amounts of messages (single digit millions a day) the cost will be trivial. It's a good compromise but not suitable for every use case. The thing I really don't like is that you need to be connected to the cloud even for local development and test.
- afandian 3y agoYou don’t. SQS works fine in Localstack.
- bblommers 3y agoIn addition to Localstack, Moto can help speedup the dev/test cycle as well. If you're architecture is on AWS, you're most likely using either one of those solutions.
- nijave 3y agoAWS SDK can stub responses. In general, I usually recommend creating local/in memory implementations of infrastructure so you don't end up running a gigantic stack locally. You can write tests against the real integration that selectively run against a real environment and write contract tests that cover the real implementation and your local stubs (or maybe even interfaces is strongly typed languages are sufficient). Some languages/frameworks have this built in (a lot of ORMs do this out of the box). A lot of frameworks also have facilities for conditionally wiring in different implementations based on runtime config.
- ComodoHacker 3y ago>Postgres queue tech is a thing of beauty, but far from mainstream. A small nitpick. FOR UPDATE SKIP LOCKED was mainstream (in a sense) way before it was copied from Oracle to Postgres. It's used under the hood in most non-trivial Oracle deployments. Like for replication or refreshing materialized views in parallel.
- snicker7 3y agoI wonder how this is is actually simpler than an a managed queue, say SQS.
- HeavyStorm 3y agoTerrible idea. Using a database for queues means using a file format that's not optimized for this, so, unless you don't delete the rows (effectively having a always-growing table) you'll run into performance issues. Also the index for a processed flag will cause contention. I'm certain you can work around those issues, but why if you can use a proper queue?
- aduffy 3y agoFor several projects I’ve opted for the even dumber approach, that works out of the box with every ORM/Query DSL framework in every language: using a normal table with SELECT FOR UPDATE SKIP LOCKED https://www.pgcasts.com/episodes/the-skip-locked-feature-in-postgres-9-5 https://www.pgcasts.com/episodes/the-skip-locked-feature-in-... It’s not “web scale” but it easily extends to several thousand background jobs in my experience
- Klonoar 3y agoI’ve used this for a queue with millions of items and some indexes. It “just works”.
- johnthescott 3y agoskip lock works well on many Ks/sec message queues.
- boruto 3y agoNot if you have groups in those thousands and want to maintain order on those groups
- Karrot_Kream 3y agoIn my experience, a queue system is the worst thing to find out isn't scaling properly because once you find out your queue system can't architecturally scale, there's no easy fix to avoid data loss. You talk about "several thousand background jobs" but generally, queues are measured in terms of Little's Law [1] for which you need to be talking about rates; according to Little's Law namely average task enqueue rate per second and average task duration per second. Raw numbers don't mean that much. In the beginning you can do a naive UPDATE ... SET, which locks way too much. While you can make your locking more efficient, doing UPDATE with SELECT subqueries for dequeues and SELECT FOR UPDATE SKIP LOCKED, eventually your dequeue queries will throttle each other's locks and your queue will grind to a halt. You can try to disable enqueues at that point to give your DB more breathing room but you'll have data loss on lost enqueues and it'll mostly be your dequeues locking each other out. You can try very quickly to shard out your task tables to avoid locking and that may work but it's brittle to roll out across multiple workers and can result in data loss. You can of course drop a random subset of tasks but this will cause data loss. Any of these options is not only highly stressful in a production scenario but also very hard to recover from without a ground-up rearchitecture. Is this kind of a nightmare production scenario really worth choosing Boring Technology? Maybe if you have a handful of customers and are confident you'll be working at tens of tasks per second forever. Having been in the hot seat for one of these I will always choose a real queue technology over a database when possible. [1]: https://en.wikipedia.org/wiki/Little%27s_law https://en.wikipedia.org/wiki/Little%27s_law
- andrelaszlo 3y agoOne issue with Redis as a queue backend seems to be that persistence is quite expensive, at least for managed Redis instances. Using PG seems like it could be much cheaper, especially if you already have an instance with room to spare. I thought it was an interesting article, and I'd love to hear more from people using PG for queues in production (my intuition would say you'd get a lot of table bloat and/or vacuum latency, but I haven't tested it myself), but when it comes to the conclusion - "choosing boring technology should be one’s default choice" - I can't think of anything more boring (in a good sense, mostly) than Sidekiq + Redis for a Rails app.
- pqdbr 3y agoSame here. Sidekiq + Rails in a Rails app is a powerhouse, simple and reliable, but I do worry about losing the queue in Redis. It would be great to have that in Postgres as well.
- jarofgreen 3y ago> I’d love to see more neoq-like libraries for languages other than Go. Python has Celery, but maybe the author is looking for more choice between brokers. https://docs.celeryq.dev/en/stable/index.html https://docs.celeryq.dev/en/stable/index.html
- welder 3y agoCelery is crap. Full of bugs. https://wakatime.com/blog/56-building-a-background-task-queue-in-python https://wakatime.com/blog/56-building-a-background-task-queu...
- nicoburns 3y agoFor running queues on Postgres with Node.js backend(s), I highly recommend https://github.com/timgit/pg-boss https://github.com/timgit/pg-boss. I'm sure it has it scale limits. But if you're one of the 90% of the apps that never needs any kind of scale that a modern server can't easily handle then it's fantastic. You get transactional queueing of jobs, and it automatically handles syncing across multiple job processing servers using Postgres locks.
- levkk 3y agoRunning this exact implementation with 47M jobs processed and counting. SKIP LOCKED is great for VACUUM, and having durable storage with indexes make otherwise expensive patterns like delayed jobs, retries, status updates, "at least once", etc. really easy to implement.
- dlisboa 3y agoDo you have some idea of how many jobs per minute or hour do you have? Just want to compare with what we have on Redis at work. Do you also have any idea on the concurrency? How many workers you have pulling from Postgres. I’ve used this approach before (ages ago) when Redis wasn’t even a thing, though not at high throughout requirements.
- cle 3y agoI recently did the same thing but without LISTEN/NOTIFY and with a partial index. Pushed about 700 jobs/sec with 1000 workers (via pgbouncer).
- devoutsalsa 3y agoI'm sure Redis is much faster than an RDBMS w/ all the ACID features turned on. The biggest concern I always have with Redis is simply overwhelming the in-memory storage limits when someone wants to do process a large number of good-sized messages at an inconvenient time. #tradeoffs
- dilyevsky 3y agoI’ve seen it used for up to 1000 jobs per second with concurrency of 3-12
- pphysch 3y agoAnother point is that task queue technology is highly fungible. There's nothing stopping you from starting with cron, adding in Postgres/Redis, then graduating to Kafka or something as need arises. And running all three in parallel, with different jobs in each. I would be surprised if the average Kafka shop didn't also have a bunch of random cron jobs doing things that could be implemented on Kafka or vice versa. At some point you may want to refactor things to reduce tech debt, but it really is a "and" rather than "or" decision.
- Scubabear68 3y agoI agree with all of this except for the part about “cron”. Cron jobs in my experience quickly become hard to manage and effectively invisible over time. Use almost anything else to manage job scheduling….
- xdanger 3y agoI do enjoy using https://github.com/graphile/worker https://github.com/graphile/worker for my postgresql queuing needs. Very scalable, the next release 0.14 even more so, and easy to use.
- mildavw 3y agoFor Rails apps, you can do this using the ActiveJob interface via https://github.com/bensheldon/good_job https://github.com/bensheldon/good_job Had it in production for about a quarter and it’s worked well.
- paulddraper 3y agoUSE. ADVISORY. LOCKS. Do not use SKIP LOCKED unless it is a toy/low throughout. Row locks require transactions and disk writes. Advisory locks require neither. (However, you do have to stay inside the configurable memory budget.)
- ukd1 3y agoMaybe it's changed in the last year or so, but from benchmarking and writing / running queue software for Postgres - SKIP LOCKED was/is significantly faster. Is that different for MySQL?
- mikeocool 3y agoPretty common advice for scaling Postgres is to deploy pgbouncer in transaction mode in front of it to handle connection pooling. Advisory locks don’t work in this setup (and will start behaving in strange ways if you do try to use them.) Something to consider if you go this route.
- paulddraper 3y agoDepends. That has more to do with how your're scaling application servers.
- ris 3y agoTransaction-scoped advisory locks are very much a thing too.
- sorentwo 3y agoTo do anything safe and interesting you’ll need transactions. Using SKIP LOCKED won’t be your bottleneck, your application will. Job queues are about side effects and the rest of your application needs to keep up. Oban is able to run over 1m jobs a minute, and the ultimate bottleneck is throttling in application code to prevent thrashing the database: https://getoban.pro/articles/one-million-jobs-a-minute-with-oban https://getoban.pro/articles/one-million-jobs-a-minute-with-...
- 3y ago
- 5id 3y agoOne of the biggest benefits imo of using Postgres as your application queue, is that any async work you schedule benefits from transactionality. That is, say you have a relatively complex backend mutation that needs to schedule some async work (eg sending an email after signup). With a Postgres queue, if you insert the job to send the email and then in a later part of the transaction, something fails and the transaction rollbacks, the email is never queued to be sent.
- vb-8448 3y ago> With a Postgres queue, if you insert the job to send the email and then in a later part of the transaction, something fails and the transaction rollbacks, the email is never queued to be sent. An option could be use a second connection and a separate transaction to insert data in the queue table.
- robertlagrant 3y agoI agree - having to tell a database that something was processed, and fire off a message into RabbitMQ, say, is never 100% transactional. This would be my top reason to use this approach. > With a Postgres queue, if you insert the job to send the email and then in a later part of the transaction, something fails and the transaction rollbacks, the email is never queued to be sent. This is true - definitely worth isolating what should be totally separate database code into different transactions. On the other hand, if your user is not created in the DB, you might not want your signup email. Just depends on the situation.
- matsemann 3y agoGood point. We alleviate that a bit by scheduling our queue adds to not run until after commit. But then we still have some unsafety, and if connection to rabbit is down we're in trouble.
- theptip 3y agoWorth being clear that bridging to another non-idempotent system necessarily requires you to pick at-least-once or at-most-once semantics. So for emails, if you fail awaiting confirmation of your email you still need to pick between failing your transaction and potentially duplicating the email, or continuing and potentially dropping it. The big advantage is for code paths which async modify your DB; these can be done fully transactionally with exactly-once semantics since the Job consumption and DB update are in the same transaction.
- skybrian 3y agoIt seems like listen/notify doesn't play well with a serverless architecture. Would it make sense for Postgres to make a web request when there's work in the queue? Is that a thing?
- jpgvm 3y agoFew things. 1. The main downside to using PostgreSQL as a pub/sub bus with LISTEN/NOTIFY is that LISTEN is a session feature, making it incompatible with statement level connection pooling. 2. If you are going to do this use advisory locks [0]. Other forms of explicit locking put more pressure on the database while advisory locks are deliberately very lightweight. My favorite example implementation is que [1] which is ported to several languages. [0] https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS https://www.postgresql.org/docs/current/explicit-locking.htm... [1] https://github.com/que-rb/que https://github.com/que-rb/que
- MrPowerGamerBR 3y agoOne reason that makes me dislike NOTIFY/LISTEN is that issues with it are hard to diagnose. Recently I had to stop using it because after a while all NOTIFY/LISTENS would stop working, and only a database restart would fix the issue https://dba.stackexchange.com/questions/325104/error-could-not-access-status-of-transaction-could-not-open-file-pg-xact-0029 https://dba.stackexchange.com/questions/325104/error-could-n...
- runeks 3y agoAt my previous company, they switched from using NOTIFY/LISTEN for Postgres notifications to a custom solution built on top of logical replication. As I understand it, part of the reason was reliability. I never touched that part of the code, but I believe the idea was to subscribe to logical replication updates and send out notifications based on these.
- cpursley 3y agoThis is what we do. Even put together a library for it: https://github.com/cpursley/walex https://github.com/cpursley/walex
- teaearlgraycold 3y agoIs there a Node version of Que?
- andrewstuart 3y agoMS SQL server, Postgres and MySQL all support SKIP LOCKED, which means they are all suitable for running queues. I built a complete implementation in Python designed to work the same as SQS but be more simple: https://github.com/starqueue/starqueue https://github.com/starqueue/starqueue Alternatively if you just want to quickly hack something into your application, here is a complete solution in one Python function with retries (ask ChatGPT to tell you what the table structure is): import psycopg2 import psycopg2.extras import random db_params = { 'database': 'jobs', 'user': 'jobsuser', 'password': 'superSecret', 'host': '127.0.0.1', 'port': '5432', } conn = psycopg2.connect(**db_params) cur = conn.cursor(cursor_factory=psycopg2.extras.DictCursor) def do_some_work(job_data): if random.choice([True, False]): print('do_some_work FAILED') raise Exception else: print('do_some_work SUCCESS') def process_job(): sql = """DELETE FROM message_queue WHERE id = ( SELECT id FROM message_queue WHERE status = 'new' ORDER BY created ASC FOR UPDATE SKIP LOCKED LIMIT 1 ) RETURNING *; """ cur.execute(sql) queue_item = cur.fetchone() print('message_queue says to process job id: ', queue_item['target_id']) sql = """SELECT * FROM jobs WHERE id =%s AND status='new_waiting' AND attempts <= 3 FOR UPDATE;""" cur.execute(sql, (queue_item['target_id'],)) job_data = cur.fetchone() if job_data: try: do_some_work(job_data) sql = """UPDATE jobs SET status = 'complete' WHERE id =%s;""" cur.execute(sql, (queue_item['target_id'],)) except Exception as e: sql = """UPDATE jobs SET status = 'failed', attempts = attempts + 1 WHERE id =%s;""" # if we want the job to run again, insert a new item to the message queue with this job id cur.execute(sql, (queue_item['target_id'],)) else: print('no job found, did not get job id: ', queue_item['target_id']) conn.commit() process_job() cur.close() conn.close()
- scraplab 3y agoYep, we process hundreds of thousands and sometimes a few million jobs daily inside Postgres, using Oban in Elixir. Having transactional semantics around background jobs is incredibly convenient for things like scheduling email only if the transaction is successful, and so on. You do need to do a little bit of autovacuum tuning, but once sorted it’s been great for us.
- kissgyorgy 3y agoHere is a Python example how to use it: https://gist.github.com/kissgyorgy/beccba1291de962702ea9c237a900c79 https://gist.github.com/kissgyorgy/beccba1291de962702ea9c237...
- evil-olive 3y agothere's an important dimension of scalability that I think gets overlooked in a lot of these discussions about database-as-a-queue vs queue-system-as-a-queue: are you queuing jobs, or are you queuing messages? that's a fuzzy distinction, so somewhat equivalently, what's the expected time it takes for a worker to process a given queue item? at one end, an item on the queue may take several seconds to a minute or longer to process. at the other end, an item might take only a few milliseconds to process. in that latter case, it's often useful to do micro-batching, where a single worker pulls 100 or 1000 items off the queue at once, and processes them as a batch (such as by writing them to a separate datastore) the "larger" the items are (in terms of wall-clock processing time, not necessarily in terms of size in bytes of the serialized item payload) the more effective the database-as-a-queue solution is, in my experience. as queue items get smaller / shorter to process, and start to feel more like "messages" rather than discrete "jobs", that's when I tend to reach for a queue system over a database. for example, there's a RabbitMQ blog post [0] on cluster sizing where their recommendations start at 1000 messages/second. that same message volume on a database-as-a-queue would require, generally speaking, 3000 write transactions per second (if we assume one transaction to enqueue the message, one for a worker to claim it, and one for a worker to mark it as complete / delete it). can Postgres and other relational databases be scaled & tuned to handle that write volume? yes, absolutely. however, how much write volume are you expecting from your queue workload, compared to the write volume from its "normal database" workload? [1] I think that ends up being a useful heuristic when deciding whether or not to use a database-as-a-queue - will you have a relational database with a "side gig" of acting like a queue, or will you have a relational database that in terms of data volume is primarily acting like a queue, with "normal database" work relegated to "side gig" status? 0: https://blog.rabbitmq.com/posts/2020/06/cluster-sizing-and-other-considerations/ https://blog.rabbitmq.com/posts/2020/06/cluster-sizing-and-o... 1: there's also a Postgres-specific consideration here where a lot of very short-lived "queue item" database rows can put excessive pressure on the autovacuum system.
- doctor_eval 3y agoI’ve used PG as a message queue, actually it was used as a transactional front end to Kafka; we’d push messages to a PG table during a transaction, which would then be snarfed up to Kafka by a separate process after the transaction completed. I’ve seen very high transaction rates from this arrangement, more than 20k messages/second.
- ukd1 3y agoI maintain QueueClassic (https://github.com/QueueClassic/queue_classic https://github.com/QueueClassic/queue_classic) for Rails/Ruby folks; which is basically what you're talking about - a queuing system for Postgres. A bonus reason, and why I originally wanted this was the ability to use transactions fully - i.e. I can start one, do some stuff, add a job in to the queue (to send an email), .....and either commit, or roll back - avoiding sending the email. If you use resque, I found sometimes either you can't see the record (still doing other stuff and it's not committed), or it's not there (rollback) - so either way you had to deal with it. QC (and equivs) use the same db, and same connection, so same transaction. Saves quite a bit of cruft.
- andrewstuart 3y agoYou don't even need a database to make a message queue. The Linux file system makes a perfectly good basis for a message queue since file moves are atomic. My guess is that many people are implementing queuing mechanisms just for sending email. You can see how this works in Arnie SMTP buffer server, a super simple queue just for emails, no database at all, just the file system. https://github.com/bootrino/arniesmtpbufferserver https://github.com/bootrino/arniesmtpbufferserver
- doctor_eval 3y agoThis is true, and I’ve worked on systems that use this, but it’s a lot more work than just a rename. I’d recommend that, if you have a Postgres database already, definitely use that instead. Your queues will be transactional and they will get backed up when the rest of your database does.
- andrewstuart 3y ago>> but it’s a lot more work than just a rename Such as?
- ruuda 3y agoDetecting broken files when your application crashed half-way through a write; coordinating id generation.
- doctor_eval 3y agoWell, if you have multiple writers then you need to decide who’s responsible for rotating the queues, and you need to serialise writes; or if each writer has its own queue then the reader has to do more work. And then you need to worry about fsync, and backup. And of course you need to be careful to flush to the queue after each write to avoid partial writes. Basically I’m saying that there are just a number of potential footguns when using files as queues - I speak from experience! - which are trivially taken care of by a database, especially if you have one already. I’m not saying that it’s not possible, just that for non trivial applications, it’s certainly more complex than just an atomic file move.
- whartung 3y agoFor those using simple SELECTs, what kind of WHERE clause are you using that works well with lots of qualified pending messages and (somewhat) guarantees the most appropriate (oldest?) message?
- rubenfiszel 3y agoWe use exactly this for windmill (OSS Retool alternative + modern airflow) and run benchmarks everyday. On a modest github CI instance where one windmill worker and postgres run as containers, our benchmarks run at 1200jobs/s. Workers can be added and it will scale gracefully up to 5000jobs/s. We are exploring using Citus to cross the barrier of 5000j/s on our multi-tenant instance. https://github.com/windmill-labs/windmill/tree/benchmarks https://github.com/windmill-labs/windmill/tree/benchmarks
- dools 3y agoI'm always surprised that when I see people talk about queues I never see anyone mention beanstalkd. I've been using it for basically everything for 10 years and it's solid as a rock, incredibly simple and requires basically no maintenance. It Just Works™
- arthurbrown 3y agoI've used beanstalkd for personal projects previously with a similar experience to you. For one such project, the message 'priority' feature was a life saver and a feature that is not super common in competing solutions.
- mixmastamyk 3y agoYes, I liked it when I encountered it a few years back. Lately been using redis and rq on a recent project since we're already using redis for caching. Is there much difference other than that?
- jjice 3y agoI've implemented queues with tables in RDBMSs a few times and it's always great and usually all you need. Worried about future scale? Make a class to wrapper the queue with a decent interface and swap it for RabbitMQ or whatever you want down the road. Implementation stays opaque and you have an easy upgrade path later on.
- tiagod 3y agoTemporal, which AFAIK was made by the Uber Cadence team, which was also involved in SQS, uses postgres as a backend. I used it for a web automation system for an accounting client (automatically read files from a network share, lookup the clients on a database, submit the documents to government websites, using headless browsers, and put the resulting files in the directory). It allows for completely effortless deterministic programs that call workers that run the non deterministic code, with built in configurable retries (react to certain exception type, exponential back off) so you can write code that works almost like there were no issues with api connections, filesystem, etc. This code has been running for 5 or more years, with barely any maintenance, with 0 issues so far. It keeps everything in postgres, so even full reboots and crashes have no impact, it will just move the work back to the queue and it will run when there's an available worker.
- leoqa 3y agoTemporal is a pretty complicated system. It has sharding built in, stores the entire activity history and runs multiple queues for timers and events. I’m a big fan (worked at Uber) but it’s definitely not just postgres with a few indices.
- simonw 3y agoOne of my favourite pieces of writing about worker queues is this by Brandur Leach: Transactionally Staged Job Drains in Postgres - https://brandur.org/job-drain https://brandur.org/job-drain It's about the challenge of matching up transactions with queues - where you want a queue to be populated reliably if a transaction completes, and also reliably NOT be populated if it doesn't. Brandur's pattern is to have an outgoing queue in a database table that gets updated as part of that transaction, and can then be separately drained to whatever queue system you like.
- vhiremath4 3y agoWe used postgres for some of our queues back when we were at ~10 msg/s. It scaled quite a bit, but, honestly, setting up SQS or some other queue stack in AWS, GCP, or Azure is so simple and purpose built for the task (with DL queues and the like built in), I don’t know why you wouldn’t just go that route and not have to worry about that system shitting the bed and affecting the rest of the DB’s health. It seems foolish. I am a big fan of “use the dumbest tool”, but sometimes engineers take it too far and you’re left with the dumbest tool with caveats that don’t seem worth it given the mainstream alternative is relatively cheap and simple.
- Rapzid 3y ago> I don’t know why you wouldn’t just go that route and not have to worry about that system shitting the bed Because different software has different requirements. Not having an external service requirement other than Postgres might be a feature of an on-prem/b2b appliance. Because some software may be projected never outgrow the capabilities of Postgres, and if it does moving to another service can be made very easy. Because you want a transitional job system and the simplicity of doing it in Postgres.
- bicijay 3y agoTransactions, data consistency. This is the answer that you will not find in SQS.
- mjb 3y agoYou can't do better than At Least Once if you're having side effects outside the database, so it's not clear that SQS's weaker semantics have any practical effect.
- rtpg 3y agoWhat I've settled on is "store most job state in the DB, use task queues just to poke workers into working on the jobs". Storing the job state in the DB means you can query state nicely. It's not going to exactly show the state of things but it's helpful for working through a live incident (especially when most job queues just delete records as work is processed). And if you make all the background tasks idempotent anyways then you're almost always safe with running a thing like "send a job to the task queue to handle this job". If you rely _just_ on message queues, there are a lot of times where you can have performance issues, yet have a lot of trouble knowing what's going on (for example, rabbitMQ might tell you the size of your queues, but offer little-to-no inspection of the data inside them).
- MattIPv4 3y agoWe process around 1 million events a day using a queue like this in Postgres, and have processed over 400 million events since the system this is used in went live. Only issue we've had was slow queries due to the table size, as we keep an archive of all the events processed, but some scheduled vacuums every so often kept that under control.
- djbusby 3y agoActive Queue table and then archive jobs to a JobDone table? I do that. Queue table is small but archive goes back many months
- MattIPv4 3y agoWe just have a single table, with a column indicating if the job has been taken by a worker or not. Probably could get a bit more performance out of it by splitting into two tables, but it works as it is for now.
- paulddraper 3y agoIn modern PG you can use partitioned table for a similar effect.
- andrewstuart 3y agoPartial indexes might help.
- encoderer 3y agoThis has its place. I would gladly use Postgres queues in places where I use redis queues today; but I would not consider replacing my SQS queues with Postgres.
- Ozzie_osman 3y agoOne thing I love about Kafka is... It's just an append-only log, and a client is essentially just holding an offset. This is conceptually very simple to reason about. It's also persistent and pretty fault-tolerant (you can just go back and read any offset). Unfortunately, Kafka carries with it enough complexity (due to the distributed nature) that it ends up not being worth it for most use-cases. Personally I'd love something similar that's easier to operate. You'd probably be able to handle hundreds (if not thousands) of events per second on a single node, and without distributed complexity it'd be really nice. And yes, in theory you could still use postgres for this (and just never delete rows). And maybe that's the answer.
- chalcolithic 3y agoI used to hope that FasterLog would eventually be mature enough
- valzam 3y agoConsidering that you have a native "offset" (auto incrementing id) and the ability to partition by date I would say postgres is a great candidate for a simple Kafka replacement. It will also be significantly simpler to set up consumers if you don't really need to whole consumer group, partition etc. functionality.
- maxbond 3y agoUnfortunately `serial` is not sufficient on it's own for that use case. If you observe the values 1, 2, and 4, you can't actually conclude whether 3 exists or not. That transaction may have failed after incrementing the serial, meaning 3 does not exist, or it may be an ongoing transaction which hasn't yet committed, meaning 3 exists but is not yet visible to you. So if you update your offset to 4 before the transaction for 3 commits, you'll lose 3 forever (unless you control for this, eg by periodically detecting & requeueing orphaned jobs, or by using a strictly serial/gap free integer rather than the built in auto incrementing type).
- sgarland 3y ago
- what-no-tests 3y agoReally unimpressed with so many people thinking it's OK to shoehorn their database as a message queue. Use the right tool for the job.
- beachy 3y agoOur industry is full of shysters pushing their own technology. Time and again, it turns out that a RDBMS will handle that job just fine. That's really the premise of this article. So please, go on and back up your bold statements with some specifics. Why specifically is it not OK to use a database as a message queue?
- what-no-tests 3y agoNo time at the moment to break it all down, but here's a previous discussion thread on a similar topic. https://news.ycombinator.com/item?id=27483003 https://news.ycombinator.com/item?id=27483003
- physicles 3y agoGood counter-point. I see these main points over there: 1. It doesn't scale (there it is again) 2. Queuing with Postgres is super fiddly to get right 3. You're hacking a queue on top of something that isn't a queue 4. Running redis or rabbit isn't all that complicated #1 as TFA argues, premature concern about scaling is the root of so much needless complexity. You should make scaling decisions like this: 1) assume boring tech like PG will satisfy your needs; 2) if it demonstrably does not, then find something that does. #2 is obviously true; just look at this thread. There are battle-tested queuing libraries in most popular languages, but you do have to dig into the details of how they interact with things like pgbouncer. #3 I guess so? But if the queue abstraction works and isn't leaky, what does it matter? #4 can be debated. For several years I've been running a moderately complicated setup with 2 databases, redis, and kafka for several years. There's no way I'm going to add another piece of tech unless there's no other choice. The cognitive cost is too high. The main debate is in the tradeoff between #2 and #4. Personally, if I can use an existing piece of tech to solve a problem to avoid having to ops another piece of tech, then I'm going to do that every time.