10 KiB
10 KiB
Projection Options
Configure projection behavior with auto-start, batching, and checkpoint intervals.
Overview
Projection options control how projections run and process events:
- Auto-Start - Start projections automatically on application startup
- Batch Size - Number of events to process per batch
- Checkpoint Interval - How often to save checkpoints
- Error Handling - Retry strategies and dead letter queues
Quick Start
using Svrnty.CQRS.Events;
var builder = WebApplication.CreateBuilder(args);
// Register projections with options
builder.Services.AddDynamicProjections(options =>
{
options.AutoStart = true;
options.BatchSize = 100;
options.CheckpointInterval = TimeSpan.FromSeconds(5);
options.CatchUpBatchSize = 1000;
options.MaxDegreeOfParallelism = 1;
});
var app = builder.Build();
app.Run();
Projection Options
public class ProjectionOptions
{
public bool AutoStart { get; set; } // Auto-start on startup
public int BatchSize { get; set; } // Events per batch
public TimeSpan CheckpointInterval { get; set; } // Checkpoint frequency
public int CatchUpBatchSize { get; set; } // Batch size for catch-up
public int MaxDegreeOfParallelism { get; set; } // Parallel projections
public TimeSpan PollingInterval { get; set; } // Poll for new events
public TimeSpan ErrorRetryDelay { get; set; } // Retry delay
public int MaxRetryAttempts { get; set; } // Max retries
public bool EnableDeadLetterQueue { get; set; } // DLQ for failures
}
Auto-Start
Automatically start projections on application startup:
// ✅ Auto-start enabled (default)
builder.Services.AddDynamicProjections(options =>
{
options.AutoStart = true;
});
// ❌ Manual start required
builder.Services.AddDynamicProjections(options =>
{
options.AutoStart = false;
});
// Manual start
var projectionService = app.Services.GetRequiredService<IProjectionService>();
await projectionService.StartProjectionAsync("order-summary");
Batch Size
Control how many events are processed per batch:
// Small batches - lower latency, more checkpoints
builder.Services.AddDynamicProjections(options =>
{
options.BatchSize = 10;
});
// Medium batches - balanced (default)
builder.Services.AddDynamicProjections(options =>
{
options.BatchSize = 100;
});
// Large batches - higher throughput, fewer checkpoints
builder.Services.AddDynamicProjections(options =>
{
options.BatchSize = 1000;
});
Batch Size Impact
// Small batch: 10 events
// - Checkpoint every 10 events
// - Lower latency (5-10ms)
// - More database writes
// Medium batch: 100 events
// - Checkpoint every 100 events
// - Moderate latency (50-100ms)
// - Balanced performance
// Large batch: 1000 events
// - Checkpoint every 1000 events
// - Higher latency (500ms-1s)
// - Fewer database writes, better throughput
Checkpoint Interval
Control how often checkpoints are saved:
// Frequent checkpoints - every 1 second
builder.Services.AddDynamicProjections(options =>
{
options.CheckpointInterval = TimeSpan.FromSeconds(1);
});
// Moderate checkpoints - every 5 seconds (default)
builder.Services.AddDynamicProjections(options =>
{
options.CheckpointInterval = TimeSpan.FromSeconds(5);
});
// Infrequent checkpoints - every 30 seconds
builder.Services.AddDynamicProjections(options =>
{
options.CheckpointInterval = TimeSpan.FromSeconds(30);
});
Checkpoint Strategies
// Time-based checkpointing
builder.Services.AddDynamicProjections(options =>
{
options.CheckpointInterval = TimeSpan.FromSeconds(5); // Every 5 seconds
});
// Batch-based checkpointing
builder.Services.AddDynamicProjections(options =>
{
options.BatchSize = 100; // Checkpoint every 100 events
});
// Combined checkpointing (whichever comes first)
builder.Services.AddDynamicProjections(options =>
{
options.BatchSize = 100;
options.CheckpointInterval = TimeSpan.FromSeconds(5);
});
Catch-Up Mode
Optimize for rebuilding projections from scratch:
// Standard mode
builder.Services.AddDynamicProjections(options =>
{
options.BatchSize = 100; // Standard batch size
});
// Catch-up mode - larger batches for faster rebuild
builder.Services.AddDynamicProjections(options =>
{
options.BatchSize = 100; // Real-time batch size
options.CatchUpBatchSize = 5000; // Catch-up batch size
});
// Projection detects catch-up automatically
public async Task RunAsync(CancellationToken ct)
{
var checkpoint = await _checkpointStore.GetCheckpointAsync(ProjectionName);
var streamHead = await _eventStore.GetStreamHeadAsync("orders");
var lag = streamHead - checkpoint;
// Use larger batch if lagging
var batchSize = lag > 10000 ? _options.CatchUpBatchSize : _options.BatchSize;
await foreach (var @event in _eventStore.ReadStreamAsync(
"orders",
fromOffset: checkpoint + 1,
batchSize: batchSize,
cancellationToken: ct))
{
await HandleEventAsync(@event, ct);
}
}
Parallel Projections
Run multiple projections in parallel:
// Sequential projections (default)
builder.Services.AddDynamicProjections(options =>
{
options.MaxDegreeOfParallelism = 1;
});
// Parallel projections
builder.Services.AddDynamicProjections(options =>
{
options.MaxDegreeOfParallelism = 4; // Run 4 projections concurrently
});
// Caution: Only use for independent projections
// Don't parallelize projections that update the same data
Polling Interval
Control how often to check for new events:
// Aggressive polling - every 100ms
builder.Services.AddDynamicProjections(options =>
{
options.PollingInterval = TimeSpan.FromMilliseconds(100);
});
// Moderate polling - every 1 second (default)
builder.Services.AddDynamicProjections(options =>
{
options.PollingInterval = TimeSpan.FromSeconds(1);
});
// Relaxed polling - every 5 seconds
builder.Services.AddDynamicProjections(options =>
{
options.PollingInterval = TimeSpan.FromSeconds(5);
});
Error Handling
Configure retry and dead letter queue:
// Retry with backoff
builder.Services.AddDynamicProjections(options =>
{
options.MaxRetryAttempts = 5;
options.ErrorRetryDelay = TimeSpan.FromSeconds(10);
});
// Dead letter queue for permanent failures
builder.Services.AddDynamicProjections(options =>
{
options.MaxRetryAttempts = 3;
options.EnableDeadLetterQueue = true;
});
// No retry - fail fast
builder.Services.AddDynamicProjections(options =>
{
options.MaxRetryAttempts = 0;
});
Per-Projection Configuration
// Global defaults
builder.Services.AddDynamicProjections(options =>
{
options.BatchSize = 100;
options.CheckpointInterval = TimeSpan.FromSeconds(5);
});
// Per-projection override
public class OrderSummaryProjection : IDynamicProjection
{
public string ProjectionName => "order-summary";
public ProjectionOptions Options => new()
{
BatchSize = 1000, // Override global setting
CheckpointInterval = TimeSpan.FromSeconds(10)
};
public async Task RunAsync(CancellationToken ct)
{
// Use per-projection options
}
}
Configuration Examples
Real-Time Projection
// Optimize for low latency
builder.Services.AddDynamicProjections(options =>
{
options.AutoStart = true;
options.BatchSize = 10; // Small batches
options.CheckpointInterval = TimeSpan.FromSeconds(1); // Frequent checkpoints
options.PollingInterval = TimeSpan.FromMilliseconds(100); // Aggressive polling
});
Batch Projection
// Optimize for throughput
builder.Services.AddDynamicProjections(options =>
{
options.AutoStart = true;
options.BatchSize = 5000; // Large batches
options.CheckpointInterval = TimeSpan.FromSeconds(30); // Infrequent checkpoints
options.PollingInterval = TimeSpan.FromSeconds(5); // Relaxed polling
options.CatchUpBatchSize = 10000; // Very large catch-up batches
});
Resilient Projection
// Optimize for reliability
builder.Services.AddDynamicProjections(options =>
{
options.AutoStart = true;
options.BatchSize = 100;
options.CheckpointInterval = TimeSpan.FromSeconds(5);
options.MaxRetryAttempts = 10; // Many retries
options.ErrorRetryDelay = TimeSpan.FromSeconds(30);
options.EnableDeadLetterQueue = true; // DLQ for failures
});
Monitoring Options
// Log projection configuration
var options = app.Services.GetRequiredService<IOptions<ProjectionOptions>>().Value;
_logger.LogInformation(
"Projection options: AutoStart={AutoStart}, BatchSize={BatchSize}, CheckpointInterval={Interval}",
options.AutoStart,
options.BatchSize,
options.CheckpointInterval);
// Monitor projection performance
var metrics = new
{
ProjectionName = "order-summary",
BatchSize = options.BatchSize,
CheckpointInterval = options.CheckpointInterval,
EventsPerSecond = await GetProjectionThroughputAsync("order-summary"),
Lag = await GetProjectionLagAsync("order-summary")
};
if (metrics.Lag > 10000)
{
_logger.LogWarning(
"Projection {Name} lagging: {Lag} events behind",
metrics.ProjectionName,
metrics.Lag);
// Consider increasing batch size
}
Best Practices
✅ DO
- Use auto-start for production projections
- Start with default settings and tune based on metrics
- Use larger batches for catch-up mode
- Enable DLQ for critical projections
- Monitor projection lag
- Use appropriate checkpoint intervals
- Test with production-like volumes
- Configure retries for transient failures
❌ DON'T
- Don't use very small batches (< 10) in production
- Don't checkpoint too frequently (< 1 second)
- Don't poll too aggressively (< 100ms)
- Don't run dependent projections in parallel
- Don't ignore projection errors
- Don't use same options for all projections
- Don't forget to monitor lag
- Don't disable auto-start in production