335 lines
6.7 KiB
Markdown
335 lines
6.7 KiB
Markdown
# Commit Strategies
|
|
|
|
Choosing the right offset commit strategy for your workload.
|
|
|
|
## Overview
|
|
|
|
Commit strategies determine when consumer offsets are persisted to storage. The choice affects performance, fault tolerance, and potential reprocessing.
|
|
|
|
## Strategy Comparison
|
|
|
|
| Strategy | Performance | Reprocessing | Use Case |
|
|
|----------|-------------|--------------|----------|
|
|
| **AfterBatch** | ⭐⭐⭐ | ≤ BatchSize events | Production (recommended) |
|
|
| **AfterEach** | ⭐ | ≤ 1 event | Critical data, low volume |
|
|
| **Periodic** | ⭐⭐ | Variable | Time-based workflows |
|
|
| **Manual** | ⭐⭐ | Depends on impl | Custom control needed |
|
|
|
|
## AfterBatch Strategy
|
|
|
|
**Default and recommended for most scenarios.**
|
|
|
|
### Configuration
|
|
|
|
```csharp
|
|
var options = new ConsumerGroupOptions
|
|
{
|
|
BatchSize = 100,
|
|
CommitStrategy = OffsetCommitStrategy.AfterBatch
|
|
};
|
|
|
|
await foreach (var @event in _consumerGroup.ConsumeAsync(
|
|
"orders",
|
|
"order-processing",
|
|
"worker-1",
|
|
options))
|
|
{
|
|
await ProcessEventAsync(@event);
|
|
// Offset committed after processing 100 events
|
|
}
|
|
```
|
|
|
|
### Behavior
|
|
|
|
```
|
|
Process event 1 → Process event 2 → ... → Process event 100 → Commit offset 100
|
|
Process event 101 → Process event 102 → ... → Process event 200 → Commit offset 200
|
|
```
|
|
|
|
### Performance
|
|
|
|
- **Database writes:** 1 per batch
|
|
- **Throughput:** ~10,000-50,000 events/sec
|
|
- **Latency:** Low
|
|
|
|
### Reprocessing
|
|
|
|
On failure, up to `BatchSize` events may be reprocessed:
|
|
|
|
```
|
|
Processed: events 1-150
|
|
Committed: offset 100
|
|
Crash
|
|
On restart: Reprocess events 101-150
|
|
```
|
|
|
|
### Best For
|
|
|
|
- High-throughput scenarios
|
|
- Idempotent event handlers
|
|
- Production workloads
|
|
|
|
## AfterEach Strategy
|
|
|
|
**Commit after every single event.**
|
|
|
|
### Configuration
|
|
|
|
```csharp
|
|
var options = new ConsumerGroupOptions
|
|
{
|
|
CommitStrategy = OffsetCommitStrategy.AfterEach
|
|
};
|
|
|
|
await foreach (var @event in _consumerGroup.ConsumeAsync(..., options))
|
|
{
|
|
await ProcessEventAsync(@event);
|
|
// Offset committed immediately after each event
|
|
}
|
|
```
|
|
|
|
### Behavior
|
|
|
|
```
|
|
Process event 1 → Commit offset 1
|
|
Process event 2 → Commit offset 2
|
|
Process event 3 → Commit offset 3
|
|
```
|
|
|
|
### Performance
|
|
|
|
- **Database writes:** 1 per event
|
|
- **Throughput:** ~1,000-5,000 events/sec
|
|
- **Latency:** Higher (DB roundtrip per event)
|
|
|
|
### Reprocessing
|
|
|
|
Minimal reprocessing on failure:
|
|
|
|
```
|
|
Processed: events 1-150
|
|
Committed: offset 150
|
|
Crash
|
|
On restart: Resume from event 151 (no reprocessing)
|
|
```
|
|
|
|
### Best For
|
|
|
|
- Critical financial transactions
|
|
- Non-idempotent operations
|
|
- Low-volume streams (< 1000 events/sec)
|
|
- When reprocessing is unacceptable
|
|
|
|
## Periodic Strategy
|
|
|
|
**Commit every N seconds.**
|
|
|
|
### Configuration
|
|
|
|
```csharp
|
|
var options = new ConsumerGroupOptions
|
|
{
|
|
CommitStrategy = OffsetCommitStrategy.Periodic,
|
|
CommitInterval = TimeSpan.FromSeconds(30)
|
|
};
|
|
|
|
await foreach (var @event in _consumerGroup.ConsumeAsync(..., options))
|
|
{
|
|
await ProcessEventAsync(@event);
|
|
// Offset committed every 30 seconds
|
|
}
|
|
```
|
|
|
|
### Behavior
|
|
|
|
```
|
|
T=0: Process events 1-500
|
|
T=30: Commit offset 500
|
|
T=60: Commit offset 1200 (processed 500-1200)
|
|
T=90: Commit offset 1800
|
|
```
|
|
|
|
### Performance
|
|
|
|
- **Database writes:** 1 per time interval
|
|
- **Throughput:** High
|
|
- **Latency:** Low
|
|
|
|
### Reprocessing
|
|
|
|
Variable based on processing speed:
|
|
|
|
```
|
|
T=0: Processed events 1-500
|
|
T=15: Processed events 501-1000
|
|
T=29: Processed events 1001-1500 (not committed yet)
|
|
Crash
|
|
On restart: Reprocess events 501-1500
|
|
```
|
|
|
|
### Best For
|
|
|
|
- Time-based workflows
|
|
- Analytics pipelines
|
|
- When consistent commit intervals matter
|
|
- Reducing database load
|
|
|
|
## Manual Strategy
|
|
|
|
**Explicit control over commits.**
|
|
|
|
### Configuration
|
|
|
|
```csharp
|
|
var options = new ConsumerGroupOptions
|
|
{
|
|
CommitStrategy = OffsetCommitStrategy.Manual
|
|
};
|
|
|
|
await foreach (var @event in _consumerGroup.ConsumeAsync(..., options))
|
|
{
|
|
await ProcessEventAsync(@event);
|
|
|
|
// Explicit commit decision
|
|
if (ShouldCommit(@event))
|
|
{
|
|
await _offsetStore.CommitOffsetAsync(
|
|
"orders",
|
|
"order-processing",
|
|
"worker-1",
|
|
@event.Offset);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Use Cases
|
|
|
|
**Conditional commits:**
|
|
```csharp
|
|
// Commit only on specific events
|
|
if (@event.EventType == "OrderCompletedEvent")
|
|
{
|
|
await _offsetStore.CommitOffsetAsync(...);
|
|
}
|
|
```
|
|
|
|
**Transaction-based commits:**
|
|
```csharp
|
|
using var transaction = await _dbContext.Database.BeginTransactionAsync();
|
|
|
|
try
|
|
{
|
|
await ProcessEventAsync(@event);
|
|
await _offsetStore.CommitOffsetAsync(...);
|
|
await transaction.CommitAsync();
|
|
}
|
|
catch
|
|
{
|
|
await transaction.RollbackAsync();
|
|
throw;
|
|
}
|
|
```
|
|
|
|
**Batch with custom size:**
|
|
```csharp
|
|
var processedCount = 0;
|
|
|
|
await foreach (var @event in _consumerGroup.ConsumeAsync(...))
|
|
{
|
|
await ProcessEventAsync(@event);
|
|
processedCount++;
|
|
|
|
// Custom batch size
|
|
if (processedCount >= 500)
|
|
{
|
|
await _offsetStore.CommitOffsetAsync(..., @event.Offset);
|
|
processedCount = 0;
|
|
}
|
|
}
|
|
```
|
|
|
|
### Best For
|
|
|
|
- Complex commit logic
|
|
- Transaction coordination
|
|
- Custom batching requirements
|
|
- Advanced scenarios
|
|
|
|
## Choosing a Strategy
|
|
|
|
### Decision Tree
|
|
|
|
```
|
|
Is throughput > 10,000 events/sec?
|
|
├─ Yes → Use AfterBatch (BatchSize: 1000-5000)
|
|
└─ No
|
|
└─ Can you handle reprocessing?
|
|
├─ Yes → Use AfterBatch (BatchSize: 100-1000)
|
|
└─ No
|
|
└─ Is volume < 1000 events/sec?
|
|
├─ Yes → Use AfterEach
|
|
└─ No → Use AfterBatch + idempotency
|
|
```
|
|
|
|
### Recommendations
|
|
|
|
**High-throughput (> 10k events/sec):**
|
|
```csharp
|
|
new ConsumerGroupOptions
|
|
{
|
|
BatchSize = 5000,
|
|
CommitStrategy = OffsetCommitStrategy.AfterBatch
|
|
}
|
|
```
|
|
|
|
**Medium-throughput (1k-10k events/sec):**
|
|
```csharp
|
|
new ConsumerGroupOptions
|
|
{
|
|
BatchSize = 1000,
|
|
CommitStrategy = OffsetCommitStrategy.AfterBatch
|
|
}
|
|
```
|
|
|
|
**Low-throughput (< 1k events/sec):**
|
|
```csharp
|
|
new ConsumerGroupOptions
|
|
{
|
|
BatchSize = 100,
|
|
CommitStrategy = OffsetCommitStrategy.AfterEach // Or AfterBatch with small batch
|
|
}
|
|
```
|
|
|
|
**Time-sensitive workflows:**
|
|
```csharp
|
|
new ConsumerGroupOptions
|
|
{
|
|
CommitStrategy = OffsetCommitStrategy.Periodic,
|
|
CommitInterval = TimeSpan.FromMinutes(1)
|
|
}
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### ✅ DO
|
|
|
|
- Use AfterBatch for production
|
|
- Set appropriate batch sizes (100-5000)
|
|
- Implement idempotent handlers
|
|
- Monitor commit lag
|
|
- Test reprocessing scenarios
|
|
|
|
### ❌ DON'T
|
|
|
|
- Don't use AfterEach for high-volume streams
|
|
- Don't set very large batch sizes (> 10000)
|
|
- Don't forget to commit in Manual mode
|
|
- Don't ignore reprocessing implications
|
|
|
|
## See Also
|
|
|
|
- [Consumer Groups Overview](README.md)
|
|
- [Offset Management](offset-management.md)
|
|
- [Fault Tolerance](fault-tolerance.md)
|
|
- [Performance Best Practices](../../best-practices/performance.md)
|