Targets#

A target names a database the run drives. A config that declares no targets drives the single database given by --url and --driver, exactly as it always has. A config that declares several drives them all concurrently from one process, reporting each separately:

target primary(url: env('PRIMARY_URL'))
target replica(url: env('REPLICA_URL'), driver: 'mysql')
OptionRequiredDescription
urlYesConnection URL. Must resolve to a non-empty string.
driverNoDatabase driver. Falls back to --driver when omitted.

Values come from the environment#

Both options are expressions, not bare text, so a target can be pointed at a database by its environment. That is what makes a multi-target config portable - the config names the roles, the environment supplies the addresses:

let host = env('DB_HOST')

target primary(url: 'postgres://' + host + ':26257/defaultdb')
target replica(url: 'postgres://' + host + ':26258/defaultdb')

Because they are expressions, string literals must be quoted:

# Correct.
target primary(url: 'postgres://localhost:26257/defaultdb')

# Wrong - parsed as an expression referring to an unknown name.
target primary(url: postgres://localhost:26257/defaultdb)

Targets resolve before any connection is opened, which is what lets them decide where to connect. env(), globals (let) and arithmetic all work. Anything needing a live database does not, and neither do expr functions, which are compiled later in startup.

url and driver must resolve to strings. env_nil() on an unset variable yields nil, so wrap it: coalesce(env_nil('REPLICA_URL'), 'postgres://localhost:26257/defaultdb').

How many targets#

Targets declaredBehaviour
NoneOne database, from --url / --driver. Metrics are labelled default.
OneOne database, from the target. Same single-stream run, but metrics and output take the target’s name.
Two or moreOne connection and one workload stream per target, run concurrently and rendered side by side with a delta table against the first target.

Which target the other commands use#

up, seed, deseed and down act on a single database: the first declared target, or --url/--driver when the config declares none. Only run drives every target.

Use --target to pick a different one, which is how each target gets its own schema and data:

edg up   --config workload.edg --target replica
edg seed --config workload.edg --target replica

--target works on run too, where it narrows a multi-target config down to that one database instead of driving them all - useful for reproducing one target’s behaviour on its own. Naming a target the config doesn’t declare is an error listing the ones it does.

edg workload all runs up and seed against one target, then runs the workload against all of them. Prepare each target with --target first if they need their own data.

Limits are per target#

--max-queries and --max-qps apply to each target independently rather than across the run. Targets are separate workload streams, and a shared budget would let a fast target starve a slow one, which would make the comparison between them meaningless.

Targets and includes#

Targets can live in an included file, which is the usual way to share one set of connections across configs:

# targets.edg
target primary(url: env('PRIMARY_URL'))
target replica(url: env('REPLICA_URL'))
include 'targets.edg'

Included targets are appended after the including file’s own. A name declared in both files is an error, not an overwrite.