Introduction
Multi-step workflows are common in modern applications.
A single business operation may involve payment processing, database updates, inventory management, notifications, document generation, or calls to external APIs. When these operations are executed as a sequence of independent steps, reliability becomes more difficult to measure.
A workflow can fail between steps. A dependency can time out. A function can be retried. A process can terminate after completing an external operation but before recording the result.
Serverless platforms can simplify infrastructure management, but they do not automatically solve workflow reliability.
Durable execution introduces a different model: instead of treating each function invocation as an isolated unit, the workflow maintains enough execution state to resume after interruptions.
For .NET workloads running on AWS Lambda, this creates an interesting engineering question:
What is the performance and operational cost of durable execution compared with a conventional multi-step serverless workflow?
A useful benchmark should measure more than execution time. It should examine latency, state persistence, retries, workflow duration, resource consumption, and the behavior of the system when failures occur.
What Is Durable Execution?
A conventional serverless workflow can look like this:
API Request
↓
Lambda A
↓
Lambda B
↓
Lambda C
↓
Complete
Each function performs part of the operation.
The workflow state may be passed explicitly between functions:
Lambda A
↓
State
↓
Lambda B
↓
State
↓
Lambda C
A durable workflow adds persistent execution state:
┌──────────────────┐
│ Workflow State │
└────────┬─────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Activity A Activity B Activity C
If execution is interrupted, the workflow can use its recorded state to determine what should happen next.
The workflow lifetime therefore becomes independent of the lifetime of an individual Lambda execution environment.
Why Benchmark Durable Execution?
Durability introduces additional infrastructure and orchestration work.
A workflow may need to:
These capabilities provide reliability, but they can introduce overhead.
A benchmark should answer questions such as:
How much latency does workflow orchestration add?
How does execution time change as the number of steps increases?
How does state size affect performance?
What happens when an activity fails?
How expensive are retries?
How does a durable workflow behave during long waits?
What is the throughput under concurrent workflows?
The objective is not simply to prove that durable execution works.
The objective is to quantify its trade-offs.
Design a Multi-Step .NET Workflow
Start with a deterministic workflow that contains several independent activities.
For example:
Start
↓
Validate Order
↓
Calculate Price
↓
Reserve Inventory
↓
Create Shipment
↓
Send Notification
↓
Complete
Each activity should perform a measurable amount of work.
Avoid making every activity a no-op because that would primarily measure orchestration overhead.
A useful benchmark should contain both:
Lightweight Activity
and:
Realistic I/O Activity
This allows you to understand how orchestration overhead behaves relative to actual application work.
Create Different Workflow Sizes
Do not benchmark only one workflow.
Create several variants:
| Workflow | Activities |
|---|
| Small | 3 |
| Medium | 5 |
| Large | 10 |
| Extended | 25 |
This allows you to examine how workflow duration changes as the number of durable steps increases.
For example:
3 Activities
↓
Small orchestration overhead
25 Activities
↓
More state transitions
↓
More orchestration work
The resulting curve is more useful than a single benchmark number.
Keep Activity Work Consistent
Suppose Activity A takes 100 ms and Activity B takes 2 seconds.
A comparison between workflow configurations may then be dominated by Activity B.
For an orchestration benchmark, start with controlled workloads:
Activity A → 100 ms
Activity B → 100 ms
Activity C → 100 ms
Activity D → 100 ms
After establishing a baseline, introduce realistic variability.
This separates workflow-engine overhead from business-logic execution time.
Establish a Baseline Without Durability
Before measuring durable execution, establish a baseline.
For example:
API
↓
Lambda A
↓
Lambda B
↓
Lambda C
Measure:
Total execution time
Lambda duration
Number of invocations
Memory consumption
Cold starts
Network calls
Error rate
Then implement the equivalent durable workflow:
Workflow
↓
Activity A
↓
Activity B
↓
Activity C
Now compare the two approaches.
This is much more useful than benchmarking durable execution in isolation.
Measure End-to-End Latency
The first important metric is workflow completion time.
Define:
Workflow Latency =
Completion Timestamp
-
Workflow Start Timestamp
Run enough iterations to calculate:
Average
Median
p95
p99
Minimum
Maximum
Average latency alone can hide important tail behavior.
For example:
Average: 420 ms
p95: 800 ms
p99: 2.4 s
The p99 value may matter much more for a production system handling latency-sensitive workloads.
Measure Per-Activity Latency
Total workflow latency is only part of the picture.
Record each activity:
Workflow
├── ValidateOrder 85 ms
├── CalculatePrice 42 ms
├── ReserveInventory 130 ms
├── CreateShipment 160 ms
└── NotifyCustomer 70 ms
This makes it possible to distinguish orchestration overhead from business execution.
A workflow may appear slow when one downstream dependency is actually responsible for most of the latency.
Measure State Size
Durable execution depends on workflow state.
Benchmark multiple state sizes:
1 KB
10 KB
100 KB
1 MB
The state should represent realistic workflow information.
For example:
public sealed record OrderState(
Guid OrderId,
string Status,
decimal Total,
string CustomerId,
IReadOnlyList<OrderItem> Items);
Do not use large state objects merely to produce artificial benchmark numbers.
The objective is to understand how realistic state growth affects workflow performance.
Measure the Cost of Additional Steps
One useful experiment is to increase the number of activities while keeping the activity workload constant.
For example:
3 activities → X ms
5 activities → Y ms
10 activities → Z ms
25 activities → N ms
Plotting this relationship can reveal whether orchestration overhead grows approximately linearly or whether certain workflow sizes introduce additional bottlenecks.
The important observation is not the absolute number.
It is the shape of the curve.
Benchmark Sequential Workflows
Start with sequential execution:
A
↓
B
↓
C
↓
D
This establishes a simple baseline.
The total duration is approximately:
Ttotal =
TA + TB + TC + TD + Orchestration Overhead
This is the easiest workflow type to benchmark.
Benchmark Parallel Activities
Then introduce independent activities:
┌── Activity A ──┐
│ │
Start ──┼── Activity B ──┼── Complete
│ │
└── Activity C ──┘
If the activities are independent, parallel execution can reduce total workflow duration.
Conceptually:
Sequential:
A ── B ── C
| | |
100 100 100 ms
≈ 300 ms
versus:
Parallel:
A ── 100 ms ──┐
B ── 100 ms ──┼── Continue
C ── 100 ms ──┘
≈ 100 ms
Actual results will include orchestration and scheduling overhead.
Parallelism should therefore be benchmarked rather than assumed to be free.
Benchmark Failure Recovery
A durable workflow should be tested under controlled failure.
Introduce a failure into one activity:
Activity A
↓
Activity B
X
Activity C
Then measure:
The benchmark should verify that the workflow eventually reaches the correct state.
Measure Retry Overhead
Suppose an activity succeeds on the third attempt:
Attempt 1 → Failure
Attempt 2 → Failure
Attempt 3 → Success
The total workflow duration now includes:
Initial Execution
+
Failure Handling
+
Retry Delay
+
Second Execution
+
Retry Delay
+
Third Execution
A benchmark should record these components separately.
Otherwise, the result may show only a large latency increase without explaining why.
Test a Non-Idempotent Activity
Failure testing should also consider external side effects.
For example:
ChargeCustomer()
might succeed remotely but return a timeout to Lambda.
If the workflow retries blindly:
ChargeCustomer()
↓
Timeout
↓
Retry
↓
ChargeCustomer()
the customer could potentially be charged twice.
The benchmark environment should therefore use an idempotency key:
var idempotencyKey =
$"order-{orderId}-payment";
The external operation should treat repeated requests with the same key as the same logical operation.
This is a critical part of measuring durable workflow reliability.
Test Long Waits
One of the most important durable-workflow scenarios is a long wait.
For example:
Order Submitted
↓
Wait for Approval
↓
Approval Received
↓
Continue
Do not test only workflows that complete in milliseconds.
Use scenarios representing:
1 second
1 minute
1 hour
several hours
The important measurement is whether the workflow can remain logically active without requiring a continuously running application process.
Measure Workflow Resume Latency
For event-driven workflows, measure the time between an external event and workflow continuation.
For example:
Approval Event
↓
Event Received
↓
Workflow Resumed
↓
Next Activity Started
Record:
Resume Latency =
Activity Start
-
Event Arrival
This is a separate metric from the total workflow duration.
Cold Starts Matter
AWS Lambda workloads can experience cold starts.
A benchmark should distinguish:
Warm Execution
from:
Cold Execution
Measure both.
For .NET applications, startup behavior can become especially important when activities are short.
If an activity performs only a small amount of work, initialization overhead may represent a large portion of total execution time.
A useful test therefore looks like:
| Scenario | Cold Start | Warm Start |
|---|
| 3-step workflow | ... | ... |
| 5-step workflow | ... | ... |
| 10-step workflow | ... | ... |
Measure Memory Configuration
Lambda memory configuration affects more than available memory.
It can also affect available compute capacity.
Run the benchmark at multiple memory configurations:
512 MB
1024 MB
2048 MB
Then compare:
Duration
CPU behavior
Cost
Throughput
Cold-start behavior
The goal is to find the most efficient configuration rather than simply selecting the largest memory allocation.
Measure Cost, Not Just Performance
A faster workflow is not automatically cheaper.
A useful benchmark should estimate:
Total Cost =
Function Execution
+
Workflow Orchestration
+
State Storage
+
Network
+
Other Required Services
The exact billing model depends on the AWS services and architecture being tested.
For that reason, record resource usage during the benchmark and calculate cost using the current pricing model applicable to the deployment.
Avoid publishing fixed cost claims without specifying the region, configuration, workload, and pricing assumptions.
Run High-Concurrency Tests
A workflow that works well for one request may behave differently at scale.
Test increasing concurrency:
10 workflows
50 workflows
100 workflows
500 workflows
1000 workflows
Track:
This identifies scaling bottlenecks that are invisible in sequential tests.
Example Benchmark Matrix
A practical benchmark matrix could look like this:
| Test | Steps | State | Concurrency | Failure |
|---|
| Baseline | 3 | 1 KB | 1 | No |
| Medium | 5 | 10 KB | 10 | No |
| Large | 10 | 100 KB | 50 | No |
| High Load | 10 | 100 KB | 500 | No |
| Retry | 5 | 10 KB | 10 | Step 3 |
| Recovery | 10 | 100 KB | 50 | Step 5 |
| Long Wait | 5 | 10 KB | 10 | Timer/Event |
This produces a more complete picture than a single happy-path test.
Benchmark .NET Release Builds
Always benchmark production-like builds.
For example:
dotnet publish -c Release
Do not use Debug builds for performance comparisons.
Keep the following consistent between test runs:
.NET version
Lambda runtime
Region
Memory configuration
Architecture
Workflow definition
Payload
External dependencies
Concurrency
Changing multiple variables simultaneously makes the results difficult to interpret.
Common Benchmarking Mistakes
Measuring Only Average Latency
Tail latency can be much more important under production load.
Ignoring Cold Starts
Short-lived .NET activities can be significantly affected by startup overhead.
Using Unrealistically Small Activities
If every activity does almost no work, orchestration overhead can dominate the results.
Ignoring Failures
Durable execution is primarily valuable when things do not go perfectly.
Measuring Only Successful Workflows
Retry and recovery behavior should be part of the benchmark.
Ignoring Idempotency
Retries around external side effects can produce incorrect business outcomes.
Changing Multiple Variables
Keep the experiment controlled so that observed differences have a clear cause.
Best Practices
Establish a non-durable baseline before measuring durable execution.
Benchmark sequential and parallel workflows.
Test different workflow sizes.
Measure p50, p95, and p99 latency.
Record per-activity execution time.
Measure workflow state size.
Test cold and warm Lambda executions.
Test realistic memory configurations.
Introduce controlled failures.
Measure retry and recovery overhead.
Test long waits and event-driven resumption.
Use idempotency keys for external side effects.
Test realistic concurrency.
Track throttling and error rates.
Include infrastructure and execution costs in the final comparison.
Conclusion
Durable execution provides an important reliability model for multi-step serverless workflows, but reliability features should still be evaluated from a performance and cost perspective.
A useful benchmark should move beyond:
"Does the workflow complete?"
and instead ask:
How fast does it complete?
How does performance change with more steps?
What happens when an activity fails?
How expensive are retries?
How quickly does the workflow resume?
How does concurrency affect tail latency?
What does the complete workflow cost?
For .NET workloads on AWS Lambda, the most valuable benchmark is therefore a controlled comparison across workflow size, state size, concurrency, failures, and execution conditions.
The final result should not be a single claim that durable execution is “faster” or “slower.”
The engineering value comes from identifying where the durability overhead is small, where it becomes significant, and where the reliability benefits justify that overhead.
For long-running business processes, that trade-off is often more important than raw function execution time.