By the end of this guide you will have a working Proletarian queue worker that picks up jobs from your PostgreSQL or MySQL database, a job handler that processes those jobs, and the code to enqueue your first background job — all wired together in a real Clojure namespace.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/msolli/proletarian/llms.txt
Use this file to discover all available pages before exploring further.
Create the database tables
Before the worker can run you need the
proletarian.job and proletarian.archived_job tables in your database. See the Installation page for the complete SQL for both PostgreSQL and MySQL.Run the migration once against your development (and later production) database before proceeding.Define a job handler
The job handler is an arity-2 function (or multimethod) that Proletarian calls whenever it pulls a job off the queue. The first argument is the job type — a Clojure keyword — and the second is the payload map you supplied when enqueueing.Using a
defmulti makes it easy to dispatch to different handler functions as your set of job types grows:Create and start the worker
A queue worker polls the database for ready jobs and dispatches them to your handler. Create it with
worker/create-queue-worker, passing a javax.sql.DataSource and your handler function, then call worker/start!:create-queue-worker accepts an optional third argument — an options map — where you can configure the queue name, thread count, polling interval, retry strategy, and more.Enqueue a job
Enqueue a job by calling
job/enqueue! inside a jdbc/with-transaction block. The job and any other database writes you make in the same transaction will be committed atomically:job/enqueue! takes a java.sql.Connection (not a data source) as its first argument — the connection you get from jdbc/with-transaction is exactly right. The second argument is the job type keyword and the third is any serialisable Clojure value as the payload. It returns the job ID of the newly enqueued job.What Just Happened?
Whenworker/start! is called, Proletarian spins up a thread pool (one thread by default) that polls the proletarian.job table at a regular interval. Each poll issues a SELECT ... SKIP LOCKED query, which atomically claims the next available job for the queue without blocking other worker threads.
When you called job/enqueue! inside the transaction, Proletarian inserted a row into proletarian.job with the job type, payload, and a process_at timestamp of now. When the transaction committed, the row became visible to the worker’s poll query.
The worker thread picked up the job, incremented the attempts counter, and called your handle-job! multimethod with the job type and payload. On success, it moved the row from proletarian.job to proletarian.archived_job — with a status of success — in the same database transaction. The job is now gone from the queue and permanently recorded in the archive.
In a production application you should manage the worker’s lifecycle with a
component library such as Component,
Integrant, or
Mount. Each library provides its own
mechanism for starting and stopping stateful resources in the correct order,
so your worker is started after the database connection pool is ready and
stopped cleanly on shutdown.
Next Steps
Queue Worker
Learn about worker threads, polling intervals, multiple queues, retry strategies, and shutdown behaviour.
Job Handler
Explore advanced handler modes, logging, failed-job callbacks, and system state in handlers.
