feat(06-02): add just worker target and document worker in README

- justfile: worker target depends on db-up, passes MinIO dev defaults
  (DATABASE_URL, S3_ENDPOINT/BUCKET/REGION/ACCESS_KEY/SECRET_KEY/USE_PATH_STYLE)
- README: replace skeleton section with full "Running the Worker" docs
  (just worker command, expected logs, single-worker constraint, graceful shutdown,
   failed job retry observation)
This commit is contained in:
Arthur Belleville 2026-05-15 16:38:01 +02:00
parent 6e70478417
commit e202ad3a9e
No known key found for this signature in database
2 changed files with 62 additions and 7 deletions

View file

@ -66,7 +66,7 @@ identical. If you don't have podman:
backend/ backend/
cmd/ cmd/
web/main.go # HTTP server entry point web/main.go # HTTP server entry point
worker/main.go # background worker (skeleton — boot/log/shutdown only) worker/main.go # background worker — river periodic jobs (Phase 6)
internal/ internal/
db/ # pgxpool wiring + sqlc-generated queries db/ # pgxpool wiring + sqlc-generated queries
web/ # chi router, handlers, middleware, design-system web/ # chi router, handlers, middleware, design-system
@ -123,17 +123,61 @@ Every command in this table is a recipe in `backend/justfile`.
| `just build` | Generates assets, then builds `bin/web` and `bin/worker` | Producing release binaries locally | | `just build` | Generates assets, then builds `bin/web` and `bin/worker` | Producing release binaries locally |
| `just clean` | Removes `bin/`, `tmp/`, `static/htmx.min.js`, `static/tailwind.css`, and `*_templ.go` files | Reset to a fresh-clone state without dropping the Postgres volume | | `just clean` | Removes `bin/`, `tmp/`, `static/htmx.min.js`, `static/tailwind.css`, and `*_templ.go` files | Reset to a fresh-clone state without dropping the Postgres volume |
## Worker (skeleton — Phase 1 only) ## Running the Worker
`cmd/worker` in Phase 1 boots, logs `worker ready`, and idles waiting for a `cmd/worker` is the background job processor. It runs river periodic jobs against
signal. Real job runtime lands in Phase 6 (D-03). To run it manually: the same Postgres as `cmd/web`. Start it with:
``` ```
DATABASE_URL=postgres://xtablo:xtablo@localhost:5432/xtablo?sslmode=disable \ just worker
go run ./cmd/worker
``` ```
Ctrl-C to exit. This requires `just db-up` (handled automatically as a dependency) and MinIO
running (used by the orphan-file cleanup job). If MinIO is not running, the worker
will exit on startup with "file store init failed".
### What to expect
- Structured logs appear immediately at startup.
- A `"worker ready"` log line appears within a few seconds after `rivermigrate`
and S3 init complete.
- A `"worker heartbeat"` log line appears almost immediately (the heartbeat job
is configured with `RunOnStart: true`, so it fires on the first scheduler tick
which happens within seconds of startup).
- Subsequent heartbeat logs appear every ~1 minute.
- The orphan-file cleanup job runs every hour (no `RunOnStart` — first run is
~1 hour after startup).
### Single-worker constraint
**Run only one worker process at a time (v1).** River uses advisory locks for
leader election and concurrent rivermigrate runs are unsafe. Do not run multiple
worker instances against the same database in this version.
### Graceful shutdown
Send SIGINT (Ctrl+C) and observe:
```
{"level":"INFO","msg":"shutting down"}
{"level":"INFO","msg":"shutdown complete"}
```
The worker calls `riverClient.StopAndCancel` with a 10-second timeout, which
cancels in-flight job contexts and waits for goroutines to exit before closing
the pool.
### Observing failed job retries
River logs each failure via the `SlogErrorHandler`. A failed job produces a log
line like:
```
{"level":"ERROR","msg":"job error","job_id":42,"job_kind":"heartbeat","attempt":1,"max_attempts":25,"err":"..."}
```
River retries up to 25 times with exponential backoff (`attempts^4` + jitter).
After 25 failed attempts the job is moved to the discarded state in `river_job`.
## Troubleshooting ## Troubleshooting

View file

@ -112,6 +112,17 @@ dev: db-up
just generate just generate
DATABASE_URL='{{ database_url }}' SESSION_SECRET=191affeb1624de1f0e07bd5cfab14cd655510a24f7e673bd784ea56847890caf air -c .air.toml DATABASE_URL='{{ database_url }}' SESSION_SECRET=191affeb1624de1f0e07bd5cfab14cd655510a24f7e673bd784ea56847890caf air -c .air.toml
# Start the worker binary (development — requires db-up and MinIO running).
worker: db-up
DATABASE_URL='{{ database_url }}' \
S3_ENDPOINT='http://localhost:9000' \
S3_BUCKET='xtablo' \
S3_REGION='us-east-1' \
S3_ACCESS_KEY='minioadmin' \
S3_SECRET_KEY='minioadmin' \
S3_USE_PATH_STYLE='true' \
go run ./cmd/worker
test: test:
just generate just generate
go test ./... go test ./...