Distributed Task Queue
Build a distributed task queue in Go, backed by PostgreSQL. Producers submit tasks (a type and a JSON payload) through a gRPC API; any number of worker processes, on any number of machines, take tasks, process them and report the result. Each task is leased to one worker at a time: if the worker crashes, the lease expires and another worker picks the task up. Failed tasks are retried with exponential backoff, and tasks that keep failing go to a dead-letter queue for a human to inspect. Every task also has an idempotency key, so the same task submitted twice runs only once. You'll learn queue design on a relational database with SELECT ... FOR UPDATE SKIP LOCKED, leases and heartbeats, at-least-once delivery and idempotency, gRPC streaming and testing concurrent systems.
- Estimate
- ~15h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
gRPC · PostgreSQL
Project roadmap
- 01
Design the schema
~2hWrite migrations for a tasks table: id, queue name, type, payload (jsonb), status (queued, leased, succeeded, failed or dead), attempts, max attempts, run_after, lease owner and lease expiry, idempotency key (unique per queue), last error and timestamps. Add a partial index on (queue, run_after) for queued tasks, so finding the next task stays fast. Use pgx for database access. Done when: the migrations create the table and indexes on an empty database.
- 02
Define the gRPC API
~2hWrite queue.proto with a TaskQueue service: Enqueue (queue, type, payload, idempotency key, max attempts, optional delay), Lease (a worker id, queues and a lease duration, returning a task or nothing), Heartbeat (extend a lease), Complete, Fail (with an error and whether to retry) and GetTask. Generate the Go code and implement Enqueue with INSERT ... ON CONFLICT on the idempotency key, returning the existing task on a duplicate. Done when: enqueuing the same idempotency key twice returns the same task id.
- 03
Lease tasks safely
~2.5hImplement Lease in one transaction: select the oldest queued task whose run_after has passed, with FOR UPDATE SKIP LOCKED, and mark it leased with the worker id and an expiry time. SKIP LOCKED lets many workers lease at the same time without blocking each other or getting the same task. Heartbeat extends the expiry only if the caller still owns the lease. Done when: 10 concurrent workers never receive the same task.
- 04
Complete, fail and retry
~2hComplete marks a task succeeded only if the caller still owns an unexpired lease, since otherwise another worker may already be running it. Fail increases attempts and, if attempts remain, puts the task back to queued with run_after = now + base × 2^attempts plus random jitter. Otherwise it moves the task to dead with the error. Done when: a task that fails twice then succeeds ends as succeeded, and one that always fails ends as dead.
- 05
Recover expired leases
~1.5hRun a reaper every few seconds, in the server, that puts tasks whose lease has expired back to queued (counting it as an attempt), so a crashed worker's tasks aren't lost. Make sure a slow worker that finishes after its lease expired can't overwrite the new result: its Complete call must be rejected. Done when: killing a worker in the middle of a task leads another worker to finish it.
- 06
Write a worker library and dead-letter tools
~2.5hWrite a Go worker package: register a handler per task type, poll Lease with backoff when the queue is empty, send heartbeats while a handler runs, and report Complete or Fail. Handlers must be idempotent, since a task can run more than once. Add a CLI to list dead tasks, show their errors and requeue them. Done when: a sample worker processes tasks end to end, and dead tasks can be requeued with the CLI.
- 07
Test under concurrency and failure
~2.5hWrite integration tests with a real PostgreSQL (testcontainers-go): no task is leased twice by 20 concurrent workers, idempotent enqueue, retries and the dead-letter queue, lease expiry after a simulated crash, and a late Complete being rejected. Run them with -race. Done when: go test -race ./... passes reliably when run several times.
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack