Skip to content

Operations Framework: Configuration Options

This document provides a complete reference for all Operations Framework configuration options.

Setup Overview

Operations Framework is configured through AddDbContextServices and AddOperations:

cs
services.AddDbContextServices<AppDbContext>(db => {
    db.AddOperations(operations => {
        // Configuration goes here
        operations.ConfigureOperationLogReader(_ => new() { /* ... */ });
        operations.ConfigureOperationLogTrimmer(_ => new() { /* ... */ });
        operations.ConfigureOperationScope(_ => new() { /* ... */ });
        operations.ConfigureEventLogReader(_ => new() { /* ... */ });
        operations.ConfigureEventLogTrimmer(_ => new() { /* ... */ });

        // Add a log watcher
        operations.AddFileSystemOperationLogWatcher();
        // Or: operations.AddNpgsqlOperationLogWatcher();
        // Or: operations.AddRedisOperationLogWatcher();
    });
});

// Operation reprocessing (separate from DbContext)
var fusion = services.AddFusion();
fusion.AddOperationReprocessor(_ => new() { /* ... */ });

DbOperationLogReader Options

Controls how the operation log is read and processed.

cs
operations.ConfigureOperationLogReader(_ => new() {
    StartOffset = TimeSpan.FromSeconds(3),
    CheckPeriod = TimeSpan.FromSeconds(5).ToRandom(0.1),
    BatchSize = 64,
    ConcurrencyLevel = Environment.ProcessorCount * 4,
    ReprocessDelay = TimeSpan.FromSeconds(0.1).ToRandom(0.1),
    RetryDelays = RetryDelaySeq.Exp(0.25, 5),
    LogLevel = LogLevel.Information,
    IsTracingEnabled = false,
});

Options Reference

OptionTypeDefaultDescription
StartOffsetTimeSpan3 secondsOn startup, process operations logged within this time window
CheckPeriodRandomTimeSpan5s ± 0.5sHow often to check for new operations (unconditional wake-up)
BatchSizeint64Maximum operations processed per batch
ConcurrencyLevelint4 × CPU countNumber of concurrent operation processors
ReprocessDelayRandomTimeSpan100ms ± 100msDelay before reprocessing a failed item
ReprocessPolicyIRetryPolicy5 retries, 30s timeoutPolicy for reprocessing failed operations
RetryDelaysRetryDelaySeqExp(0.25s, 5×)Delays between read retries on error
LogLevelLogLevelInformationLogging verbosity
IsTracingEnabledboolfalseEnable Activity tracing

StartOffset Explained

On service startup, StartOffset determines how far back to look for unprocessed operations:

Operations older than StartOffset from startup time are ignored. This prevents processing very old operations on service restart while ensuring recent ones aren't missed.

CheckPeriod and Watchers

CheckPeriod is the unconditional wake-up interval. When using a log watcher, the reader wakes up immediately on notifications, so you can set a longer CheckPeriod:

cs
// With file system watcher: notifications are reliable
operations.ConfigureOperationLogReader(_ => new() {
    CheckPeriod = TimeSpan.FromSeconds(60).ToRandom(0.05),
});

// Without watcher: rely on polling only
operations.ConfigureOperationLogReader(_ => new() {
    CheckPeriod = TimeSpan.FromMilliseconds(250).ToRandom(0.1),
});

DbOperationLogTrimmer Options

Controls cleanup of old operations from the log.

cs
operations.ConfigureOperationLogTrimmer(_ => new() {
    MaxEntryAge = TimeSpan.FromMinutes(30),
    BatchSize = 4096,  // .NET 7+: 4096, .NET 6: 1024
    CheckPeriod = TimeSpan.FromMinutes(15).ToRandom(0.25),
    RetryDelays = RetryDelaySeq.Exp(
        TimeSpan.FromSeconds(15),
        TimeSpan.FromMinutes(10)),
    StatisticsPeriod = TimeSpan.FromHours(1).ToRandom(0.1),
    LogLevel = LogLevel.Information,
    IsTracingEnabled = false,
});

Options Reference

OptionTypeDefaultDescription
MaxEntryAgeTimeSpan30 minutesOperations older than this are trimmed
BatchSizeint4096 (.NET 7+)Operations deleted per batch
CheckPeriodRandomTimeSpan15min ± 3.75minHow often to run trimming
RetryDelaysRetryDelaySeq15s to 10min expDelays on trim failures
StatisticsPeriodRandomTimeSpan1hr ± 6minHow often to log statistics
LogLevelLogLevelInformationLogging verbosity
IsTracingEnabledboolfalseEnable Activity tracing

Trimming Condition

Operations are trimmed when:

sql
LoggedAt < (NOW - MaxEntryAge)

Note: MaxEntryAge should be longer than:

  • Maximum expected operation processing time
  • Maximum clock skew between servers
  • Time needed for all hosts to read the operation

DbOperationScope Options

Controls the database transaction behavior for operations.

cs
operations.ConfigureOperationScope(_ => new() {
    IsolationLevel = IsolationLevel.ReadCommitted,
});

Options Reference

OptionTypeDefaultDescription
IsolationLevelIsolationLevelUnspecifiedTransaction isolation level

Isolation Levels

LevelDescriptionUse Case
UnspecifiedDatabase defaultMost applications
ReadCommittedPrevents dirty readsGeneral use
RepeatableReadPrevents non-repeatable readsWhen re-reading data
SerializableStrictest isolationFinancial transactions
SnapshotPoint-in-time consistencyLong-running reads (SQL Server)

Global Default

You can set a global default isolation level:

cs
DbOperationScope<TDbContext>.Options.DefaultIsolationLevel = IsolationLevel.ReadCommitted;

DbEventLogReader Options

Controls how the event log is read and processed.

cs
operations.ConfigureEventLogReader(_ => new() {
    CheckPeriod = TimeSpan.FromSeconds(5).ToRandom(0.1),
    BatchSize = 64,
    ConcurrencyLevel = Environment.ProcessorCount * 4,
    ReprocessDelay = TimeSpan.FromSeconds(0.1).ToRandom(0.1),
    RetryDelays = RetryDelaySeq.Exp(0.25, 5),
    LogLevel = LogLevel.Information,
    IsTracingEnabled = false,
});

Options Reference

OptionTypeDefaultDescription
CheckPeriodRandomTimeSpan5s ± 0.5sHow often to check for new events
BatchSizeint64Maximum events processed per batch
ConcurrencyLevelint4 × CPU countNumber of concurrent event processors
ReprocessDelayRandomTimeSpan100ms ± 100msDelay before reprocessing a failed event
ReprocessPolicyIRetryPolicy5 retries, 5min timeoutPolicy for reprocessing failed events
RetryDelaysRetryDelaySeqExp(0.25s, 5×)Delays between read retries on error
LogLevelLogLevelInformationLogging verbosity
IsTracingEnabledboolfalseEnable Activity tracing

Key Difference from Operations

Events have a longer reprocess timeout (5 minutes vs 30 seconds) because event processing may involve external services that take longer to respond.

DbEventLogTrimmer Options

Controls cleanup of old events from the log.

cs
operations.ConfigureEventLogTrimmer(_ => new() {
    MaxEntryAge = TimeSpan.FromHours(1),
    BatchSize = 4096,
    CheckPeriod = TimeSpan.FromMinutes(15).ToRandom(0.25),
    RetryDelays = RetryDelaySeq.Exp(
        TimeSpan.FromSeconds(15),
        TimeSpan.FromMinutes(10)),
    StatisticsPeriod = TimeSpan.FromHours(1).ToRandom(0.1),
    LogLevel = LogLevel.Information,
    IsTracingEnabled = false,
});

Options Reference

OptionTypeDefaultDescription
MaxEntryAgeTimeSpan1 hourEvents older than this are trimmed
BatchSizeint4096 (.NET 7+)Events deleted per batch
CheckPeriodRandomTimeSpan15min ± 3.75minHow often to run trimming
RetryDelaysRetryDelaySeq15s to 10min expDelays on trim failures
StatisticsPeriodRandomTimeSpan1hr ± 6minHow often to log statistics
LogLevelLogLevelInformationLogging verbosity
IsTracingEnabledboolfalseEnable Activity tracing

Trimming Condition

Events are trimmed when:

sql
DelayUntil <= (NOW - MaxEntryAge) AND State != 'New'

This ensures:

  • Events that haven't been processed yet are never trimmed
  • Delayed events remain until processed
  • Processed/discarded events are eventually cleaned up

OperationReprocessor Options

Controls command retry behavior for transient errors.

cs
var fusion = services.AddFusion();
fusion.AddOperationReprocessor(_ => new() {
    MaxRetryCount = 3,
    RetryDelays = RetryDelaySeq.Exp(0.50, 3, 0.33),
    Filter = OperationReprocessor.DefaultFilter,
});

Options Reference

OptionTypeDefaultDescription
MaxRetryCountint3Maximum retry attempts
RetryDelaysRetryDelaySeqExp(0.5s, 3×, 33% jitter)Delay sequence between retries
DelayClockMomentClock?nullCustom clock for testing
FilterFunc<ICommand, CommandContext, bool>DefaultFilterDetermines which commands can be retried

RetryDelaySeq Examples

cs
// Exponential backoff: 0.5s → 1.5s → 4.5s (max 3x, no jitter)
RetryDelaySeq.Exp(0.50, 3)

// Exponential with jitter: 0.5s ± 33%
RetryDelaySeq.Exp(0.50, 3, 0.33)

// Custom range: 1s to 30s exponential
RetryDelaySeq.Exp(
    TimeSpan.FromSeconds(1),
    TimeSpan.FromSeconds(30))

// Fixed delays
RetryDelaySeq.Fixed(TimeSpan.FromSeconds(1))

OperationCompletionNotifier Options

Controls duplicate detection for operation completion.

cs
// Typically configured via FusionBuilder internals
public record Options
{
    public int MaxKnownOperationCount { get; init; } = 16384;
    public TimeSpan MaxKnownOperationAge { get; init; } = TimeSpan.FromMinutes(15);
    public MomentClock? Clock { get; init; }
}

Options Reference

OptionTypeDefaultDescription
MaxKnownOperationCountint16384Cache size for recently seen operations
MaxKnownOperationAgeTimeSpan15 minutesHow long to remember operations
ClockMomentClock?nullCustom clock for testing

These settings ensure duplicate notifications (from multiple sources) are properly deduplicated.

RandomTimeSpan

Many options use RandomTimeSpan to add jitter and prevent thundering herd:

cs
// Fixed interval (no jitter)
TimeSpan.FromSeconds(5)

// With jitter: 5s ± 10% (4.5s to 5.5s)
TimeSpan.FromSeconds(5).ToRandom(0.1)

// With larger jitter: 5s ± 50% (2.5s to 7.5s)
TimeSpan.FromSeconds(5).ToRandom(0.5)

Complete Configuration Example

cs
services.AddDbContextServices<AppDbContext>(db => {
    db.AddOperations(operations => {
        // Operation log reading
        operations.ConfigureOperationLogReader(_ => new() {
            StartOffset = TimeSpan.FromSeconds(5),
            CheckPeriod = TimeSpan.FromSeconds(60).ToRandom(0.1),  // Long poll with watcher
            BatchSize = 128,
            ConcurrencyLevel = 16,
        });

        // Operation log cleanup
        operations.ConfigureOperationLogTrimmer(_ => new() {
            MaxEntryAge = TimeSpan.FromHours(1),
            CheckPeriod = TimeSpan.FromMinutes(30).ToRandom(0.25),
        });

        // Transaction isolation
        operations.ConfigureOperationScope(_ => new() {
            IsolationLevel = IsolationLevel.ReadCommitted,
        });

        // Event log reading
        operations.ConfigureEventLogReader(_ => new() {
            CheckPeriod = TimeSpan.FromSeconds(10).ToRandom(0.1),
            BatchSize = 256,
        });

        // Event log cleanup
        operations.ConfigureEventLogTrimmer(_ => new() {
            MaxEntryAge = TimeSpan.FromHours(24),  // Keep events longer
        });

        // Use PostgreSQL notifications
        operations.AddNpgsqlOperationLogWatcher();
    });
});

// Command retry
var fusion = services.AddFusion();
fusion.AddOperationReprocessor(_ => new() {
    MaxRetryCount = 5,
    RetryDelays = RetryDelaySeq.Exp(1, 10, 0.25),
});

Environment-Specific Configuration

cs
services.AddDbContextServices<AppDbContext>(db => {
    db.AddOperations(operations => {
        if (env.IsDevelopment()) {
            // Faster feedback in development
            operations.ConfigureOperationLogReader(_ => new() {
                CheckPeriod = TimeSpan.FromSeconds(1),
            });
            operations.ConfigureOperationLogTrimmer(_ => new() {
                MaxEntryAge = TimeSpan.FromMinutes(5),  // Clean up faster
                CheckPeriod = TimeSpan.FromMinutes(1),
            });
        }
        else {
            // Production: rely on watchers, longer retention
            operations.ConfigureOperationLogReader(_ => new() {
                CheckPeriod = TimeSpan.FromMinutes(5).ToRandom(0.1),
            });
            operations.ConfigureOperationLogTrimmer(_ => new() {
                MaxEntryAge = TimeSpan.FromHours(4),
            });
        }

        // Choose watcher based on infrastructure
        if (useRedis)
            operations.AddRedisOperationLogWatcher();
        else if (usePostgres)
            operations.AddNpgsqlOperationLogWatcher();
        else
            operations.AddFileSystemOperationLogWatcher();
    });
});