Distributed Job Worker
Build a job worker system in Rust where jobs are taken safely by many workers at once: each worker leases a job for a limited time, renews the lease while it works, and reports the result exactly once, even if it crashes, the network drops or two workers try to take the same job. You'll build both the coordinator (a small HTTP service that keeps jobs and leases in a SQLite database) and the async workers with Tokio, with retries, backoff and a dead-letter list for jobs that keep failing. Messages are defined with Serde. You'll learn lease-based distributed coordination, idempotency, fencing tokens that stop stale workers from overwriting results, async Rust, and testing failures on purpose.
- Estimate
- ~16h
- Steps
- 7
- Completed by
- 0
- Proposed by
- codeseed.app
Tokio · Serde
Project roadmap
- 01
Design the protocol
~2.5hWrite DESIGN.md: a job (id, kind, payload as JSON, status, attempts, max attempts, run after, lease owner, lease expiry and a lease token that increases each time the job is leased), and the API: POST /jobs (with an idempotency key), POST /lease (returns a job and its token, or nothing), POST /jobs/{id}/renew, POST /jobs/{id}/complete and /fail (each with the token). Explain why a stale token must be rejected. Done when: DESIGN.md covers every state and failure case.
- 02
Build the coordinator
~3hBuild the coordinator with Tokio, axum and SQLite (through sqlx), with Serde types for every request and response. Leasing picks the oldest available job in a single transaction (an UPDATE ... RETURNING on one row), increasing its token and setting the expiry. Enqueue with an existing idempotency key returns the existing job. Done when: leasing from many concurrent requests never gives the same job to two workers.
- 03
Renew, complete and reject stale workers
~2hRenew extends the lease only when the token matches and the lease hasn't expired. Complete and fail only succeed with the current token, so a worker whose lease expired and was taken over can't overwrite the new result (answer 409). Completing twice with the same token is accepted without changing anything, so retried requests are safe. Done when: tests show stale tokens rejected, and duplicate completions ignored.
- 04
Retry and recover
~2hOn failure, schedule the job again with exponential backoff and jitter (setting run after) until max attempts, then move it to dead. A background task in the coordinator makes jobs with expired leases available again, counting it as an attempt. Add endpoints to list and requeue dead jobs. Done when: a job that fails twice then succeeds completes, and a job whose worker vanished is leased again.
- 05
Write the worker
~2.5hWrite a worker binary that runs N concurrent job loops with Tokio: lease a job, run its handler (chosen by kind) while a separate task renews the lease every third of its duration, then complete or fail with the token. If renewal fails, cancel the handler. Retry calls to the coordinator on network errors, and shut down cleanly on Ctrl+C. Done when: several workers process 1,000 jobs with each finished exactly once.
- 06
Add observability
~1.5hAdd structured logs with tracing (job id, kind, attempt and worker) and a /stats endpoint on the coordinator with jobs by status, average time waiting and running, and lease expirations. Done when: /stats and the logs show what the system is doing.
- 07
Test failures on purpose
~2.5hWrite integration tests that start the coordinator on a random port with a temporary database: concurrent leasing, idempotent enqueue, a worker killed mid-job (its lease expires and another finishes it), a slow worker whose late completion is rejected, retries into the dead list, and duplicate completion requests. Done when: cargo test passes reliably when run several times.
Resources
Ready to build this?
Get a GitHub repo and start building. Your AI reviewer checks each step as you go.
Tech stack