diff --git a/README.md b/README.md index 2c5d9f7..4cdd451 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/benchmark/StoragesBenchmark/DurableTaskSqlServerBenchmark.cs b/benchmark/StoragesBenchmark/DurableTaskSqlServerBenchmark.cs index f030403..175678f 100644 --- a/benchmark/StoragesBenchmark/DurableTaskSqlServerBenchmark.cs +++ b/benchmark/StoragesBenchmark/DurableTaskSqlServerBenchmark.cs @@ -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; @@ -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); @@ -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(); + } } diff --git a/benchmark/StoragesBenchmark/OrchestrationBenchmark.cs b/benchmark/StoragesBenchmark/OrchestrationBenchmark.cs index 15b0291..a3ba638 100644 --- a/benchmark/StoragesBenchmark/OrchestrationBenchmark.cs +++ b/benchmark/StoragesBenchmark/OrchestrationBenchmark.cs @@ -14,7 +14,7 @@ namespace StoragesBenchmark; -[SimpleJob(RunStrategy.Monitoring, iterationCount: 5)] +[SimpleJob(RunStrategy.Monitoring, warmupCount: 3, iterationCount: 10)] public abstract class OrchestrationBenchmark { diff --git a/src/LLL.DurableTask.EFCore/README.md b/src/LLL.DurableTask.EFCore/README.md index 3d1ed12..5c001d4 100644 --- a/src/LLL.DurableTask.EFCore/README.md +++ b/src/LLL.DurableTask.EFCore/README.md @@ -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). |