From my week in Redmond I still had notes on the performance session of WF. Instead of throwing them away, I thought I would share them. The session follows the technical artical ‘Performance Characteristics of Windows Workflow Foundation‘ for the larger parts. This means that the points on performance below are complementary to the article. For more background and nice graphs, read the article.
1. Persistence service
Recommendation: Consider when to persist.
Persistence happens when:
- the WorkflowRuntime instance is stopped,
- a custom activity marked with PersistOnClose=true closes or
- when UnloadOnIdle=true for the persistence provider.
An instance is handed to the persistence provider, which uses the binary formatter to serialize it, that then gets passed to a GZipStream to compress the binary stream. For unloading an instance first gets persisted, then unloaded from memory. Also, persistence occurs at the end of a TransactionScope (also marked as PersistOnClose).
2. Activity Execution Context cloning
Recommendation: Write custom activities instead of cloning where high performance is required.
Issue: Workflow runtime uses AEC to maintain activity instance state and to run compensation logic.
3. Transactions in WF Activities
Recommendation: Use IPendingWork for simple workflow database transactions. Check out the WorkflowCommitWorkBatch in the SDK samples for more information. The workflow instance initial state is copied before the transaction in case of rollback. The use of System.Transaction means MSDTC occurs only if necessary.
4. Nesting compensation
Recommendation: Avoid nesting of compensating activities where performance is desired.
Issue: Because compensation retains a copy of the workflow instance state, nested compensation causes the state to multiply.
5. Workflow tracking service
Recommendation: Review your tracking profile for the required number of tracking events. Track only the events you really need.
Issue: Database operations caused by tracking affect performance.
The number of track points can be modified at runtime by editing the tracking profile. When batching is used the load is about 5% CPU usage for the SQL Server. Turning it off will make SQL Server CPU usage go up to 75%. The tracking service database can be partitioned into multiple tables. See Moustafa‘s blog. There are stored procedures you can run yourself and you can turn on automatic partitioning.
6. Workflow activity complexity
Recommendation: Optimize factors affecting state
Issue: State size affects persistence time.
Factors: Numbers of fields on the class. Properties and dependency properties being accessed. Serives being consumed. Workflow queues being used. Activity execution overhead.
7. Workflow activation cost
Recommendation: Consider startup time.
Issue: First time tree creation and validation.
Activation occurs when the workflow host calls WorkflowRuntime.CreateWorkflow. This startup time increases with the number of activities.
8. ExternalDataExchange and parameters
Recommendation: Consider serialized event data size
Issue: ExternalDataExchange and parameters are serialized through WF internal queues. Giant chunks of data will take more time to get serialized.
9. Workflow declarative rules and policy
Recommendation: Consider performance of rules
Issue: Declarative rules take longer than code conditions
Using the RuleEngine class can improve performance over the Policy activity. Rule priorities and chaining have a big effect on performance.
10. Workflow instance dynamic update
Recommendation: Consider performance
Issue: This mechanism is slow.
What it does is:
- instance is suspended
- clone the instance
- edit the clone
- validate changes (all validators are called on the new tree)
- apply changes
This has impact on tracking, tracing and persistence.
11. Workflow dependency properties
Recommendation: Avoid when regular properties can be used.
Issue: Dependency properties are slower than regular .NET properties. There’s just a few more .NET instructions in play.
12. Workflow state machine root activity
Recommendation: avoid deeply nested states.
Issue: state transitions in and out of deeply nested states are expensive due to tree navigation and AEC cloning.
13. Workflow runtime startup cost
Recommendation: only use one WorkflowRuntime per AppDomain
Issue: Instantiating WorkflowRuntime and calling StartRuntime is expensive.
Why? Retrieves runtime config parameters. Starts default and custom services. These are significant performance configuration settings:
- Runtime: EnablePerformanceCounters (default true) and ValidateOnCreate (defaults to true)
- TransactionService: EnableRetries
- SchedulerService: MaxSimultaneousWorkflows
- PersistenceService: EnableRetries, LoadIntervalSeconds, OwnershipTimeoutSeconds
- TrackingService: EnableRetries, IsTransactional (has nothing to do with real transactions, but will batch up tracking database writes), UseDefaultProfile (default profile will track all workflow level events and activity level events), PartitionOnCompletion