Submit and monitor Slurm jobs with iris-compute jobs¶
iris-compute is the CLI for the compute bounded context:
it registers a TaskSpec/TaskRun pair per submission, dispatches it to Slurm via
SlurmJobDispatcher, and tracks its lifecycle in Redis (RedisTaskRunRepository).
It exists so that submitting and monitoring cluster jobs from projects/ scripts
goes through the same domain model that a future API/UI will drive — the CLI
commands are thin wrappers over reusable use cases in
iris.applications.compute.use_cases.
This guide walks through the jobs sub-app end to end using a real two-step
genomics pipeline as the workload, so you can verify a fresh Slurm + Redis
environment works before wiring it into anything bigger.
Prerequisites¶
- Redis reachable at
IRIS_REDIS_URL(defaultredis://localhost:6379/0). Any reachable instance works for a smoke test — this does not have to be the same Redis used for theeventsstreams. - Slurm binaries (
sbatch,squeue,sacct,scancel) onPATH— true by default on any cluster login node. - A GENCODE annotation, HGNC JSON dump, the WES Zarr cohort store,
and a reachable VEP Flight server / MCPS AF tables — the same data
dependencies
projects/recgenes/already uses. Default paths point at the cluster'scephfslocations; override with flags if yours differ.
export IRIS_REDIS_URL=redis://localhost:6379/0
export IRIS_COMPUTE_SUBMIT_DIR=~/.iris/compute/slurm # rendered .sbatch scripts, one per run
export IRIS_COMPUTE_LOG_DIR=~/.iris/compute/slurm/logs # defaults to $IRIS_COMPUTE_SUBMIT_DIR/logs
The example workload¶
projects/examples/gene_variant_extraction.py has two subcommands,
deliberately split so they can run as two dependent Slurm jobs:
make-bed— resolves a gene symbol to its canonical transcript's exons (+ splice buffer) using theresolve_gene_regionsuse case (GENCODE + HGNC alias fallback), then writes them to a BED file withBedRegionExporter.extract— reads that BED back withBedRegionReader, opens the WES Zarr cohort store with the existingPerIndividualAltCountProcessorgenotype processor, runsVariantExtractionPipelinewithVepFlightAnnotatorMcpsAFAnnotator, and exports the annotated variants to TSV viaTsvVariantExporter.
dbSNP rsIDs need no separate annotator: ZarrVariantStore already reads them
from the source VCF's ID field into VariantNomenclature.dbsnp_id, exposed
as the dbsnp column in the TSV output.
Try it locally first, outside Slurm, to confirm paths and connectivity:
uv run python projects/examples/gene_variant_extraction.py make-bed \
--gene BRCA1 --output-bed /tmp/BRCA1.exons.bed
uv run python projects/examples/gene_variant_extraction.py extract \
--bed /tmp/BRCA1.exons.bed --output /tmp/BRCA1.variants.tsv
Submit the first job¶
iris-compute jobs submit brca1_bed \
"uv run python projects/examples/gene_variant_extraction.py make-bed --gene BRCA1 --output-bed results/BRCA1.exons.bed" \
--workdir "$(pwd)" \
--cpus 2 --mem-mb 4000 --time-s 600
The command line is a single string parsed with shlex, exactly like typing it
in a shell — this avoids ambiguity between iris-compute's own flags and the
payload's flags. submit maps flags onto the domain's scheduler-neutral
ResourceRequest / DispatchHints, which SlurmJobDispatcher then translates
into sbatch options:
jobs submit flag |
Domain field | Slurm option |
|---|---|---|
--cpus |
ResourceRequest.cpus_per_rank |
--cpus-per-task |
--mem-mb |
ResourceRequest.memory_mb |
--mem |
--time-s |
ResourceRequest.time_limit_s |
--time |
--nodes |
ResourceRequest.nodes |
--nodes |
--gpus |
ResourceRequest.gpus |
--gres=gpu:N |
--queue |
DispatchHints.queue |
--partition |
--project |
DispatchHints.project |
--account |
--quality |
DispatchHints.quality |
--qos |
--nodelist |
DispatchHints.extra (raw escape hatch) |
--nodelist |
--env KEY=VALUE (repeatable) |
CommandSpec.env |
export KEY=VALUE in the rendered script |
Save the printed run_id — every other jobs command addresses a job by it,
not by the Slurm job id.
Check on it¶
run_id: run_44a36f211a69440c8bfc0335bcd52077
task: brca1_bed (task_1eebc66eddf840af94e2b0352789ded3)
status: SUBMITTED
slurm_id: 424242
status is whatever was last written to Redis — it does not query Slurm
itself. Slurm has no push notifications, so state only changes on the next
jobs poll (see below). List every run known to IRIS, optionally filtered:
Sync state with poll¶
poll fetches every run that is not yet terminal, calls
SlurmJobDispatcher.poll() (squeue, falling back to sacct for jobs that
already left the queue), and folds the resulting JobObservation into a
domain event (TaskQueued / TaskRunStarted / TaskRunSucceeded /
TaskRunFailed / …). Run it again once the job finishes and status moves to
SUCCEEDED or FAILED. Since nothing pushes changes to IRIS, poll needs to
be invoked periodically — a cron entry works well for a first environment test:
* * * * * IRIS_REDIS_URL=redis://localhost:6379/0 iris-compute jobs poll >> ~/.iris/compute/poll.log 2>&1
Chain the second job with --depends-on¶
Once make-bed reaches SUCCEEDED, submit extract so it depends on it:
iris-compute jobs submit brca1_extract \
"uv run python projects/examples/gene_variant_extraction.py extract --bed results/BRCA1.exons.bed --output results/BRCA1.variants.tsv --workers 4" \
--workdir "$(pwd)" \
--cpus 4 --mem-mb 16000 --time-s 3600 \
--depends-on run_44a36f211a69440c8bfc0335bcd52077
--depends-on (repeatable) resolves the given run_ids to their Slurm job ids
and passes --dependency=afterany:<job_id>[:<job_id>...] to sbatch — the
second job won't start until the first one finishes, regardless of whether
it succeeded (afterany, the default; override with --dependency-condition
afterok to require success). This is the domain-CLI equivalent of the
--array=0-3%1 throttling used in projects/recgenes/*.slurm to keep
concurrent jobs from saturating shared I/O (Zarr, dbNSFP, VEP Flight) — except
each step is now its own tracked TaskRun instead of an opaque array index.
A dependency must already have a Slurm job id (i.e. have been submitted) or
submit fails fast instead of producing a broken sbatch --dependency:
Cancel a job¶
cancel only issues scancel — the tracked status still shows whatever it
was before cancellation until the next jobs poll picks up Slurm's
CANCELLED state and applies it.
Environment variables¶
| Variable | Used by | Default |
|---|---|---|
IRIS_REDIS_URL |
build_repository() |
redis://localhost:6379/0 |
IRIS_COMPUTE_SUBMIT_DIR |
build_dispatcher() |
~/.iris/compute/slurm |
IRIS_COMPUTE_LOG_DIR |
build_dispatcher() |
$IRIS_COMPUTE_SUBMIT_DIR/logs |
Troubleshooting¶
sbatch failed with exit=...—submit_dircouldn't be created, or the cluster rejected the resource request (check--mem-mb/--time-sagainst the partition's limits). The rendered script is left underIRIS_COMPUTE_SUBMIT_DIR/<run_id>.sbatchfor inspection either way.jobs statusstaysSUBMITTEDforever — nothing has calledjobs pollyet; it is not automatic.Connection refusedfrom Redis — checkIRIS_REDIS_URLand that the Redis process is reachable from the node running the CLI (the submitting shell, not the compute node the job itself runs on).
Reference¶
iris.applications.compute.use_cases.submit_task
¶
Use case: build a TaskSpec from flat parameters and dispatch it to Slurm.
Sequencing (--depends-on in the CLI) is expressed as a Slurm
--dependency=<condition>:<job_id>[:<job_id>...] via the dispatcher's raw
escape hatch (DispatchHints.extra, see
:meth:SlurmJobDispatcher._build_sbatch_args
<iris.adapters.driven.dispatchers.slurm.SlurmJobDispatcher._build_sbatch_args>)
rather than by extending the domain, which does not model dependency-aware
dispatch. Consequently a dependency must already have been submitted (i.e.
carry an external_ref with a Slurm job id) before it can be depended on —
mirroring how sbatch --dependency itself requires an existing job id.
DependencyNotSubmittedError
¶
Bases: RuntimeError
Raised when a depends_on run does not exist or has no Slurm job id yet.
submit_task
¶
submit_task(
*,
repo,
dispatcher,
name,
argv,
workdir=None,
env=None,
cpus_per_rank=1,
memory_mb=None,
time_limit_s=None,
nodes=None,
gpus=0,
queue=None,
project=None,
quality=None,
nodelist=None,
depends_on=None,
dependency_condition="afterany",
)
Register a :class:TaskSpec, persist a fresh :class:TaskRun, and dispatch it.
Returns the run as left by :class:TaskDispatchRequestedHandler: SUBMITTED
with external_ref carrying the Slurm job id.
| RAISES | DESCRIPTION |
|---|---|
DependencyNotSubmittedError
|
If any |
Source code in src/iris/applications/compute/use_cases/submit_task.py
iris.applications.compute.use_cases.poll_tasks
¶
Use case: sync every non-terminal run against Slurm's live state.
Slurm has no push notifications, so this is meant to be invoked periodically (cron, systemd timer, or a poll loop in a future API/UI process).
poll_tasks
¶
Poll Slurm for every tracked, non-terminal run and apply the resulting events.
Requires :class:RedisTaskRunRepository rather than the generic
TaskRunRepository port: enumerating runs by status is a query-side
concern the domain port does not declare (it only knows point lookups).
Source code in src/iris/applications/compute/use_cases/poll_tasks.py
iris.applications.compute.use_cases.list_runs
¶
Use case: list known TaskRuns, optionally filtered by status.
list_runs
¶
Return known runs, filtered by status (e.g. "RUNNING") when given.