12 ms·
Show HN: PgQueuer – Transform PostgreSQL into a Job Queue
PgQueuer is a minimalist, high-performance job queue library for Python, leveraging the robustness of PostgreSQL. Designed for simplicity and efficiency, PgQueuer uses PostgreSQL's LISTEN/NOTIFY to manage job queues effortlessly.
- CRConrad 2y agoThe name is perhaps slightly fraught with risk... I missed the second 'u', at first, so I misread it.
- samwillis 2y agoThis looks like a great task queue, I'm a massive proponent of "Postgres is all you need" [0] and doubling down on it with my project that takes it to the extreme. What I would love is a Postgres task queue that does multi-step pipelines, with fan out and accumulation. In my view a structured relational database is a particularly good backend for that as it inherently can model the structure. Is that something you have considered exploring? The one thing with listen/notify that I find lacking is the max payload size of 8k, it somewhat limits its capability without having to start saving stuff to tables. What I would really like is a streaming table, with a schema and all the rich type support... maybe one day. 0: https://www.amazingcto.com/postgres-for-everything/ https://www.amazingcto.com/postgres-for-everything/
- jeeybee 2y agoThanks for your insights! Regarding multi-step pipelines and fan-out capabilities: It's a great suggestion, and while PgQueuer doesn't currently support this, it's something I'm considering for future updates. As for the LISTEN/NOTIFY payload limit, PgQueuer uses these signals just to indicate changes in the queue table, avoiding the size constraint by not transmitting substantial data through this channel.
- halfcat 2y agoIs multi-step (fan out, etc) typically something a queue or message bus would handle? I’ve always handled this with an orchestrator solution like (think Airflow and similar). Or is this a matter of use case? Like for a real-time scenario where you need a series of things to happen (user registration, etc) maybe a queue handling this makes sense? Whereas with longer running tasks (ETL pipelines, etc) the orchestrator is beneficial?
- whateveracct 2y agoI've come to hate "Postgres is all you need." Or at least, "a single Postgres database is all you need."
- philippta 2y agoWhy?
- deleted 2y ago[deleted]
- tmountain 2y agoJust to share an anecdote, we've been able to get to market faster than ever before by just using Postgres (Supabase) for basically everything. We're leveraging RLS, and it's saved us from building an API (just using PostgREST). We've knocked months off our project timeline by doing this.
- berkes 2y agoI won´t call it "hate", but I've ran into quite some situations where the Postgres version caused a lot of pain. - When it wasn't as easy as a dedicated solution: where installing and managing a focused service is overall easier than shoehorning it into PG. - when it didn't perform anywhere close to a dedicated solution: overhead from the guarantees that PG makes (acid and all that) when you don't need them. Or where the relational architecture isn't suited for this type of data: e.g. hierarchical, time-series, etc. - when it's not as feature complete as a dedicated service: for example I am quite sure one can build (parts of) an ActiveDirectory or Kafka Bus, entirely in PG. But it will lack features that in future you'll likely need - they are built into these dedicated solutions because they are often needed after all.
- jashmatthews 2y agoPutting low throughput queues in the same DB is great both for simplicity and for getting exactly-once-processing. Putting high throughput queues in Postgres sucks because... No O(1) guarantee to get latest job. Query planner can go haywire. High update tables bloat like crazy. Needs a whole new storage engine aka ZHEAP Write amplification as every update has to update every index LISTEN/NOTIFY doesn't work through connection pooling
- mickeyp 2y agoUpdate-related throughput and index problems are only a problem if you update tables. You can use an append-only structure to mitigate some of that: insert new entries with the updated statuses instead. You gain the benefit of history also. You can even coax the index into holding non-key values for speed with INCLUDE to CREATE INDEX. You can then delete the older rows when needed or as required. Query planner issues are a general problem in postgres and is not unique to this problem. Not sure what O(1) means in this context. I am not sure pg has ever been able to promise constant-time access to anything; indeed, with an index, it'd never be asymptotically upper bounded as constant time at all?
- jashmatthews 2y agoBy the time you need append-only job statuses it's better to move to a dedicated queue. Append-only statuses help but they also make the polling query a lot more expensive. Deleting older rows is a nightmare at scale. It leaves holes in the earlier parts of the table and nerfs half the advantage of using append-only in the first place. You end up paying 8kb page IO costs for a single job. Dedicated queues have constant time operations for enqueue and dequeue which don't blow up at random times.
- iTokio 2y agopartitions are often used to drop old data in constant time. They can also help to mitigate io issues if you use your insertion timestamp as the partition key and include it in your main queries.
- deleted 2y ago[deleted]
- CowOfKrakatoa 2y agoHow does LISTEN/NOTIFY compare to using select for update skip locked? I thought listen/notify can lose queue items when the process crashes? Is that true? Do you need to code for those cases in some manner?
- jeeybee 2y agoLISTEN/NOTIFY and SELECT FOR UPDATE SKIP LOCKED serve different purposes in PgQueuer. LISTEN/NOTIFY notifies consumers about changes in the queue table, prompting them to check for new jobs. This method doesn’t inherently lose messages if a process crashes, because it simply triggers a check rather than transmitting data. The actual job handling and locking are managed by SELECT FOR UPDATE SKIP LOCKED, which safely processes each job even when multiple workers are involved.
- severino 2y agoI think the usage of listen/notify is just a mechanism to save you from querying the database every X seconds looking for new tasks (polling). That has some drawbacks, because if the timeout is too small, you are making too much queries that usually may not return any new tasks, and if it's too big, then you may start processing the task long after it was submitted. This way, it just notifies you that new tasks are ready so you can query the database.
- halfcat 2y agoThere are two things: 1. Signaling 2. Messaging In some systems, those are, effectively, the same. A consumer listens, and the signal is the message. If the consumer process crashes, the message returns to the queue and gets processed when the consumer comes back online. If the signal and messaging are separated, as in Postgres, where LISTEN/NOTIFY is the signal, and the skip locked query is the message pull, the consumer process would need to do some combination of polling and listening. In the consumer, that could essentially be a loop that’s just doing the skip locked query on startup, then dropping into a LISTEN query only once there are no messages present in the queue. Then the LISTEN/NOTIFY is just signaling to tell the consumer to check for new messages.
- cassepipe 2y ago
- cklee 2y agoI’ve been thinking about the potential for PostgreSQL-backed job queue libraries to share a common schema. For instance, I’m a big fan of Oban in Elixir: https://github.com/sorentwo/oban https://github.com/sorentwo/oban Given that there are many Sidekiq-compatible libraries across various languages, it might be beneficial to have a similar approach for PostgreSQL-based job queues. This could allow for job processing in different languages while maintaining compatibility. Alternatively, we could consider developing a core job queue library in Rust, with language-specific bindings. This would provide a robust, cross-language solution while leveraging the performance and safety benefits of Rust.
- deleted 2y ago[deleted]
- memset 2y agoI am building an SQS compatible queue for exactly that reason. Use with any language or framework. https://github.com/poundifdef/smoothmq https://github.com/poundifdef/smoothmq It is based on SQLite, but it’s written in a modular way. It would be easy to add Postgres as a backend (in fact, it might “just work” if I switch the ORM connection string.)
- GordonS 2y agoDoes SmoothMQ support running multiple nodes for high availability? (I didn't see anything in the docs, but they seem unfinished)
- memset 2y agoNot today. It's a work in progress! There are several iterations that I'm working on: 1. Primary with secondaries as replicas (replication for availability) 2. Sharding across multiple nodes (sharding for horizontal scaling) 3. Sharding with replication However, those aren't ready yet. The easiest way to implement this would probably be to use Postgres as the backing storage for the queue, which means relying on Postgres' multiple node support. Then the queue server itself could also scale up and down independently. Working on the docs! I'd love your feedback - what makes them seem unfinished? (What would you want to see that would make them feel more complete?)
- redskyluan 2y agothere seems to be a big hype to adapt pg into any infra. I love PG but this seems not be right thing.
- mlnj 2y agoI use it as a job queue. Yes, it has it's cons, but not dealing with another moving piece in the big picture is totally worth it.
- sgarland 2y agoAt low-medium scale, this will be fine. Even at higher scale, so long as you monitor autovacuum performance on the queue table. At some point it may become practical to bring a dedicated queue system into the stack, sure, but this can massively simplify things when you don’t need or want the additional complexity.
- jeeybee 2y agoI agree, there is no need for FANG level infrastructure. Imo. in most cases, the simplicity / performance tradeoff for small/medium is worth it. There is also a statistics tooling that helps you monitor throughput and failure rats (aggregated on a per second basis)
- arp242 2y agoAside from that, the main advantage of this is transactions. I can do: begin; insert_row(); schedule_job_for_elasticsearch(); commit; And it's guaranteed that both the row and job for Elasticsearch update are inserted. If you use a dedicated queue system them this becomes a lot more tricky: begin; insert_row(); schedule_job_for_elasticsearch(); commit; // Can fail, and then we have a ES job but no SQL row. begin; insert_row(); commit; schedule_job_for_elasticsearch(); // Can fail, and then we have a SQL row and no job. There are of course also situations where this doesn't apply, but this "insert row(s) in SQL and then queue job to do more with that" is a fairly common use case for queues, and in those cases this is a great choice.
- 2y ago
- westurner 2y agoDoes the celery SQLAlchemy broker support PostgreSQL's LISTEN/NOTIFY features? Similar support in SQLite would simplify testing applications built with celery. How to add table event messages to SQLite so that the SQLite broker has the same features as AMQP? Could a vtable facade send messages on tablet events? Are there sqlite Triggers? Celery > Backends and Brokers: https://docs.celeryq.dev/en/stable/getting-started/backends-and-brokers/index.html https://docs.celeryq.dev/en/stable/getting-started/backends-... /? sqlalchemy listen notify: https://www.google.com/search?q=sqlalchemy+listen+notify https://www.google.com/search?q=sqlalchemy+listen+notify : asyncpg.Connection.add_listener sqlalchemy.event.listen, @listen_for psychopg2 conn.poll(), while connection.notifies psychopg2 > docs > advanced > Advanced notifications: https://www.psycopg.org/docs/advanced.html#asynchronous-notifications https://www.psycopg.org/docs/advanced.html#asynchronous-noti... PgQueuer.db, PgQueuer.listeners.add_listener; asyncpg add_listener: https://github.com/janbjorge/PgQueuer/blob/main/src/PgQueuer/db.py https://github.com/janbjorge/PgQueuer/blob/main/src/PgQueuer... asyncpg/tests/test_listeners.py: https://github.com/MagicStack/asyncpg/blob/master/tests/test_listeners.py https://github.com/MagicStack/asyncpg/blob/master/tests/test... /? sqlite LISTEN NOTIFY: https://www.google.com/search?q=sqlite+listen+notify https://www.google.com/search?q=sqlite+listen+notify sqlite3 update_hook: https://www.sqlite.org/c3ref/update_hook.html https://www.sqlite.org/c3ref/update_hook.html
- aflukasz 2y agoBTW: Good PostgresFM episode on implementing queues in Postgres, various caveats etc: https://www.youtube.com/watch?v=mW5z5NYpGeA https://www.youtube.com/watch?v=mW5z5NYpGeA .
- jeeybee 2y agothanks for sharing, added to my to watch list.
- deleted 2y ago[deleted]
- fijiaarone 2y agoYou can make anything that stores data into a job queue.
- kaoD 2y agoBut can you make a decent job queue with anything that stores data? Not easily. E.g. you need atomicity if multiple consumers can take jobs, and I think you need CAS for that, not just any storage will do, right? You probably need ACI and also D if you want your jobs to persist.
- _medihack_ 2y agoThere is also Procrastinate: https://procrastinate.readthedocs.io/en/stable/index.html https://procrastinate.readthedocs.io/en/stable/index.html Procrastinate also uses PostgreSQL's LISTEN/NOTIFY (but can optionally be turned off and use polling). It also supports many features (and more are planned), like sync and async jobs (it uses asyncio under the hood), periodic tasks, retries, task locks, priorities, job cancellation/aborting, Django integration (optional). DISCLAIMER: I am a co-maintainer of Procrastinate.
- deleted 2y ago[deleted]
- joking 2y agoit should be the opposite of procastination, but good naming anyway.
- lukebuehler 2y agoI’m using Procrastinate in several projects. Would definitely like to see a comparison. What I personally love about Procrastinate is async, locks, delayed and scheduled jobs, queue specific workers (allowing to factor the backend in various ways). All this with a simple codebase and schema.
- deleted 2y ago[deleted]
- martinald 2y agoAny suggestions for something like this for dotnet?
- eknkc 2y agoHangfire with PostgreSQL driver.
- simplyinfinity 2y agoHangfire with few plugins can be an absolute godsent in 99% of situations i've encountered. The one downside is the documentation is very very lacking, and you have to google a lot until you get to a good place. Despite that, i've used, use, and will continue to use Hangfire, as it's a great tool!
- hkon 2y agoUpdlock rowlock readpast should do the trick
- wordofx 2y agoIt’s a simple query to write. You don’t need a library or framework.
- rgbrgb 2y agoCool, congrats on releasing. Have you seen graphile worker? Wondering how this compares or if you're building for different use-cases.
- mind-blight 2y agoI think graphile worker is Node only. This project is for Python.
- pestaa 2y agoThere is experimental support for arbitrary executables: https://worker.graphile.org/docs/tasks#loading-executable-files https://worker.graphile.org/docs/tasks#loading-executable-fi... But you can use a thin JS wrapper to make shell calls from Node. Slightly inconvenient, but works well for my use case.
- jeeybee 2y agoI haven’t used Graphile Worker since I’m not familiar with JavaScript. PgQueuer is tailored for Python with PostgreSQL environments. I’d be interested to hear about Graphile Worker’s features and how they might inspire improvements to PgQueuer.
- ijustlovemath 2y agoYou could even layer in PostgREST for a nice HTTP API that is available from any language!
- topspin 2y agoAlready done. See: PostgREST. Want to use PostgreSQL (or most other RDBMSs) as the backend for an actively developed, multiprotocol, multiplatform, open source, battle proven message broker that also provides a messaging REST API of its own? Use ActiveMQ (either flavor) and configure a JDBC backend. Done.
- gmag 2y agoYou might also want to look at River (https://github.com/riverqueue/river https://github.com/riverqueue/river) for inspiration as they support scheduled jobs, etc. From an end-user perspective, they also have a UI which is nice to have for debugging.
- onionisafruit 2y agoI’ve been using river for some low volume stuff. I love that I can add a job to the queue in the same db transaction that handle the synchronous changes.
- bdcravens 2y agoGlancing at it briefly, I like the Workflows feature. I'm a long time Sidekiq user (Ruby), and while you can construct workflows pretty easily (especially using nested batches and callbacks in the Pro version), there really isn't a dedicated UI for visualizing them.
- rubenvanwyk 2y agoAlso wanted to say I thought this problem has already been solved by River. Although seems like OP references a Python library rather than standalone server, so would probably be useful to Python devs.
- deleted 2y ago[deleted]
- bdcravens 2y agoGood Job does the same for Rails https://github.com/bensheldon/good_job https://github.com/bensheldon/good_job
- strzibny 2y agoWanted to post this, glad it's already here. This is PgQueuer for Rails but also with some history under its belt.
- Lio 2y agoThere’s also the new built in SolidQueue. https://github.com/rails/solid_queue/ https://github.com/rails/solid_queue/
- sandGorgon 2y agohttps://dev.37signals.com/introducing-solid-queue/ https://dev.37signals.com/introducing-solid-queue/
- BilalBudhani 2y agoThanks for mentioning this gem (literally). I moved my projects to GoodJob and it has been smooth sailing.
- deleted 2y ago[deleted]
- airocker 2y agoWe use listen notify extensively and it is great. The things it lacks most for us is guaranteed single recipient. All subscribers get all notifications which leads to problems in determining who should act on the message n our case.
- m11a 2y agoCould use the notify to awake, and then the worker needs to lock the job row? Whichever worker gets the lock, acts on the message
- airocker 2y agoThats exactly what we do but taking a lock takes 1 RTT to the database which means about 100ms. it limits the number of events receivers can handle. IF you have too many events, receivers will be just trying to take a lock most of the time.
- jeeybee 2y agoOf my head, you could attach a uuid or sequence number to the emitted event. Then based on the uuid or sequence you can let one or the other event consumer pick? Ex. you have two consumers, if the sequence number is odd, A picks it, if its even B picks.
- collingreen 2y agoWithout more logic than this does this mean any workers that are busy or down mean you lose jobs?
- airocker 2y agoWe use a garbage collector to error restart if a job is not served within a specified amount of time.
- airocker 2y ago
- rtpg 2y agoI am going to go the other direction on this... to anyone reading this, please consider using a backend-generic queueing system for your Python project. Why? Mainly because those systems offer good affordances for testing and running locally in an operationally simple way. They also tend to have decent default answers for various futzy questions around disconnects at various parts of the workflow. We all know Celery is a buggy pain in the butt, but rolling your own job queue likely ends up with you just writing a similary-buggy pain in the butt. We've already done "Celery but simpler", it's stuff like Dramatiq! If you have backend-specific needs, you won't listen to this advice. But think deeply how important your needs are. Computers are fast, and you can deal with a lot of events with most systems. Meanwhile if you use a backend-generic system... well you could write a backend using PgQueuer!
- nsonha 2y ago> those systems offer good affordances for testing and running locally in an operationally simple way Define "operationally simple", most if not all of them need persistent anyway, on top of the queue itself. This eliminates the queue and uses a persistent you likely already have.
- rtpg 2y agoWell for example, lots of queueing libraries have an "eager task" runtime option. What does that do? Instead of putting work into a backend queue, it just immediately runs the task in-process. You don't need any processing queue! How many times have you shipped some background task change, only to realize half your test suite doesn't do anything with background tasks, and you're not testing your business logic to the logical conclusion? Eager task execution catches bugs earlier on, and is close enough to the reality for things that matter, while removing the need for, say, multi-process cordination in most tests. And you can still test things the "real way" if you need to! And to your other point: you can use Dramatiq with Postgres, for example[0]. I've written custom backends that just use pg for these libs, it's usually straightforward because the broker classes tend to abstract the gnarly things. [0]: https://pypi.org/project/dramatiq-pg/ https://pypi.org/project/dramatiq-pg/
- 2y ago
- worik 2y agoWhy so much code for Avery simple concept One table. Producer writes Co sumer reads A very good idea
- joseferben 2y agofor typescript there is pg-boss, works great for us
- est 2y agoThe most simple job queue in MySQL: update job_table set key=value where ... limit 1 It's simple and atomic. Unfortunately PG doesn't allow `update ... limit` syntax
- DemocracyFTW2 2y ago> It's simple and atomic and almost certainly incorrect, gotta read at least https://www.pgcon.org/2016/schedule/attachments/414_queues-pgcon-2016.pdf https://www.pgcon.org/2016/schedule/attachments/414_queues-p... which discusses FOR UPDATE SKIP LOCKED
- est 2y agothat's a nice read, but does it also apply to MySQL (InnoDB)?
- hparadiz 2y agoI've done this with mysql. Never do one at a time if you have jobs per minute over 30. It won't scale. Instead have the job dispatcher reserve 100 at a time and then fire that off to a subprocess which will subsequently fire off a process for each job. A three layer approach makes it much easier to build out multiserver. Or if you don't want the headache just use SQS which is pretty much free under 1 million jobs.
- est 2y agoYeah it's very basic and limited. However if I am about to use DB as a job queue for budget reasons, I'd make sure the job doesn't get too complicated.
- hparadiz 2y agoFor me it was a lot of small jobs. I was able to get it up to 3500 jobs an hour and likely could have gone far past that but the load on the MySQL server was not reasonable
- kdunglas 2y agoThe Symfony framework (PHP) provides a similar feature, which also relies on LISTEN/NOTIFY and FOR UPDATE SKIP LOCKED: https://symfony.com/doc/current/messenger.html#doctrine-transport https://symfony.com/doc/current/messenger.html#doctrine-tran... It also supports many other backends including AMQP, Beanstalkd, Redis and various cloud services. This component, called Messenger, can be installed as a standalone library in any PHP project. (Disclaimer: I’m the author of the PostgreSQL transport for Symfony Messenger).
- deleted 2y ago[deleted]
- odie5533 2y agoI've been referring to this post about issues with Celery: https://docs.hatchet.run/blog/problems-with-celery https://docs.hatchet.run/blog/problems-with-celery Does PgQueuer address any of them?
- jeeybee 2y agoAt first glans i i see two thins that PgQueuer can. It has native async support, it you can also have sync functions, they will be offloaded to threads via anyio (https://github.com/janbjorge/PgQueuer/blob/99c82c2d661b2ddfccc8568ab3302bce77a643fb/src/PgQueuer/qm.py#L236 https://github.com/janbjorge/PgQueuer/blob/99c82c2d661b2ddfc...) It has a global rate limit, synced via pg notify.
- odie5533 2y agoPgQueuer seems pretty similar to Hatchet - both use Postgres, require their own worker processes, both support async. Hatchet seems to be a lot more powerful though: cron, DAG workflows, retries, timeouts, and a UI dashboard.
- TkTech 2y agohttps://github.com/tktech/chancy https://github.com/tktech/chancy is a very early-stage pet project to work around specific issues I keep running into with Celery that has all of this, including a simple plugin-extendable UI with DAG workflow visualization.
- piyushtechsavy 2y agoAlthough I am more of a MySQL guy, I have been exploring PostgreSQL from sometime. Seems it has lot of features out of box. This is very interesting tool.
- _joel 2y agoBroadcaster is one we use in production for PUB/SUB stuff with OPA/OPAL. https://pypi.org/project/broadcaster/ https://pypi.org/project/broadcaster/
- nifal_adam 2y agoIt looks like PgQueuer integrates well with Postgres RPC calls, triggers, and cronjobs (via pg_cron). Interesting, will check it out.
- bfelbo 2y agoHow does this compare to the popular Graphile Worker library? https://github.com/graphile/worker https://github.com/graphile/worker
- deleted 2y ago[deleted]
- mads_quist 2y agoI really like the emergence of simple queuing tools for robust database management systems. Keep things simple and remove infrastructure complexity. Definitely a +1 from me! For handling straightforward asynchronous tasks like sending opt-in emails, we've developed a similar library at All Quiet for C# and MongoDB: https://allquiet.app/open-source/mongo-queueing https://allquiet.app/open-source/mongo-queueing In this context: LISTEN/NOTIFY in PostgreSQL is comparable to MongoDB's change streams. SELECT FOR UPDATE SKIP LOCKED in PostgreSQL can be likened to MongoDB's atomic read/update operations.
- deleted 2y ago[deleted]
- deleted 2y ago[deleted]
- jackbravo 2y agoMost of the small python alternatives I've seen use Redis as backend: - https://github.com/rq/rq https://github.com/rq/rq - https://github.com/coleifer/huey https://github.com/coleifer/huey - https://github.com/resque/resque https://github.com/resque/resque
- deleted 2y ago[deleted]
- mslot 2y agoNice! Seems to be pretty well-crafted.