A distributed task queue built with Rust, Axum, and PostgreSQL.
Uses SELECT FOR UPDATE SKIP LOCKED for concurrent job claiming — no Redis or RabbitMQ needed. Postgres handles the queue, the locking, and the persistence.
- Submit jobs via HTTP API with priority and scheduling
- Background workers poll and execute jobs concurrently
- Exponential backoff retries (2s, 4s, 8s, ...) with configurable max attempts
- Jobs that exhaust retries land in a
deadstate for review - Stale job reaper recovers work from crashed workers
- Graceful shutdown — workers finish in-flight jobs before exiting
- Auth (JWT + cookies) and dashboard endpoints
- Axum — HTTP server
- sqlx — Postgres queries (compile-time checked)
- Tokio — async runtime, background task spawning
- PostgreSQL — queue backend, job persistence
- tracing — structured logging
Start Postgres:
docker compose up -dCopy .env.example to .env (or use the defaults), then:
cd apps/backend
cargo runYou should see workers start polling and the server listening on :8000.
Swagger docs at http://localhost:8000/docs.
# register + grab token
curl -s localhost:8000/api/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"test@test.com","name":"Test","password":"password123"}' | jq .data.access_token
# submit a job
curl -s localhost:8000/api/jobs \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"job_type":"flaky_task","priority":3,"max_retries":5}' | jqJob types included for demo: fast_task, slow_task, flaky_task, critical_report. These simulate varying durations and failure rates.
Requires Postgres running on localhost:5432.
cargo testEach test gets its own database, so they don't interfere with each other or your dev data.
- Producer inserts a row into
jobswith statuspending - Worker runs
SELECT ... FOR UPDATE SKIP LOCKEDto atomically claim a job - Job transitions to
running, worker executes the handler - On success →
completed. On failure → back topendingwith backoff delay, ordeadif retries exhausted - Stale job reaper periodically finds orphaned
runningjobs and resets them
The partial index WHERE status = 'pending' on the jobs table keeps polling fast regardless of how many completed jobs accumulate.
apps/backend/
├── src/
│ ├── api/ # HTTP handlers (auth, users, jobs)
│ ├── middleware/ # JWT auth middleware
│ ├── models/ # Request/response types, DB entities
│ ├── repository/ # Database queries
│ ├── services/ # JWT, password hashing
│ ├── worker/ # Job handler trait, registry, polling loop, reaper
│ ├── config.rs
│ ├── error.rs
│ ├── state.rs
│ └── main.rs
├── migrations/
└── tests/