Version v0.1 · dotnet

Getting Started

Detailed setup with explicit C# configuration and the most common runtime options.

Getting Started

This guide expands on Quick Start and focuses on the most common production options.

All runtime configuration is set in C#.

Most teams can rely on defaults for many options. This example sets them explicitly so the behavior is easy to understand.

using DurableStack.Core.Options;
using DurableStack.Hosting.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);
var connectionString = "Host=localhost;Port=5432;Database=durable_stack;Username=postgres;Password=postgres";
var workerName = $"orders-api-{Environment.MachineName}-{Environment.ProcessId}";

builder.Services.AddDurableStackPostgres(connectionString, options =>
{
    options.JobActivation = DurableStackJobActivationMode.ScopedPerExecution; // Default: ScopedPerExecution
    options.WorkerName = workerName; // Default: "{HOSTNAME-or-machine}-{processId}"

    options.PollIntervalSeconds = 5; // Default: 5
    options.BatchSize = 50; // Default: 50
    options.MaxConcurrentRuns = 10; // Default: 5
    options.LeaseDurationSeconds = 30; // Default: 30

    options.RetryDelay = TimeSpan.FromSeconds(5); // Default: 00:00:05
    options.RetryMaxDelay = TimeSpan.FromMinutes(60); // Default: 01:00:00
    options.RetryJitterEnabled = true; // Default: false
    options.RetryJitterRatio = 0.20; // Default: 0.2

    options.Recurring.CatchUpPolicy = RecurringCatchUpPolicy.SkipMissed; // Default: SkipMissed
    options.Recurring.RegistrationSync.ExistingJobBehavior = ExistingRecurringJobBehavior.KeepDatabase; // Default: KeepDatabase
    options.Recurring.RegistrationSync.OrphanedJobBehavior = OrphanedRecurringJobBehavior.Disable; // Default: Disable

    options.Retention.Enabled = true; // Default: true
    options.Retention.RunRetentionSeconds = 86400; // Default: null (effective 86400 for durable DB providers)
    options.Retention.SweepIntervalSeconds = 300; // Default: 300
    options.Retention.DeleteBatchSize = 1000; // Default: 1000

    options.Eventing.TenantId = null; // Default: null
    options.Eventing.ClientSecret = null; // Default: null
});

Option groups that matter most

Worker and throughput

  • WorkerName (default: "{HOSTNAME-or-machine}-{processId}"): unique identity used for lease ownership and diagnostics.
  • PollIntervalSeconds (default: 5): how often workers check for available runs.
  • BatchSize (default: 50): maximum runs claimed per poll cycle.
  • MaxConcurrentRuns (default: 5): concurrency limit per worker instance.
  • LeaseDurationSeconds (default: 30): ownership timeout before another worker can recover a stalled run.

Retry behavior

  • RetryDelay (default: 00:00:05): baseline delay between attempts.
  • RetryMaxDelay (default: 01:00:00): cap for backoff growth.
  • RetryJitterEnabled (default: false) and RetryJitterRatio (default: 0.2): spread retry pressure across time.

Recurring behavior

  • Recurring.CatchUpPolicy (default: SkipMissed): avoids large catch-up bursts after downtime.
  • ExistingJobBehavior (default: KeepDatabase): keeps existing schedule definitions stable by default.
  • OrphanedJobBehavior (default: Disable): safely disables schedules removed from code.

Retention

  • Retention.Enabled (default: true): enables cleanup for terminal runs.
  • RunRetentionSeconds (default: null, effective 86400 for durable DB providers and 3600 for in-memory): how long completed/failed runs remain queryable.
  • SweepIntervalSeconds (default: 300) and DeleteBatchSize (default: 1000): cleanup cadence and batch sizing.

Optional hosted observability

  • Eventing.TenantId (default: null) and Eventing.ClientSecret (default: null): enable ingestion when both are set.

Defaults-first guidance

In most projects, you only need to set:

  • provider selection and connection string
  • optional WorkerName convention
  • any non-default values that match your workload goals

Everything else can stay at defaults until you have data that justifies tuning.

What to tune first in production

  • Increase MaxConcurrentRuns and BatchSize carefully as workload grows.
  • Keep LeaseDurationSeconds longer than normal job execution for your slowest routine jobs.
  • Use retry jitter to reduce synchronized retries during external outages.
  • Set retention windows based on audit/debug needs and database cost profile.

Full options reference

using DurableStack.Core.Options;
using DurableStack.Hosting.DependencyInjection;

builder.Services.AddDurableStack(options =>
{
    options.StorageProvider = DurableStackStorageProvider.InMemory; // Backing store selection when using property-based config; default: InMemory.

    options.Postgres.ConnectionString = "Host=localhost;Port=5432;Database=durable_stack;Username=postgres;Password=postgres"; // PostgreSQL connection string used when StorageProvider is Postgres; default: "".
    options.SqlServer.ConnectionString = "Server=localhost;Database=durable_stack;Trusted_Connection=True;TrustServerCertificate=True"; // SQL Server connection string used when StorageProvider is SqlServer; default: "".
    options.Sqlite.ConnectionString = "Data Source=durable_stack.db"; // SQLite connection string used when StorageProvider is Sqlite; default: "".
    options.MySql.ConnectionString = "Server=localhost;Port=3306;Database=durable_stack;Uid=root;Pwd=password"; // MySQL connection string used when StorageProvider is MySql; default: "".

    options.JobActivation = DurableStackJobActivationMode.ScopedPerExecution; // Job activation model (ScopedPerExecution or RootProvider); default: ScopedPerExecution.
    options.WorkerName = DurableStackOptions.CreateDefaultWorkerName(); // Unique worker identity for leasing and diagnostics; default: "{HOSTNAME-or-machine}-{processId}".
    options.DatabaseTablePrefix = null; // Optional prefix for durable database objects; default: null.

    options.PollInterval = TimeSpan.FromSeconds(5); // Polling cadence for fetching available work; default: 00:00:05.
    options.PollIntervalSeconds = 5; // Same as PollInterval but in seconds (<= 0 resets to default 5); default: 5.
    options.BatchSize = 50; // Max runs leased per poll cycle; default: 50.
    options.LeaseDuration = TimeSpan.FromSeconds(30); // Lease TTL before another worker can recover stalled runs; default: 00:00:30.
    options.LeaseDurationSeconds = 30; // Same as LeaseDuration but in seconds (<= 0 resets to default 30); default: 30.

    options.RetryDelay = TimeSpan.FromSeconds(5); // Base retry delay for failed attempts; default: 00:00:05.
    options.RetryMaxDelay = TimeSpan.FromHours(1); // Retry backoff upper bound; default: 01:00:00.
    options.RetryJitterEnabled = false; // Enables random jitter on retry delays; default: false.
    options.RetryJitterRatio = 0.2; // Jitter ratio applied when RetryJitterEnabled is true; default: 0.2.

    options.Eventing.TenantId = null; // Tenant ID for hosted ingestion auth; default: null.
    options.Eventing.ClientSecret = null; // Client secret for hosted ingestion auth; default: null.
    options.Eventing.IngestionApiBaseUrl = "https://api.durablestack.com"; // Base URL for event ingestion API; default: https://api.durablestack.com.
    options.Eventing.IngestionPath = "/v1/events/batch"; // Relative ingestion endpoint path; default: /v1/events/batch.
    options.Eventing.IngestionMaxBatchSize = 100; // Max events sent per ingestion request; default: 100.
    options.Eventing.IngestionMaxRequestBodyBytes = 1_000_000; // Max request body size per ingestion post; default: 1,000,000.
    options.Eventing.IngestionMaxRetryAttempts = 5; // Max retry attempts for failed ingestion posts; default: 5.
    options.Eventing.IngestionFlushInterval = TimeSpan.FromSeconds(5); // Flush interval for telemetry publishing; default: 00:00:05.
    options.Eventing.IngestionFlushIntervalSeconds = 5; // Same as IngestionFlushInterval in seconds (<= 0 resets to default 5); default: 5.
    options.Eventing.IncludeErrorDetail = false; // Includes captured error detail text in events when true; default: false.
    options.Eventing.MaxErrorDetailLength = 4096; // Max captured error-detail characters per event (<= 0 uses 4096); default: 4096.

    options.Recurring.CatchUpPolicy = RecurringCatchUpPolicy.SkipMissed; // Recurring schedule behavior after downtime (SkipMissed or CatchUp); default: SkipMissed.
    options.Recurring.RegistrationSync.ExistingJobBehavior = ExistingRecurringJobBehavior.KeepDatabase; // Existing recurring definitions: keep DB values or update from code; default: KeepDatabase.
    options.Recurring.RegistrationSync.OrphanedJobBehavior = OrphanedRecurringJobBehavior.Disable; // Jobs removed from code: disable or ignore in DB; default: Disable.

    options.Retention.Enabled = true; // Enables automatic cleanup of terminal runs; default: true.
    options.Retention.RunRetentionSeconds = null; // Terminal run retention window in seconds; default: null (effective default = 3600 in-memory, 86400 durable stores).
    options.Retention.SweepIntervalSeconds = 300; // Retention cleanup sweep interval in seconds (<= 0 uses 300); default: 300.
    options.Retention.DeleteBatchSize = 1000; // Max rows removed per retention cleanup batch (<= 0 uses 1000); default: 1000.

    options.JobRegistration.AutoDiscoverJobsFromAssembly = true; // Auto-registers public IDurableJob/IDurableJob<TArgs> from the app assembly; default: true.
});

Store helper methods

Use one of these helpers inside the same AddDurableStack callback when you prefer method-based store selection.

builder.Services.AddDurableStack(options =>
{
    options.UseInMemory(); // Selects in-memory store; default if no store is selected.
    // options.UsePostgres("Host=..."); // Selects PostgreSQL store and sets connection string; use only one Use* store method at a time (last call wins).
    // options.UseSqlServer("Server=..."); // Selects SQL Server store and sets connection string; use only one Use* store method at a time (last call wins).
    // options.UseSqlite("Data Source=..."); // Selects SQLite store and sets connection string; use only one Use* store method at a time (last call wins).
    // options.UseMySql("Server=..."); // Selects MySQL store and sets connection string; use only one Use* store method at a time (last call wins).
});

Notes

  • Setting both PollInterval and PollIntervalSeconds is redundant; use one style.
  • Setting both LeaseDuration and LeaseDurationSeconds is redundant; use one style.
  • Setting both Eventing.IngestionFlushInterval and Eventing.IngestionFlushIntervalSeconds is redundant; use one style.
  • Event ingestion is automatically enabled when both Eventing.TenantId and Eventing.ClientSecret are set.