Skip to main content

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.

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.
1

Add the dependency

Add Proletarian to your deps.edn file:
;; deps.edn
msolli/proletarian {:mvn/version "1.0.115"}
Or to project.clj if you use Leiningen:
;; project.clj
[msolli/proletarian "1.0.115"]
2

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.
3

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:
(ns your-app.handlers
  (:require [clojure.tools.logging :as log]))

;; The multimethod Proletarian will call for every job.
;; Dispatches on job-type, which is a keyword.
(defmulti handle-job!
  (fn [job-type _payload] job-type))

;; Implement a method for each job type you want to handle.
(defmethod handle-job! ::send-confirmation-email
  [job-type {:keys [email]}]
  (log/info "Sending confirmation email to" email)
  ;; Call your email service here.
  )
4

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!:
(ns your-app.workers
  (:require [next.jdbc :as jdbc]
            [proletarian.worker :as worker]
            [your-app.handlers :as handlers]))

;; next.jdbc returns a DataSource from a JDBC URL.
;; Swap in whatever connection pool you already use.
(def data-source
  (jdbc/get-datasource "jdbc:postgresql://localhost:5432/mydb?user=myuser&password=secret"))

;; Create the queue worker. By default it uses the :proletarian/default queue,
;; one worker thread, and a 100 ms polling interval.
(def email-worker
  (worker/create-queue-worker data-source handlers/handle-job!))

;; Start polling for jobs.
(worker/start! email-worker)
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.
5

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:
(ns your-app.routes
  (:require [next.jdbc :as jdbc]
            [proletarian.job :as job]
            [your-app.db :as db]))

(defn register-user-handler [system request]
  (let [user (parse-user-from-request request)]
    (jdbc/with-transaction [tx (:db system)]
      ;; Write the new user to the database.
      (db/insert-user! tx user)
      ;; Atomically enqueue a confirmation email job.
      ;; The job-type must match a defmethod in your handler multimethod.
      (job/enqueue! tx ::handlers/send-confirmation-email
        {:email (:email user)}))
    {:status 201 :body "Created"}))
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.
6

Stop the worker

Call worker/stop! to gracefully drain in-flight jobs and shut the worker down:
(worker/stop! email-worker)
stop! waits up to 10 seconds (configurable via :proletarian/await-termination-timeout-ms) for any running jobs to finish before returning.

What Just Happened?

When worker/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.

Build docs developers (and LLMs) love