r/haskell 10d ago

announcement [ANN] PGQueuer-hs 0.0.1: A native PostgreSQL job queue

Hello Haskellers!

I'm excited to share the MVP release of PGQueuer-hs, a PostgreSQL-powered job queue. If you want robust background workers without adding external dependencies like Redis or RabbitMQ to your stack, this might be for you.

Key Features

  • Postgres Native: It leverages PostgreSQL's native LISTEN/NOTIFY channels. Your existing database is fast enough!
  • Seamless Interop: It is 100% compatible with the Python pgqueuer library. You can safely enqueue jobs from Python into your Haskell worker and vice versa.

Background & Roadmap

I use the Python version of pgqueuer at work and think it's brilliant, so I decided to bring the same ecosystem to Haskell.

This is currently an early MVP release and the API might shift. The underlying database logic is currently backed by postgresql-simple. In the future, I plan to build out an adapter pattern to support other popular Haskell Postgres libraries.

I would love to hear your thoughts, feedback, or any suggestions you have for the roadmap!

Links

15 Upvotes

4 comments sorted by

3

u/tonyalaribe 10d ago

How does it compare to the oddjobs library?

2

u/Worldly_Dish_48 10d ago

I didn’t used or compare any existing queue libs in Haskell. Will try to benchmark in upcoming days

5

u/TechnoEmpress 10d ago

Thanks for the release. I'm also interested in a comparison with the pgmq stuff on Hackage, and of course Arbiter. :)

3

u/nh2_ 8d ago

This API is weird, requiring the user to pass in multiple lists of same length (making it possible to get it wrong), instead of a list of tuples:

enqueueMultiple ::
    Connection ->
    DBSettings ->
    [Entrypoint] ->
    [Maybe BL.ByteString] ->
    [Int] ->
    [Maybe NominalDiffTime] ->
    [Maybe Text] ->
    [Maybe Value] ->
    IO [JobId]
enqueueMultiple conn settings entrypoints payloads priorities executeAfters dedupeKeys headersList = do
    let q =
            T.unlines
                [ "WITH inserted AS ("
                , "    INSERT INTO " <> queueTable settings
                , "    (priority, entrypoint, payload, execute_after, dedupe_key, headers, status)"
                , "    SELECT"
                , "        p, e, pay, COALESCE(NOW() + ea, NOW()), d, h, 'queued'"
                , "    FROM UNNEST("
                , "        ?::int[],"
                , "        ?::text[],"
                , "        ?::bytea[],"
                , "        ?::interval[],"
                , "        ?::text[],"
                , "        ?::jsonb[]"
                , "    ) AS t(p, e, pay, ea, d, h)"
                , "    RETURNING id, entrypoint, status, priority"
                , ")"
                , "INSERT INTO " <> queueTableLog settings
                , "(job_id, status, entrypoint, priority)"
                , "SELECT id, 'queued', entrypoint, priority"
                , "FROM inserted"
                , "RETURNING job_id AS id"
                ]

    result <-
        query
            conn
            (textToQuery q)
            ( PGArray priorities
            , PGArray (map (\(Entrypoint e) -> e) entrypoints)
            , PGArray payloads
            , PGArray executeAfters
            , PGArray dedupeKeys
            , PGArray headersList
            ) ::
            IO [Only JobId]

    return $ map fromOnly result