Version v1.x
Getting Started
Available in
Detailed setup with explicit configuration and the most common runtime options.
This guide expands on Quick Start and focuses on the most common production options.
Examples are shown for both .NET and Node.js.
Most teams can rely on defaults for many options. This example sets them explicitly so the behavior is easy to understand.
Recommended baseline setup
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
});
import { createDurableStackPostgres } from "durablestack";
const { runtime, closeStore } = await createDurableStackPostgres(
{ connectionString: process.env.DATABASE_URL! },
{
workerName: `orders-api-${process.pid}`,
pollIntervalSeconds: 5,
claimBatchSize: 50,
maxConcurrentRuns: 10,
leaseDurationSeconds: 30,
retryDelaySeconds: 5,
retryMaxDelaySeconds: 3600,
retryJitterEnabled: true,
retryJitterRatio: 0.2,
recurring: {
catchUpPolicy: "SkipMissed",
registrationSync: {
existingJobBehavior: "KeepDatabase",
orphanedJobBehavior: "Disable"
}
},
retention: {
enabled: true,
runRetentionSeconds: 86400,
sweepIntervalSeconds: 300,
deleteBatchSize: 1000
},
eventing: {
tenantId: "",
clientSecret: ""
}
}
);
runtime.registerJob("sample-job", async () => {
console.log("running sample-job");
});
await runtime.start();
process.on("SIGTERM", async () => {
await runtime.stop();
await closeStore();
});
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) andRetryJitterRatio(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, effective86400for durable DB providers and3600for in-memory): how long completed/failed runs remain queryable.SweepIntervalSeconds(default:300) andDeleteBatchSize(default:1000): cleanup cadence and batch sizing.
Optional hosted observability
Eventing.TenantId(default:null) andEventing.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
WorkerNameconvention - 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
MaxConcurrentRunsandBatchSizecarefully as workload grows. - Keep
LeaseDurationSecondslonger 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
The full option list is runtime-specific. Use the .NET reference below for ASP.NET-style configuration and runtime APIs, and use the Node.js package reference (durablestack-nodejs/README.md) for Node option and method names.
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.
});
const { runtime } = await createDurableStackPostgres(
{ connectionString: process.env.DATABASE_URL! },
{
workerName: `orders-api-${process.pid}`,
pollIntervalSeconds: 5,
claimBatchSize: 50,
leaseDurationSeconds: 30,
retryDelaySeconds: 5,
retryMaxDelaySeconds: 3600,
retryJitterEnabled: false,
retryJitterRatio: 0.2,
eventing: {
tenantId: "",
clientSecret: "",
ingestionApiBaseUrl: "https://api.durablestack.com",
ingestionPath: "/v1/events/batch",
ingestionMaxBatchSize: 100,
ingestionMaxRequestBodyBytes: 1_000_000,
ingestionMaxRetryAttempts: 5,
ingestionFlushIntervalSeconds: 5,
includeErrorDetail: false,
maxErrorDetailLength: 4096
},
recurring: {
catchUpPolicy: "SkipMissed",
registrationSync: {
existingJobBehavior: "KeepDatabase",
orphanedJobBehavior: "Disable"
}
},
retention: {
enabled: true,
runRetentionSeconds: 86400,
sweepIntervalSeconds: 300,
deleteBatchSize: 1000
},
autodiscovery: {
enabled: 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).
});
import {
createDurableStackInMemory,
createDurableStackMySql,
createDurableStackPostgres,
createDurableStackSqlite,
createDurableStackSqlServer
} from "durablestack";
const inMemory = await createDurableStackInMemory();
const pg = await createDurableStackPostgres({ connectionString: process.env.DATABASE_URL! });
const sqlServer = await createDurableStackSqlServer({ connectionString: process.env.SQLSERVER_URL! });
const sqlite = await createDurableStackSqlite({ connectionString: "Data Source=durable_stack.db" });
const mySql = await createDurableStackMySql({ connectionString: process.env.MYSQL_URL! });
Notes
- Setting both
PollIntervalandPollIntervalSecondsis redundant; use one style. - Setting both
LeaseDurationandLeaseDurationSecondsis redundant; use one style. - Setting both
Eventing.IngestionFlushIntervalandEventing.IngestionFlushIntervalSecondsis redundant; use one style. - Event ingestion is automatically enabled when both
Eventing.TenantIdandEventing.ClientSecretare set.