Distributed job scheduler and execution engine. Postgres is the source of truth and the coordination mechanism: jobs are claimed with row locks, owned with a lease, and protected with a fencing token.
Week 1 is the job API. It submits, reads, lists, and cancels work. It does not run jobs. Delivery, once workers exist, is at-least-once. See docs/architecture.md, docs/adr, and docs/roadmap.md.
Portfolio: brohammad.tech
POST /v1/jobspersists a job. Omitscheduled_atto make it due now, or send a future time to delay it.Idempotency-Keyreturns the original job when the request matches, and409when the same key is reused with a different body.GET /v1/jobs/{id}andGET /v1/jobsread current state. List filters arestatusandqueue, with keyset pagination.POST /v1/jobs/{id}/cancelcancels a pending job. Cancelling it again is a no-op. A running job cannot be cancelled.- The schema already enforces the state machine, lease shape, and fencing-token rules used by later weeks.
Workers, claiming, heartbeats, crash recovery, retries, dead-letter replay, cron, metrics, and tracing. Those are Weeks 2–7. No Redis, Kafka, or UI.
Requires Go 1.26 and Docker. Copy .env.example if you want a local env template.
make up
make migrate
make runDATABASE_URL defaults to postgres://arden:arden@localhost:5432/arden?sslmode=disable. ADDR defaults to :8080.
docker compose -f deploy/docker-compose.yml up --build starts Postgres and the API together. The API container applies migrations, then listens on port 8080.
curl -s -X POST localhost:8080/v1/jobs \
-H 'content-type: application/json' \
-H 'Idempotency-Key: welcome-email-42' \
-d '{"queue":"email","payload":{"to":"[email protected]"},"max_attempts":5}'GET /healthz does not touch the database. GET /readyz runs SELECT 1.
| Field | Meaning |
|---|---|
queue |
Worker selector. Default default. Pattern ^[a-z0-9][a-z0-9_-]{0,63}$. |
payload |
JSON object, at most 256 KiB. |
priority |
Higher runs first. Default 0. |
scheduled_at |
Earliest run time. Default is now. |
max_attempts |
Claims allowed in the current cycle. Default 5, range 1–100. |
attempt |
Claims in the current cycle. Replay resets this to 0. |
fencing_token |
Increases by 1 on every claim and never goes back down. |
status |
pending, running, succeeded, dead, cancelled. |
Legal changes: pending → running|cancelled, running → succeeded|pending|dead, dead → pending. A running row must hold a lease. The database rejects anything else.
make testIntegration tests start Postgres 17 with testcontainers. Unit tests cover validation, the transition table, and cursors.