A Redis-backed implementation of the OpenJobSpec (OJS) specification — a standard interface for distributed job queues and workflow orchestration. This backend gives you a production-ready job server with priority queuing, retries, scheduling, cron jobs, workflows, and dead letter handling, all powered by Redis.
- Full OJS compliance — Passes conformance levels 0–4 with 17 supported capabilities
- Priority queuing — Jobs are ordered by priority (-100 to 100) within each queue
- Retry policies — Exponential, linear, and constant backoff with jitter and non-retryable error patterns
- Scheduled jobs — Delay execution until a specific time
- Cron scheduling — Register recurring jobs with standard cron expressions and timezone support
- Workflows — Chain (sequential), group (parallel), and batch execution with callbacks
- Dead letter queue — Inspect, retry, or delete failed jobs that have exhausted retries
- Job deduplication — Unique policies with configurable conflict resolution
- Queue management — Pause/resume queues, view per-queue statistics
- Batch enqueue — Atomically submit multiple jobs in a single request
- Graceful shutdown — Clean signal handling with in-flight request draining
- Go 1.23 or later
- Redis 7.x
- Docker and Docker Compose (optional, for containerized setup)
Clone the repository and build:
git clone https://github.com/openjobspec/ojs-backend-redis.git
cd ojs-backend-redis
make buildThis compiles the server binary to bin/ojs-server.
Start both Redis and the OJS server with a single command:
make docker-upThe server will be available at http://localhost:8080. To stop:
make docker-downStart a Redis instance, then run the server:
# Start Redis (if not already running)
redis-server &
# Run the OJS server
make runcurl http://localhost:8080/ojs/v1/healthcurl -X POST http://localhost:8080/ojs/v1/jobs \
-H "Content-Type: application/json" \
-d '{
"type": "email.send",
"args": [{"to": "[email protected]", "subject": "Hello"}],
"options": {
"queue": "default",
"priority": 10
}
}'curl -X POST http://localhost:8080/ojs/v1/workers/fetch \
-H "Content-Type: application/json" \
-d '{
"queues": ["default"],
"worker_id": "worker-1"
}'curl -X POST http://localhost:8080/ojs/v1/workers/ack \
-H "Content-Type: application/json" \
-d '{
"job_id": "<job-id>",
"worker_id": "worker-1",
"result": {"status": "delivered"}
}'curl -X POST http://localhost:8080/ojs/v1/workers/nack \
-H "Content-Type: application/json" \
-d '{
"job_id": "<job-id>",
"worker_id": "worker-1",
"error": "connection timeout"
}'curl -X POST http://localhost:8080/ojs/v1/cron \
-H "Content-Type: application/json" \
-d '{
"name": "daily-report",
"schedule": "0 9 * * *",
"timezone": "America/New_York",
"type": "reports.generate",
"queue": "reports"
}'curl -X POST http://localhost:8080/ojs/v1/workflows \
-H "Content-Type: application/json" \
-d '{
"type": "chain",
"steps": [
{"type": "extract.data", "queue": "etl"},
{"type": "transform.data", "queue": "etl"},
{"type": "load.data", "queue": "etl"}
]
}'curl -X POST http://localhost:8080/ojs/v1/jobs/batch \
-H "Content-Type: application/json" \
-d '{
"jobs": [
{"type": "email.send", "args": [{"to": "[email protected]"}], "options": {"queue": "default"}},
{"type": "email.send", "args": [{"to": "[email protected]"}], "options": {"queue": "default"}}
]
}'| Method | Endpoint | Description |
|---|---|---|
GET |
/ojs/manifest |
Server manifest (version, capabilities) |
GET |
/ojs/v1/health |
Health check with Redis latency |
| Method | Endpoint | Description |
|---|---|---|
POST |
/ojs/v1/jobs |
Enqueue a job |
GET |
/ojs/v1/jobs/{id} |
Get job details |
DELETE |
/ojs/v1/jobs/{id} |
Cancel a job |
POST |
/ojs/v1/jobs/batch |
Batch enqueue multiple jobs |
| Method | Endpoint | Description |
|---|---|---|
POST |
/ojs/v1/workers/fetch |
Fetch available jobs from queues |
POST |
/ojs/v1/workers/ack |
Acknowledge job completion |
POST |
/ojs/v1/workers/nack |
Report job failure |
POST |
/ojs/v1/workers/heartbeat |
Extend visibility timeout |
| Method | Endpoint | Description |
|---|---|---|
GET |
/ojs/v1/queues |
List all queues |
GET |
/ojs/v1/queues/{name}/stats |
Get queue statistics |
POST |
/ojs/v1/queues/{name}/pause |
Pause a queue |
POST |
/ojs/v1/queues/{name}/resume |
Resume a paused queue |
| Method | Endpoint | Description |
|---|---|---|
GET |
/ojs/v1/dead-letter |
List dead letter jobs |
POST |
/ojs/v1/dead-letter/{id}/retry |
Retry a dead letter job |
DELETE |
/ojs/v1/dead-letter/{id} |
Delete a dead letter job |
| Method | Endpoint | Description |
|---|---|---|
GET |
/ojs/v1/cron |
List registered cron jobs |
POST |
/ojs/v1/cron |
Register a cron job |
DELETE |
/ojs/v1/cron/{name} |
Delete a cron job |
| Method | Endpoint | Description |
|---|---|---|
POST |
/ojs/v1/workflows |
Create a workflow |
GET |
/ojs/v1/workflows/{id} |
Get workflow status |
DELETE |
/ojs/v1/workflows/{id} |
Cancel a workflow |
The server is configured through environment variables:
| Variable | Default | Description |
|---|---|---|
OJS_PORT |
8080 |
HTTP server listen port |
REDIS_URL |
redis://localhost:6379 |
Redis connection URL |
The REDIS_URL follows the standard Redis URI format: redis://[user[:password]@]host[:port][/db]
ojs-backend-redis/
├── cmd/
│ └── ojs-server/
│ └── main.go # Application entry point
├── internal/
│ ├── api/ # HTTP handlers and middleware
│ │ ├── handler_jobs.go # Job CRUD endpoints
│ │ ├── handler_workers.go # Worker fetch/ack/nack/heartbeat
│ │ ├── handler_workflows.go # Workflow orchestration
│ │ ├── handler_dead_letter.go
│ │ ├── handler_batch.go
│ │ ├── handler_queues.go
│ │ ├── handler_cron.go
│ │ ├── handler_system.go # Health and manifest
│ │ ├── middleware.go
│ │ └── errors.go
│ ├── core/ # Domain models and interfaces
│ │ ├── backend.go # Backend interface definition
│ │ ├── job.go # Job and request structs
│ │ ├── state.go # Job state machine
│ │ ├── validate.go # Request validation
│ │ ├── retry.go # Retry policy configuration
│ │ ├── unique.go # Deduplication policy
│ │ └── ...
│ ├── redis/ # Redis backend implementation
│ │ ├── backend.go # Core Redis operations
│ │ ├── codec.go # Job serialization
│ │ └── keys.go # Redis key schema
│ ├── scheduler/ # Background processing loops
│ │ └── scheduler.go
│ └── server/ # HTTP server setup
│ ├── config.go # Environment-based configuration
│ └── server.go # Router and route registration
├── docker/
│ ├── Dockerfile # Multi-stage production build
│ └── docker-compose.yml # Redis + OJS server stack
├── .github/workflows/
│ ├── ci.yml # Build, lint, and test pipeline
│ └── conformance.yml # OJS conformance test suite
├── Makefile
├── go.mod
└── go.sum
| Target | Description |
|---|---|
make build |
Compile the server binary |
make run |
Build and run the server |
make test |
Run tests with race detection and coverage |
make lint |
Run static analysis (go vet) |
make clean |
Remove build artifacts |
make docker-up |
Start Redis + server via Docker Compose |
make docker-down |
Stop Docker Compose services |
make conformance |
Run all OJS conformance test levels |
make conformance-level-N |
Run a specific conformance level (0–4) |
make testThe conformance test suite validates this implementation against the OJS specification. It requires the ojs-conformance repository checked out as a sibling directory:
# With the server running:
make conformance # All levels
make conformance-level-0 # Individual levelContributions are welcome. See CONTRIBUTING.md for setup, testing, and pull request expectations.
Please also review:
The server supports distributed tracing via OpenTelemetry. Set the following environment variable to enable:
export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317Traces are exported in OTLP format over gRPC. Compatible with Jaeger, Zipkin, Grafana Tempo, and any OTLP-compatible collector.
You can also use the legacy env vars OJS_OTEL_ENABLED=true and OJS_OTEL_ENDPOINT for explicit control.
- Rate limiting: This server does not enforce request rate limits. Place a reverse proxy (e.g., Nginx, Envoy, or a cloud load balancer) in front of the server to add rate limiting in production.
- Authentication: Set
OJS_API_KEYto require Bearer token auth on all endpoints. For local-only testing, setOJS_ALLOW_INSECURE_NO_AUTH=true. - TLS: Terminate TLS at a reverse proxy or load balancer rather than at the application level.
See the OpenJobSpec organization for licensing details.