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.

This example walks through the most fundamental Proletarian workflow: starting a queue worker that polls PostgreSQL for work, enqueuing a single job, and watching the worker discover and process it. It covers the core concepts — job types, Transit serialization, and configurable polling intervals — in the simplest possible setting.

Prerequisites

  • PostgreSQL 9.5+ installed and running locally (or via Docker — see below).
  • Clojure CLI (clj) version 1.10.1.697 or later, with -X option support. (Installation instructions)
  • Two terminal windows — one for the worker, one for enqueueing jobs.
  • The Proletarian Git repository cloned locally:
    git clone git@github.com:msolli/proletarian.git
    cd proletarian
    

Walkthrough

1

Create the example database

Run the Makefile target to create the proletarian PostgreSQL user, database, schema, tables, and indexes:
make examples.db.install
Expected output:
DATABASE_NAME=proletarian ./database/install.sh

Installing Proletarian Database
= = =

Creating User
- - -
» proletarian role

Creating Database
- - -
» proletarian database

Creating Schema, Tables, and Index
» proletarian schema
» proletarian.job table
» proletarian.archived_job table
» proletarian.job_queue_process_at index

Granting Privileges
- - -
» schema privileges
» table privileges

= = =
Done Installing Proletarian Database
You can uninstall the database with make examples.db.uninstall and start completely fresh with make examples.db.recreate. The examples assume the default database name (proletarian) is used.
2

(Optional) Use Docker instead of a local PostgreSQL install

If you prefer an ephemeral Postgres instance, start one with Docker in a separate terminal:
docker run -p 55432:5432 -e POSTGRES_PASSWORD=proletarian postgres
Then install the schema, pointing at the Docker container:
PGHOST=localhost PGPORT=55432 PGUSER=postgres PGPASSWORD=proletarian make examples.db.install
For all subsequent commands in a Docker setup, export the database URL so the examples can find it:
export DATABASE_URL="jdbc:postgresql://localhost:55432/proletarian?user=postgres&password=proletarian"
3

Run the queue worker

In terminal window 1, start the queue worker:
clj -X:examples example-a.worker/run
You should see the worker start up, print current job counts, and begin polling the default queue every 5 seconds:
Number of jobs in :proletarian/default queue: 0
Number of jobs in proletarian.jobs table: 0
Number of jobs in proletarian.archived_jobs table: 0

Starting worker for :proletarian/default queue with polling interval 5 s
:proletarian.worker/polling-for-jobs {:worker-thread-id 1, :proletarian.worker/queue-worker-id proletarian[:proletarian/default]}
:proletarian.worker/polling-for-jobs {:worker-thread-id 1, :proletarian.worker/queue-worker-id proletarian[:proletarian/default]}
[...and so forth, until you press Ctrl-C]
Leave this process running and move to the next step.
The polling interval is set to 5 seconds in this example so it’s easy to see what’s happening. In production you would use a much smaller interval — the default is 100 ms.
4

Enqueue a job

In terminal window 2, enqueue a single job:
clj -X:examples example-a.enqueue-jobs/run
The command prints the enqueued job’s details:
Adding new job to :proletarian/default queue:
{:job-id #uuid "...",
 :job-type :example-a.enqueue-jobs/echo,
 :payload {:message "Hello world!",
           :timestamp #inst "..."}}
Run the same command again at any time to enqueue additional jobs.
5

Observe job processing in the worker terminal

Switch back to terminal window 1. Within the next polling cycle you will see the worker pick up and process the job:
:proletarian.worker/handling-job {:job-id #uuid "...", :job-type :example-a.enqueue-jobs/echo, :attempt 1, :worker-thread-id 1, :proletarian.worker/queue-worker-id proletarian[:proletarian/default]}
Running job :example-a.enqueue-jobs/echo. Payload:
{:message "Hello world!", :timestamp #inst "..."}
:proletarian.worker/job-finished {:job-id #uuid "...", :job-type :example-a.enqueue-jobs/echo, :attempt 1, :worker-thread-id 1, :proletarian.worker/queue-worker-id proletarian[:proletarian/default]}
Press Ctrl-C to stop the worker when you’re done.

Source code

Worker (example_a/worker.clj)

(ns example-a.worker
  (:require [examples.common :as common]
            [example-a.enqueue-jobs :as enqueue-jobs]
            [next.jdbc :as jdbc]
            [proletarian.worker :as worker]))

(defn run
  [_]
  (let [ds (jdbc/get-datasource (:jdbc-url common/config))]
    (common/preamble ds)
    (println "Starting worker for :proletarian/default queue with polling interval 5 s")
    (let [worker (worker/create-queue-worker ds
                                             enqueue-jobs/handle-job!
                                             {:proletarian/polling-interval-ms 5000
                                              :proletarian/on-shutdown (fn [] (shutdown-agents))})]
      (worker/start! worker))))

Enqueue jobs (example_a/enqueue_jobs.clj)

(ns example-a.enqueue-jobs
  (:require [examples.common :as common]
            [next.jdbc :as jdbc]
            [proletarian.job :as job]
            [puget.printer :as puget])
  (:import (java.time Instant)))

(defn run
  [_]
  (let [ds (jdbc/get-datasource (:jdbc-url common/config))]
    (common/preamble ds)
    (println "Adding new job to :proletarian/default queue:")
    (let [conn (jdbc/get-connection ds)
          job-type ::echo
          payload {:message "Hello world!"
                   :timestamp (Instant/now)}
          job-id (job/enqueue! conn job-type payload)]
      (puget/cprint {:job-id job-id
                     :job-type ::echo
                     :payload payload}))))

(defn handle-job!
  [job-type payload]
  (println (str "Running job " job-type ". Payload:"))
  (puget/cprint payload))

Key takeaways

  • job-id UUID — every enqueued job is assigned a unique UUID. You don’t need to track it, but it appears in all log events so you can correlate worker output with specific jobs.
  • :job-type keyword — the job type is a Clojure keyword that determines which proletarian.job/handle-job! multimethod implementation is invoked. Using a namespaced keyword with :: shorthand is idiomatic; ::echo expands to :example-a.enqueue-jobs/echo.
  • Transit serialization — payloads are serialized with Transit and stored in a TEXT column. The serializer is pluggable: implement proletarian.protocols/Serializer and pass it as :proletarian/serializer to both job/enqueue! and worker/create-queue-worker.
  • Configurable polling interval — set via :proletarian/polling-interval-ms. The example uses 5 000 ms for clarity; the built-in default is 100 ms.

Example B: Retries

Retry strategies, failure callbacks, and graceful shutdown with job summaries.

Example C: Shutdown

Graceful shutdown, job interruption, and blocking vs. CPU-bound jobs.

Build docs developers (and LLMs) love