What is a step?
A step is a function decorated with@DBOS.step() that performs a single unit of work within a workflow. Steps are idempotent and automatically retried on failure. When a step completes successfully, its result is stored in Postgres so it won’t re-execute during workflow recovery.
Why use steps?
Steps serve two critical purposes:Non-deterministic operations
Safely execute operations that may produce different results each time (API calls, random numbers, timestamps)
Automatic retry
Built-in retry logic with exponential backoff for transient failures
Idempotency
Results are cached after successful execution, preventing duplicate operations during recovery
Error isolation
Step failures don’t crash the entire workflow; they’re retried automatically
Step decorator
The@DBOS.step() decorator accepts optional parameters:
Custom name for the step. Defaults to the function name.
Whether to retry the step on failure.
Initial retry interval in seconds (doubles with each retry).
Maximum number of retry attempts before raising an exception.
Multiplier for retry interval after each failure.
Retry behavior
When a step fails (raises an exception), DBOS automatically retries it with exponential backoff:- First attempt fails
- Wait
interval_seconds(default: 1 second) - Second attempt fails
- Wait
interval_seconds * backoff_rate(default: 2 seconds) - Third attempt fails
- Wait
interval_seconds * backoff_rate²(default: 4 seconds) - Continue until
max_attemptsis reached
When to use steps
✅ Use steps for:
- External API calls (REST, gRPC, third-party services)
- File I/O operations (reading/writing files)
- Getting current time or generating random numbers
- Calling non-DBOS code that may be non-deterministic
- Long-running computations that you want to checkpoint
❌ Don’t use steps for:
- Database queries (use
@DBOS.transaction()instead) - Simple deterministic calculations (just write them in the workflow)
- Calling other workflows (call workflows directly)
Steps vs transactions
| Feature | Step | Transaction |
|---|---|---|
| Purpose | External operations, non-deterministic logic | Database operations |
| Execution | Can make any external calls | Must only access the database |
| Retry | Automatic with exponential backoff | Automatic on deadlock/serialization errors |
| Database access | Not recommended | Required |
| Idempotency | Enforced by DBOS | Enforced by DBOS |
Async steps
Steps can be async functions for improved concurrency:Step context
Access step metadata and utilities via the context:Disabling retries
For operations that should not be retried (like sending notifications), disable retries:Even with
retries_allowed=False, the step will still be checkpointed and won’t re-execute during workflow recovery.Error handling
Handle step errors in your workflow:Best practices
Keep steps focused
Keep steps focused
Each step should do one thing well. Break complex operations into multiple steps for better granularity and observability.
Make steps idempotent
Make steps idempotent
Design steps so that running them multiple times with the same input produces the same result.
Configure appropriate retries
Configure appropriate retries
Set
max_attempts and interval_seconds based on the operation’s characteristics.Use descriptive step names
Use descriptive step names
Custom step names improve observability and debugging.
Next steps
Transactions
Learn about database transactions
Workflows
Learn about durable workflows
Error handling guide
Advanced error handling patterns
Workflow tutorial
Build workflows with steps