Architecture¶
runqy uses a server-driven bootstrap architecture where workers are stateless and receive all configuration from a central server.
System Overview¶
┌─────────────────────┐ ┌─────────────────┐ ┌───────────────┐
│ runqy-server │ │ Redis │ │ Clients │
│ │───→│ │←───│ (enqueue) │
│ POST /worker/ │ │ - Task queues │ │ │
│ register │ │ - Worker state │ │ │
└─────────────────────┘ │ - Results │ └───────────────┘
│ └─────────────────┘
│ config + ↑
│ deployment │ dequeue/heartbeat
↓ │
┌──────────────────────────────────────────────────────────────────┐
│ runqy-worker (Go) │
│ │
│ Bootstrap: │
│ 1. POST /worker/register → receive Redis creds + git repo │
│ 2. git clone deployment code │
│ 3. Create virtualenv, pip install │
│ 4. Spawn Python process (startup_cmd) │
│ 5. Wait for {"status": "ready"} on stdout │
│ │
│ Run loop: │
│ 1. Dequeue task from Redis │
│ 2. Send JSON to Python via stdin │
│ 3. Read JSON response from stdout │
│ 4. Write result back to Redis │
└──────────────────────────────────────────────────────────────────┘
│
│ stdin/stdout JSON
↓
┌──────────────────────────────────────────────────────────────────┐
│ Python Task (runqy-task SDK) │
│ │
│ @load → runs once at startup, returns ctx (e.g., ML model) │
│ @task → handles each task, receives payload + ctx │
│ run() → enters stdin/stdout loop │
└──────────────────────────────────────────────────────────────────┘
Component Responsibilities¶
runqy-server¶
The server is the central control plane:
- Worker registration: Authenticates workers and provides them with Redis credentials and deployment configuration
- Queue configuration: Defines which queues exist and how tasks should be routed
- Deployment specs: Specifies which git repository, branch, and startup command each queue uses
runqy-worker¶
The worker is the execution engine:
- Bootstrap: Fetches configuration from the server, deploys code, sets up the Python environment
- Task processing: Dequeues tasks from Redis, forwards them to the Python process
- Process supervision: Monitors the Python process health, reports status via heartbeat
- Result handling: Writes task results back to Redis
runqy-task (Python SDK)¶
The SDK provides a simple interface for writing task handlers:
@loaddecorator: Marks a function that runs once at startup (e.g., loading an ML model)@taskdecorator: Marks a function that handles each taskrun()function: Enters the stdin/stdout loop for long-running moderun_once()function: Processes a single task for one-shot mode
Communication Protocol¶
Worker ↔ Python Process¶
Communication uses JSON over stdin/stdout:
Ready signal (Python → Worker, after @load completes):
Task input (Worker → Python):
Task response (Python → Worker):
Redis Key Format¶
runqy uses an asynq-compatible key format:
| Key | Purpose |
|---|---|
asynq:{queue}:pending |
List of pending task IDs |
asynq:{queue}:active |
List of currently processing task IDs |
asynq:t:{task_id} |
Hash containing task data |
asynq:result:{task_id} |
Task result string |
asynq:workers:{worker_id} |
Worker heartbeat hash |
Failure Handling¶
Python Process Crash¶
If the supervised Python process crashes:
- Worker detects the crash via process monitor
- All in-flight tasks fail immediately (no retry)
- Worker updates heartbeat with
healthy: false - Worker continues running but won't process new tasks
- Manual restart is required (no auto-recovery)
Task Failure¶
If a task returns an error:
- Worker writes the error to Redis
- If
retry: truein response, task may be retried (up tomax_retry) - Otherwise, task is marked as failed