Skip to content

Parallel steps

Run steps at the same time, race them, and fan out, with the same outcome on every replay.

Updated View as Markdown

Steps that a workflow starts together run in parallel. EZGH runs each one as its own invocation, retried on its own, and invokes the workflow again as they finish.

Promise.all, Promise.race and Promise.any over steps (including sleeps, callbacks and child workflows) hand every step to EZGH at once:

const [crm, billing] = await Promise.all([
  step.run("crm", () => crm.upsert(user)),
  step.run("billing", () => billing.createCustomer(user)),
])

const sizes = await Promise.all(urls.map((url, i) => step.run(`size-${i}`, () => fetchSize(url))))

const first = await Promise.race([
  step.run("work", doWork),
  step.sleep("limit", "10m").then(() => "timed out"),
])

Go has no implicit concurrency: run branches with Parallel, All or Race. Each branch gets its own Context and must call steps with it.

var crm, billing string
err := workflows.Parallel(ctx,
	func(c workflows.Context) (err error) { crm, err = workflows.Run(c, "crm", upsertCRM); return },
	func(c workflows.Context) (err error) { billing, err = workflows.Run(c, "billing", createCustomer); return },
)
  • Parallel returns once every branch has returned: nil, the one error, or the errors joined in branch order.
  • All returns at the first error (the other branches keep running), or nil once every branch has returned, like Promise.all.
  • Race returns the index and error of the first branch to return. The other branches keep running.

In an async def workflow, asyncio.gather, asyncio.wait and asyncio.as_completed over steps run them in parallel. A plain def workflow uses ctx.step.parallel:

crm, billing = await asyncio.gather(
    ctx.step.run("crm", sync_crm),
    ctx.step.run("billing", bill),
)

# In a plain def workflow
a, b = ctx.step.parallel(("part-a", lambda: part(rows, 0)), ("part-b", lambda: part(rows, 1)))

Rules

  • Steps pending at the same time need distinct names (or explicit IDs), such as size-${i}.
  • A run has at most 32 step invocations at once. Further steps wait their turn, in order.
  • At most 1,000 steps can start together; more fails the run. Start them in batches, or fan out through child workflows.
  • A run has at most 1,000 steps in all, each child workflow counting as one.
  • Races are deterministic. A replay resolves recorded steps one at a time in the order they finished, so a race takes the same branch every time.
  • Each parallel step uses its own retry policy, and a failed step doesn’t stop its siblings.
Navigation

Type to search…

↑↓ navigate↵ selectEsc close