Distributed crontab-style scheduler in Go, backed by PostgreSQL (via River).
Jobs are registered against a group and fire at a planned time (one-shot
@once, cron, or @every). When a job fires, gua delivers a trigger
envelope to the consumer over one of two transports:
- HTTP —
POST <target>with a JSON body - gRPC —
OnJobTrigger(Push: gua dials the consumer's gRPC server)
Both carry the same fields: job_id, job_name, group_name, plan_time,
exec_time, payload. The consumer's HTTP 2xx / gRPC JobResult is the
execution result, recorded in a short-retention history for monitoring.
This is the PostgreSQL line. A Redis-backed version lives on the
hardenbranch; see docs/pg-migration.md for why and how the store moved to Postgres.
- Register / CRUD (consumer → gua) —
RegisterGroup,AddJob,EditJob,PauseJob,ActiveJob,DeleteJob,ListJobsover HTTP REST (/v1/...) or the equivalent gRPCGuaAdmin. - Delivery (gua → consumer, when a job fires) — the trigger envelope as a
JSON
POST(HTTP) orGuaCallback.OnJobTrigger(gRPC Push). - NATS-free — gua nodes are stateless: they all dequeue from one
Postgres with
FOR UPDATE SKIP LOCKED, so each job runs exactly once across the fleet — no slot election, no per-node buckets, no de-dup fence.
Add a node and it just starts pulling; a crashed node's in-flight jobs are reclaimed by River's rescuer. Full write-up (pipeline, HA, schema) in docs/ARCHITECTURE.md.
- PostgreSQL (the only backend) — reachable via
PG_DSN; River runs its own migrations on startup, so no manual schema step. - Go 1.x to build from source (see go.mod for the version).
Brings up Postgres + gua (River runs its own migrations on startup) — no setup:
docker compose -f docker-compose/docker-compose.yml up --build
See it work — register a group, schedule a one-shot job ~5s out, then watch it fire:
curl localhost:7777/version
# 1. register a group
curl -XPOST localhost:7777/v1/groups -d '{"group_name":"demo"}'
# 2. schedule a job that POSTs a trigger envelope in ~5s. Point request_url at a
# catcher you control (e.g. grab a URL from https://webhook.site) to watch it land:
curl -XPOST localhost:7777/v1/groups/demo/jobs -d "{\"name\":\"hi\",\"exec_time\":$(( $(date +%s) + 5 )),\"request_url\":\"HTTP@https://webhook.site/your-id\",\"interval_pattern\":\"@once\",\"payload\":\"hello\"}"
# 3. after it fires, confirm in the execution history
curl localhost:7777/v1/groups/demo/history
Or open the console at http://localhost:7777/ui to register, schedule, and watch history live.
$ ./gua start -e env.example # with an env file
$ ./gua start # or rely on the process environment
Needs a reachable Postgres (PG_DSN); River runs its own migrations on startup.
See env.example for all knobs (Postgres, history retention, logging).
The repo's docker-compose.yml and example/ build from source (for dev /
the demo). To deploy without building, pull the published image from Docker Hub —
syhlion/gua, tagged per release
(:4.0.0 / :4 / :latest):
docker pull syhlion/gua:latest
# give it a reachable Postgres; it runs its own migrations on startup:
docker run --rm -p 7777:7777 -p 6666:6666 \
-e PG_DSN='postgres://user:pass@your-postgres:5432/gua?sslmode=disable' \
-e HTTP_LISTEN=:7777 -e GRPC_LISTEN=:6666 -e MACHINE_CODE=gua-1 \
syhlion/gua:latest startTo pull instead of build in your own compose, swap the build: block for
image: syhlion/gua:<tag>.
- Health:
GET /version(build/version) · web consoleGET /ui. - Monitoring:
GET /v1/status(queue health) ·GET /v1/groups/{group}/history(recent executions). Full reference in docs/MONITORING.md.
Output is selectable and rotated, via env:
| Env | Values |
|---|---|
LOG_OUTPUT |
stdout (default) / file / both |
LOG_FILE |
path (for file / both) |
LOG_FORMAT |
json (default) / text |
LOG_LEVEL |
debug / info (default) / warn / error |
LOG_ROTATE_MAX_SIZE_MB / _MAX_BACKUPS / _MAX_AGE_DAYS / _COMPRESS |
rotation |
- example/ — runnable demo: configure a job, watch gua fire it live (one
docker composecommand) - docs/ARCHITECTURE.md — architecture, pipeline, HA (with diagrams)
- docs/apiv1.md — admin REST API
proto/gua.proto— gRPCGuaAdmin+GuaCallback- docs/MONITORING.md —
/v1/status,/v1/groups/{group}/history,/ui - docs/EVAL.md — JobScheduler replacement evaluation & migration
go test ./... # unit (cron parser)
# integration / stress need Postgres:
GUA_PG_DSN='postgres://user:pass@host:5432/db?sslmode=disable' go test ./delayquene/ -run TestRiver
GUA_STRESS=1 GUA_STRESS_N=2000 GUA_PG_DSN=... go test ./delayquene/ -run TestRiverStress -v
