409 lines
10 KiB
Markdown
409 lines
10 KiB
Markdown
# 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
|
|
|
|
```csharp
|
|
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
|
|
|
|
```csharp
|
|
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:
|
|
|
|
```csharp
|
|
// ✅ 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:
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
```csharp
|
|
// 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:
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
```csharp
|
|
// 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:
|
|
|
|
```csharp
|
|
// 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:
|
|
|
|
```csharp
|
|
// 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:
|
|
|
|
```csharp
|
|
// 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:
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
```csharp
|
|
// 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
|
|
|
|
## See Also
|
|
|
|
- [Projections Overview](README.md)
|
|
- [Creating Projections](creating-projections.md)
|
|
- [Resettable Projections](resettable-projections.md)
|
|
- [Checkpoint Stores](checkpoint-stores.md)
|
|
- [Best Practices - Performance](../../best-practices/performance.md)
|