Cluster#
The cluster command group provides a distributed workload runner using CockroachDB for coordination. Multiple agents across pods, nodes, clouds, or regions execute workloads with synchronized stage changes and global QPS control.
Architecture#
The cluster uses three components:
- Coordinator - HTTP server that manages jobs, orchestrates stages, and distributes QPS and seed counts across agents
- Agents - Workers that register with the coordinator, receive commands via CockroachDB changefeeds, and execute workloads against a target database
- Coordination database - A CockroachDB instance that stores job history, agent liveness (via row-level TTL), and commands (via sinkless changefeeds)
graph TD
CLI[" edg cluster submit "]
Coord[" Coordinator <br/> edg cluster coordinator "]
CRDB[(" CockroachDB <br/> coordination DB ")]
A1[" Agent 1 <br/> edg cluster agent <br/> us-east "]
AN[" Agent N <br/> edg cluster agent <br/> eu-west "]
Target[(" Target <br/> database ")]
CLI -- " POST /cluster/jobs " --> Coord
Coord -- " write jobs & commands " --> CRDB
CRDB -- " changefeed<br/> commands " --> A1
CRDB -- " changefeed<br/> commands " --> AN
A1 -- " heartbeat &<br/> register " --> CRDB
AN -- " heartbeat &<br/> register " --> CRDB
A1 -- " execute workload " --> Target
AN -- " execute workload " --> TargetSubcommands#
| Command | Description |
|---|---|
edg cluster coordinator | Start the coordinator server |
edg cluster agent | Start a cluster agent |
edg cluster submit | Submit a workload to the cluster |
edg cluster status [id] | Show status of one or all cluster jobs |
edg cluster stream <id> | Stream live progress from a running job |
edg cluster cancel <id> | Cancel a running cluster job |
edg cluster agents | List active agents |
edg cluster reset | Reset coordination database tables |
Coordinator#
Start the coordinator, pointing it at a CockroachDB instance for coordination:
edg cluster coordinator \
--crdb-url "postgres://root@localhost:26257/edg_cluster?sslmode=disable"The coordinator creates the coordination tables on first startup and listens on port 3001 by default.
Token authentication#
Pass --token to require all endpoints to include a matching Edg-Cluster-Token header:
edg cluster coordinator \
--crdb-url "postgres://root@localhost:26257?sslmode=disable" \
--token my-secret-tokenCoordinator flags#
| Flag | Default | Description |
|---|---|---|
--crdb-url | (required) | CockroachDB URL for coordination database |
--port | 3001 | HTTP listen port |
--token | Require this token in Edg-Cluster-Token header |
Coordinator endpoints#
| Method | Path | Description |
|---|---|---|
GET | /healthz | Health check (returns ok) |
POST | /cluster/jobs | Submit a workload config |
GET | /cluster/jobs | List all jobs |
GET | /cluster/jobs/{id} | Get job status |
GET | /cluster/jobs/{id}/stream | Stream live progress via SSE |
DELETE | /cluster/jobs/{id} | Cancel a running job |
GET | /cluster/agents | List active agents |
Agent#
Start an agent, pointing it at the same CockroachDB instance:
edg cluster agent \
--crdb-url "postgres://root@localhost:26257/edg_cluster?sslmode=disable" \
--region us-eastThe agent registers itself, heartbeats every 10 seconds, and watches for commands via a CockroachDB sinkless changefeed. If the agent crashes or stops heartbeating, it is automatically reaped by row-level TTL within 30 seconds.
Agent flags#
| Flag | Default | Description |
|---|---|---|
--crdb-url | (required) | CockroachDB URL for coordination database |
--region | (required) | Agent’s region label (used for metrics and routing) |
--token | Authentication token |
Submit#
Submit a workload to the cluster:
edg cluster submit \
--coordinator-url http://localhost:3001 \
--target-url "postgres://root@localhost:26257?sslmode=disable" \
--config workload.edg \
--duration 10m \
--workers 10 \
--regions us-east,eu-west \
--streamSubmit flags#
| Flag | Short | Default | Description |
|---|---|---|---|
--coordinator-url | http://localhost:3001 | Coordinator HTTP URL | |
--target-url | (required) | Target database URL for workload execution | |
--driver | pgx | Target database driver | |
--regions | Comma-separated target regions (empty = all agents) | ||
--sections | Lifecycle sections to run, comma-separated; must include run | ||
--duration | -d | 1m | Run duration |
--workers | -w | 1 | Concurrent workers per agent |
--token | Authentication token | ||
--stream | false | Stream live logs after submitting | |
--env | -e | Set an environment variable for env()/env_nil(), as KEY=VALUE (repeatable) | |
--env-file | Read environment variables for env()/env_nil() from a KEY=VALUE file |
Separating seed from run#
By default each agent runs whichever of up and seed the config defines
before its first stage. --sections narrows that:
# Point the fleet at an estate that already exists.
edg cluster submit --config workload.edg --target-url "$URL" --sections run -d 10mThis matters more in cluster mode than on a single process, because otherwise every agent re-checks the same schema before any of them can start.
A cluster submission must include
run. Agents build their runner when the first stage command arrives, so a prepare-only job would dispatch nothing and complete having built nothing; the coordinator rejects it with a 400 rather than accept a submission that does nothing. Seed withedg jobs submit --sections up,seedor locally withedg seed.
deseedanddownare accepted but have never been run by cluster agents, so naming them changes nothing.
Per-job environment#
env('X') and env_nil('X') read the OS environment of whichever process
evaluates the config. In cluster mode that process is the agent, whose
environment is fixed at deploy time - so without --env, every
coalesce(env_nil('...'), default) in a submitted config falls back to its
default across the whole fleet.
--env attaches key/value pairs to the submission itself. The coordinator
stores them with the job and the agent resolves env()/env_nil() against
them, falling back to its own OS environment for anything not set. That makes
one config file parameterisable per submission:
edg cluster submit --config workload.edg --target-url "$URL" \
-e SHAPE_PEAK_HOUR=3 -e SHAPE_AMP=0.9
edg cluster submit --config workload.edg --target-url "$URL" \
--env-file ./peak.envSHAPE_PEAK_HOUR and SHAPE_AMP mean nothing to edg - workload.edg reads
them with env_nil(). See Shapes
for a config that does.
--env is repeatable rather than comma-separated, so a value containing a
comma survives intact. --env-file is a deliberately dumb KEY=VALUE per
line format: # comments and blank lines are skipped, and a value is the rest
of the line verbatim - no quote stripping, no escapes, no variable expansion.
An explicit --env beats --env-file, and a job’s env beats the agent’s OS
environment.
Use this for parameters, not secrets. Job env is stored in the coordination database in plaintext and is readable by anyone who can read
cluster_jobs.
Job env does not change the agent’s process environment. It only affects
env()/env_nil() while that job’s config is evaluated, so a submitted job
cannot repoint the agent’s own coordination database or license key, and the
next job the agent runs sees only its own env.
Status#
edg cluster status --coordinator-url http://localhost:3001
edg cluster status <job-id> --coordinator-url http://localhost:3001Status flags#
| Flag | Default | Description |
|---|---|---|
--coordinator-url | http://localhost:3001 | Coordinator HTTP URL |
--token | Authentication token |
Stream#
edg cluster stream <job-id> --coordinator-url http://localhost:3001Stream flags#
| Flag | Default | Description |
|---|---|---|
--coordinator-url | http://localhost:3001 | Coordinator HTTP URL |
--token | Authentication token |
Cancel#
edg cluster cancel <job-id> --coordinator-url http://localhost:3001Returns 409 Conflict if the job is not running.
Cancel flags#
| Flag | Default | Description |
|---|---|---|
--coordinator-url | http://localhost:3001 | Coordinator HTTP URL |
--token | Authentication token |
Agents List#
edg cluster agents --coordinator-url http://localhost:3001Agents flags#
| Flag | Default | Description |
|---|---|---|
--coordinator-url | http://localhost:3001 | Coordinator HTTP URL |
--token | Authentication token |
Reset#
Drop and recreate all coordination tables. Useful for clearing stale state or starting fresh:
edg cluster reset \
--crdb-url "postgres://root@localhost:26257/edg_cluster?sslmode=disable"Reset flags#
| Flag | Default | Description |
|---|---|---|
--crdb-url | (required) | CockroachDB URL for coordination database |
Global QPS Distribution#
When a job specifies QPS limits (via stages in the config), the coordinator divides the target QPS evenly across active agents:
per_agent_qps = ceil(total_qps / active_agent_count)The coordinator monitors agent counts every 5 seconds. When an agent joins or leaves, QPS is automatically redistributed via a changefeed command.
Agent Liveness#
Agents heartbeat every 10 seconds by updating their last_seen timestamp. CockroachDB row-level TTL automatically deletes agents that haven’t heartbeated in 30 seconds. When an agent is reaped, the coordinator detects the count change and redistributes QPS to the remaining agents.
Per-Region Metrics#
When running in cluster mode, all Prometheus metrics include a region label matching the agent’s --region flag. This enables per-region dashboards and alerts:
rate(edg_query_duration_seconds_sum{region="us-east"}[1m])
/ rate(edg_query_duration_seconds_count{region="us-east"}[1m])Persistent Job History#
Unlike the jobs server (which stores history in memory), cluster jobs are persisted in CockroachDB. Job history survives coordinator restarts.
Quick Start#
# 1. Start CockroachDB
cockroach start-single-node --insecure --listen-addr=localhost:26257
# 2. Create the coordination database
cockroach sql --insecure -e "CREATE DATABASE edg_cluster"
# 3. Start the coordinator
edg cluster coordinator \
--crdb-url "postgres://root@localhost:26257/edg_cluster?sslmode=disable"
# 4. Start agents (in separate terminals)
edg cluster agent \
--crdb-url "postgres://root@localhost:26257/edg_cluster?sslmode=disable" \
--region us-east
edg cluster agent \
--crdb-url "postgres://root@localhost:26257/edg_cluster?sslmode=disable" \
--region eu-west
# 5. Submit a workload
edg cluster submit \
--target-url "postgres://root@localhost:26257?sslmode=disable" \
--config workload.edg \
--duration 5m \
--workers 10 \
--stream