Optimizing Workflows¶
Practical techniques for making muster workflows faster and more reliable. This guide covers only features the workflow engine actually implements; see Workflow Creation and the Workflow CRD reference for full details.
Optimization is purely about how you structure the workflow — composing steps, conditions, and concurrency. There is no separate optimization engine, caching layer, or tuning CLI to configure.
Cut latency with parallel¶
Independent steps should run concurrently instead of one after another. A
parallel group runs its sub-steps at the same time; the workflow continues
once they all finish.
steps:
# Sequential: total time = sum of each deploy
# Parallel: total time = the slowest deploy
- id: deploy_all
parallel:
- id: deploy_frontend
tool: deploy_service
args: { service: "frontend" }
- id: deploy_backend
tool: deploy_service
args: { service: "backend" }
- id: deploy_database
tool: deploy_service
args: { service: "database" }
Siblings in a parallel group are independent: each resolves its arguments from
the workflow state as it was before the group started, so a sub-step cannot read
another sibling's result. Put dependent work in a later step.
Skip unnecessary work with conditions¶
Don't run steps an environment doesn't need. A condition.template is the
cheapest gate — it is evaluated in-process with no tool call:
- id: production_smoke_test
tool: run_smoke_tests
condition:
template: "{{ eq .input.environment \"production\" }}"
Use fromStep to branch on an earlier step's result instead of re-running a
check:
- id: skip_if_already_current
tool: deploy
condition:
fromStep: version_check
expectNot:
jsonPath:
up_to_date: true
Fan out with forEach¶
When the same operation applies to many items (clusters, namespaces, services),
a forEach loop is clearer and less error-prone than copy-pasted steps:
- id: roll_out
forEach:
items: "{{ .input.clusters }}"
as: cluster
steps:
- id: apply
tool: apply_manifest
args:
cluster: "{{ .vars.cluster.name }}"
forEach executes sequentially. For independent items where ordering doesn't
matter and latency does, prefer an explicit parallel group.
Store only what you reuse¶
A step's result is only kept when store: true. Storing everything bloats the
execution record and the template context. Store a result only when a later step
references it via {{ .results.<step_id> }}.
Make failures safe¶
- Mark genuinely optional steps
allowFailure: trueso a non-critical failure doesn't abort the whole workflow. - Add
onFailurerollback steps for workflows that mutate external state, so a failure leaves the system in a known state:
Keep workflows composable¶
Smaller, focused workflows are easier to reason about and reuse. A workflow is
exposed as an action_<name> tool, so one workflow can call another as a step.
This keeps each workflow short and lets you test pieces independently.
Inspect execution to find bottlenecks¶
Use the execution-tracking tools to see where time goes and which steps were skipped or failed:
workflow_execution_list— recent executions and their status.workflow_execution_get— per-step detail (completed,skipped,failed) for a given execution ID.
Use this to confirm that conditions are skipping the steps you expect and that parallel groups are actually overlapping.