diff --git a/src/content/docs/workflows/reference/limits.mdx b/src/content/docs/workflows/reference/limits.mdx index 9ea7e5d6673b11..0d201c075aded9 100644 --- a/src/content/docs/workflows/reference/limits.mdx +++ b/src/content/docs/workflows/reference/limits.mdx @@ -24,7 +24,7 @@ Many limits are inherited from those applied to Workers scripts and as documente | Maximum `step.sleep` duration | 365 days (1 year) | 365 days (1 year) | | Maximum steps per Workflow [^5] | 1024 | 1024 | | Maximum Workflow executions | 100,000 per day [shared with Workers daily limit](/workers/platform/limits/#worker-limits) | Unlimited | -| Concurrent Workflow instances (executions) per account | 25 | 10,000 | +| Concurrent Workflow instances (executions) per account [^7] | 25 | 10,000 | | Maximum Workflow instance creation rate | 100 per second [^6] | 100 per second [^6] | | Maximum number of [queued instances](/workflows/observability/metrics-analytics/#event-types) | 10,000 | 100,000 | | Retention limit for completed Workflow state | 3 days | 30 days [^2] | @@ -42,8 +42,55 @@ Many limits are inherited from those applied to Workers scripts and as documente [^6]: Workflows will return a HTTP 429 rate limited error if you exceed the rate of new Workflow instance creation. +[^7]: Only instances with a `running` state count towards the concurrency limits. Instances in the `waiting` state are excluded from these limits. + +### `waiting` instances do not count towards instance concurrency limits + +Instances that are on a `waiting` state - either sleeping, waiting for a retry, or waiting for an event - do **not** count towards concurrency limits. This means that other `queued` instances will be scheduled when an instance goes from a `running` state to a `waiting` one, usually the oldest instance queued, on a best-effort basis. This state transition - `running` to `waiting` - may not occur if the wait duration is too short. + +For example, consider a Workflow that does some work, waits for 30 days, and then continues with more work: + +```ts title="src/index.ts" +import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from 'cloudflare:workers'; + +type Env = { + MY_WORKFLOW: Workflow; +}; + +export class MyWorkflow extends WorkflowEntrypoint { + async run(event: WorkflowEvent, step: WorkflowStep) { + + const apiResponse = await step.do('initial work', async () => { + let resp = await fetch('https://api.cloudflare.com/client/v4/ips'); + return await resp.json(); + }); + + await step.sleep('wait 30 days', '30 days'); + + await step.do( + 'make a call to write that could maybe, just might, fail', + { + retries: { + limit: 5, + delay: '5 second', + backoff: 'exponential', + }, + timeout: '15 minutes', + }, + async () => { + if (Math.random() > 0.5) { + throw new Error('API call to $STORAGE_SYSTEM failed'); + } + }, + ); + } +} +``` + +While a given Workflow instance is waiting for 30 days, it will transition to the `waiting` state, allowing other `queued` instances to run if concurrency limits are reached. + ### Increasing Workflow CPU limits Workflows are Worker scripts, and share the same [per invocation CPU limits](/workers/platform/limits/#worker-limits) as any Workers do. Note that CPU time is active processing time: not time spent waiting on network requests, storage calls, or other general I/O, which don't count towards your CPU time or Workflows compute consumption.