Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,47 @@ Temporal is a fork of Cadence backed by a company with the same name and founded
|   [LLL.DurableTask.EFCore.PostgreSQL](src/LLL.DurableTask.EFCore.PostgreSQL) | [![Nuget](https://img.shields.io/nuget/vpre/LLL.DurableTask.EFCore.PostgreSQL)](https://www.nuget.org/packages/LLL.DurableTask.EFCore.PostgreSQL/) | EFCore storage PostgreSQL support. |
|   [LLL.DurableTask.EFCore.SqlServer](src/LLL.DurableTask.EFCore.SqlServer) | [![Nuget](https://img.shields.io/nuget/vpre/LLL.DurableTask.EFCore.SqlServer)](https://www.nuget.org/packages/LLL.DurableTask.EFCore.SqlServer/) | EFCore storage Sql Server support. |

## Comparing Storage Providers

The following table compares the EFCore storage providers from this project.

| Feature | EFCore PostgreSQL | EFCore MySQL | EFCore SQL Server | EFCore InMemory | [Others][ms-compare] |
| - | - | - | - | - | - |
| External dependencies | PostgreSQL | MySQL | SQL Server | None | [Compare][ms-compare] |
| Durable Entities | No | No | No | No | [Compare][ms-compare] |
| Disconnected environment | Yes | Yes | Yes | Yes | [Compare][ms-compare] |
| Identity-based connections | Yes¹ | Yes¹ | Yes¹ | N/A | [Compare][ms-compare] |
| Maximum throughput | Moderate | Moderate | Moderate | Moderate | [Compare][ms-compare] |
| Maturity | Not battle-tested | Used in production | Not battle-tested | Development only | [Compare][ms-compare] |
| [Worker specialization][efcore-features] | Supported | Supported | Supported | Supported | No |
| [Input/output observability][efcore-features] | Yes | Yes | Yes | Yes | No |
| [Enhanced rewind][efcore-features] | Yes | Yes | Yes | Yes | No |
| [Orchestration tags][efcore-features] | Yes | Yes | Yes | Yes | No |
| [Full execution history][efcore-features] | Yes | Yes | Yes | Yes | No |
| [Reliable event delivery][efcore-features] | Yes | Yes | Yes | Yes | No |

¹ Supported via EFCore database provider configuration (e.g. Azure AD / Entra ID tokens). Not built into this project directly.

[ms-compare]: https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-storage-providers#comparing-storage-providers
[efcore-features]: src/LLL.DurableTask.EFCore/README.md#features

### Performance Benchmarks

Benchmark running 100 orchestrations with 5 activities each (lower is better):

| Storage Provider | Median | Mean | Std Dev |
| - | - | - | - |
| EFCore InMemory | 1.56 s | 1.88 s | 0.51 s |
| DurableTask SQL Server¹ | 2.33 s | 2.34 s | 0.07 s |
| EFCore PostgreSQL | 2.75 s | 2.53 s | 0.37 s |
| EFCore SQL Server | 3.90 s | 3.77 s | 0.54 s |
| EFCore MySQL | 3.88 s | 3.85 s | 0.26 s |
| Azure Storage (Azurite)¹ | 8.53 s | 8.40 s | 0.89 s |

¹ Included for reference. [Azure Storage](https://github.com/Azure/durabletask/tree/main/src/DurableTask.AzureStorage) and [DurableTask SQL Server](https://github.com/microsoft/durabletask-mssql) are separate Microsoft packages.

> **Note:** Benchmarks were run locally using Docker containers with [BenchmarkDotNet](https://benchmarkdotnet.org/) on Apple M1 Pro, .NET 9 (3 warmup + 10 iterations). Azure Storage used the Azurite emulator. Results may vary between runs and environments — these numbers are useful for relative comparison, not absolute performance. See [benchmark source](benchmark/StoragesBenchmark) for details.

### Composability

Our components were designed to be independent and highly composable. See below some possible architectures.
Expand Down
17 changes: 17 additions & 0 deletions benchmark/StoragesBenchmark/DurableTaskSqlServerBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using DurableTask.Core;
using DurableTask.SqlServer;
using LLL.DurableTask.Worker.Builder;
using Microsoft.Data.SqlClient;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

Expand All @@ -12,6 +13,8 @@ protected override void ConfigureStorage(IServiceCollection services)
{
var connectionString = _configuration.GetConnectionString("SqlServer");

EnsureDatabaseExists(connectionString);

var settings = new SqlOrchestrationServiceSettings(connectionString);

var provider = new SqlOrchestrationService(settings);
Expand All @@ -28,4 +31,18 @@ protected override void ConfigureWorker(IDurableTaskWorkerBuilder builder)
builder.HasAllOrchestrations = true;
builder.HasAllActivities = true;
}

private static void EnsureDatabaseExists(string connectionString)
{
var builder = new SqlConnectionStringBuilder(connectionString);
var database = builder.InitialCatalog;
builder.InitialCatalog = "master";

using var connection = new SqlConnection(builder.ConnectionString);
connection.Open();

using var command = connection.CreateCommand();
command.CommandText = $"IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = '{database}') CREATE DATABASE [{database}]";
command.ExecuteNonQuery();
}
}
2 changes: 1 addition & 1 deletion benchmark/StoragesBenchmark/OrchestrationBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

namespace StoragesBenchmark;

[SimpleJob(RunStrategy.Monitoring, iterationCount: 5)]
[SimpleJob(RunStrategy.Monitoring, warmupCount: 3, iterationCount: 10)]

public abstract class OrchestrationBenchmark
{
Expand Down
14 changes: 7 additions & 7 deletions src/LLL.DurableTask.EFCore/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ LLL.DurableTask.EFCore provides relational database storage for Durable Task usi
In addition to the standard features offered by the Durable Task framework, LLL.DurableTask.EFCore includes the following enhancements:

| Feature | Description |
| - | - |
| Distributed workers | Distribute your orchestrations and activities across multiple microservices, each dedicated to specific types of tasks. These microservices connect to a shared database and collaboratively execute tasks to complete workflows efficiently and reliably. |
| Store all inputs/outputs | All inputs and outputs are stored as workflow events. This enhances visibility into the workflow's execution through the UI and supports a more robust rewind algorithm. |
| Improved rewind | We have implemented an advanced rewind algorithm. For more details, refer to [this comment](https://github.com/Azure/durabletask/issues/811#issuecomment-1324391970). |
| Tags | Orchestration tags are persisted and displayed in the UI, providing better workflow organization and tracking. |
| State per execution | The state of each execution of a workflow instance is preserved, allowing you to view all executions in the UI. |
| Guaranteed Event Delivery | Orchestrations reopen when they receive events, ensuring that no events are missed. This allows you to implement reliable orchestrations without needing to use "eternal orchestrations" via continue-as-new. More information is available at [this pull request](https://github.com/lucaslorentz/durabletask-extensions/pull/6). |
| - | - |
| Worker specialization | Workers can optionally register only specific orchestrations and activities, allowing you to distribute tasks across multiple specialized services. These services connect to a shared database and collaboratively execute workflows. |
| Input/output observability | All inputs and outputs are stored as workflow events. This enhances visibility into the workflow's execution through the UI and supports a more robust rewind algorithm. |
| Enhanced rewind | We have implemented an advanced rewind algorithm that leverages stored inputs/outputs for more reliable replay. For more details, refer to [this comment](https://github.com/Azure/durabletask/issues/811#issuecomment-1324391970). |
| Orchestration tags | Orchestration tags are persisted and displayed in the UI, providing better workflow organization and tracking. |
| Full execution history | The state of each execution of a workflow instance is preserved, allowing you to view and inspect all previous executions in the UI. |
| Reliable event delivery | Completed orchestrations automatically reopen when they receive events, ensuring that no events are missed. This allows you to implement reliable orchestrations without needing to use "eternal orchestrations" via continue-as-new. More information is available at [this pull request](https://github.com/lucaslorentz/durabletask-extensions/pull/6). |