dotnet-cqrs/docs/event-streaming/event-replay/replay-from-time.md

368 lines
8.5 KiB
Markdown

# Time-Based Event Replay
Replay events from specific timestamps for time-travel debugging and historical analysis.
## Overview
Time-based replay allows you to process events that occurred during specific time periods, enabling:
- Time-travel debugging to specific moments
- Historical data reprocessing after bug fixes
- Creating new projections from specific dates
- Auditing and compliance queries
## Quick Start
```csharp
using Svrnty.CQRS.Events.Abstractions;
var replayService = serviceProvider.GetRequiredService<IEventReplayService>();
// Replay from 7 days ago
await foreach (var @event in replayService.ReplayFromTimeAsync(
streamName: "orders",
startTime: DateTimeOffset.UtcNow.AddDays(-7)))
{
await ProcessEventAsync(@event);
}
```
## Replay From Specific Time
```csharp
// Replay from specific UTC time
var startTime = new DateTimeOffset(2025, 12, 1, 0, 0, 0, TimeSpan.Zero);
await foreach (var @event in replayService.ReplayFromTimeAsync(
"orders",
startTime,
options: new ReplayOptions
{
BatchSize = 100,
MaxEventsPerSecond = 1000
}))
{
await RebuildProjectionAsync(@event);
}
```
## Replay Time Range
Process events within a specific time window:
```csharp
// Replay single day
var startTime = new DateTimeOffset(2025, 12, 1, 0, 0, 0, TimeSpan.Zero);
var endTime = new DateTimeOffset(2025, 12, 2, 0, 0, 0, TimeSpan.Zero);
await foreach (var @event in replayService.ReplayTimeRangeAsync(
streamName: "analytics",
startTime: startTime,
endTime: endTime))
{
await ProcessAnalyticsEventAsync(@event);
}
```
## Common Time-Based Scenarios
### Last 24 Hours
```csharp
await foreach (var @event in replayService.ReplayFromTimeAsync(
"orders",
DateTimeOffset.UtcNow.AddHours(-24)))
{
await ProcessRecentEventAsync(@event);
}
```
### Specific Month
```csharp
var startOfMonth = new DateTimeOffset(2025, 11, 1, 0, 0, 0, TimeSpan.Zero);
var endOfMonth = new DateTimeOffset(2025, 12, 1, 0, 0, 0, TimeSpan.Zero);
await foreach (var @event in replayService.ReplayTimeRangeAsync(
"sales",
startOfMonth,
endOfMonth))
{
await ProcessMonthlyReportAsync(@event);
}
```
### Business Hours Only
```csharp
var businessStart = new DateTimeOffset(2025, 12, 1, 9, 0, 0, TimeSpan.Zero); // 9 AM
var businessEnd = new DateTimeOffset(2025, 12, 1, 17, 0, 0, TimeSpan.Zero); // 5 PM
await foreach (var @event in replayService.ReplayTimeRangeAsync(
"customer-interactions",
businessStart,
businessEnd))
{
await AnalyzeBusinessHoursActivityAsync(@event);
}
```
## Replay Options
Configure replay behavior with `ReplayOptions`:
```csharp
var options = new ReplayOptions
{
BatchSize = 100, // Events per database query
MaxEventsPerSecond = 1000, // Rate limit
EventTypeFilter = new[] { "OrderPlaced", "OrderShipped" },
MaxEvents = 10000, // Stop after 10k events
ProgressCallback = progress =>
{
Console.WriteLine($"Progress: {progress.EventsProcessed} events");
Console.WriteLine($"Rate: {progress.EventsPerSecond:F0} events/sec");
Console.WriteLine($"ETA: {progress.EstimatedCompletion}");
},
ProgressInterval = 1000 // Callback every 1000 events
};
await foreach (var @event in replayService.ReplayFromTimeAsync(
"orders",
DateTimeOffset.UtcNow.AddDays(-30),
options))
{
await ProcessEventAsync(@event);
}
```
## Time Zone Considerations
All timestamps are in UTC:
```csharp
// ✅ Good - UTC
var startTime = new DateTimeOffset(2025, 12, 1, 0, 0, 0, TimeSpan.Zero);
// ❌ Bad - Local time (will be converted to UTC)
var startTime = DateTime.Now.AddDays(-7); // Avoid local time
// ✅ Better - Explicit UTC
var startTime = DateTime.UtcNow.AddDays(-7);
// ✅ Best - DateTimeOffset with explicit offset
var startTime = DateTimeOffset.UtcNow.AddDays(-7);
```
## Performance Optimization
### Batch Size Tuning
```csharp
// Large time range - use larger batches
var options = new ReplayOptions
{
BatchSize = 1000 // Reduce database round-trips
};
await foreach (var @event in replayService.ReplayFromTimeAsync(
"orders",
DateTimeOffset.UtcNow.AddMonths(-6),
options))
{
await ProcessEventAsync(@event);
}
```
### Rate Limiting
```csharp
// Production replay - limit impact
var options = new ReplayOptions
{
MaxEventsPerSecond = 100 // Gentle on system
};
await foreach (var @event in replayService.ReplayFromTimeAsync(
"orders",
DateTimeOffset.UtcNow.AddDays(-30),
options))
{
await ProcessEventAsync(@event);
}
```
## Combining with Offset-Based Replay
You can combine time-based and offset-based replay:
```csharp
// Find offset for specific time
var events = replayService.ReplayFromTimeAsync("orders", specificTime);
var firstEvent = await events.FirstOrDefaultAsync();
if (firstEvent != null)
{
// Now replay from that offset
await foreach (var @event in replayService.ReplayFromOffsetAsync(
"orders",
startOffset: firstEvent.Offset))
{
await ProcessEventAsync(@event);
}
}
```
## Use Cases
### Time-Travel Debugging
```csharp
// Reproduce bug that occurred at specific time
var bugTime = new DateTimeOffset(2025, 12, 1, 14, 30, 0, TimeSpan.Zero);
var windowStart = bugTime.AddMinutes(-5);
var windowEnd = bugTime.AddMinutes(5);
await foreach (var @event in replayService.ReplayTimeRangeAsync(
"orders",
windowStart,
windowEnd))
{
Console.WriteLine($"{@event.Timestamp}: {@event.EventType} - {@event.EventId}");
// Debug state at each event
}
```
### Historical Reporting
```csharp
// Generate monthly reports from historical data
for (int month = 1; month <= 12; month++)
{
var startOfMonth = new DateTimeOffset(2025, month, 1, 0, 0, 0, TimeSpan.Zero);
var endOfMonth = startOfMonth.AddMonths(1);
var report = new MonthlyReport { Month = month };
await foreach (var @event in replayService.ReplayTimeRangeAsync(
"sales",
startOfMonth,
endOfMonth))
{
report.ProcessEvent(@event);
}
await SaveReportAsync(report);
}
```
### Compliance Auditing
```csharp
// Audit all user actions during compliance period
var auditStart = new DateTimeOffset(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
var auditEnd = new DateTimeOffset(2026, 1, 1, 0, 0, 0, TimeSpan.Zero);
await foreach (var @event in replayService.ReplayTimeRangeAsync(
"user-actions",
auditStart,
auditEnd,
options: new ReplayOptions
{
EventTypeFilter = new[] { "UserLogin", "DataAccess", "DataModification" }
}))
{
await LogAuditEventAsync(@event);
}
```
## Database Considerations
### Index Requirements
Time-based replay relies on timestamp indexes:
```sql
-- Ensure index exists
CREATE INDEX IF NOT EXISTS idx_events_stream_timestamp
ON events(stream_name, timestamp);
```
### Query Performance
```csharp
// Efficient - narrow time range
await foreach (var @event in replayService.ReplayTimeRangeAsync(
"orders",
DateTimeOffset.UtcNow.AddHours(-1),
DateTimeOffset.UtcNow))
{
// Fast query
}
// Less efficient - wide time range
await foreach (var @event in replayService.ReplayFromTimeAsync(
"orders",
DateTimeOffset.UtcNow.AddYears(-1)))
{
// May scan many events
}
```
## Error Handling
```csharp
try
{
await foreach (var @event in replayService.ReplayFromTimeAsync(
"orders",
DateTimeOffset.UtcNow.AddDays(-7)))
{
try
{
await ProcessEventAsync(@event);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to process event {EventId}", @event.EventId);
// Continue processing other events
}
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Replay cancelled by user");
}
catch (Exception ex)
{
_logger.LogError(ex, "Replay failed");
throw;
}
```
## Best Practices
### ✅ DO
- Use UTC timestamps consistently
- Add progress callbacks for long replays
- Use rate limiting for production replays
- Filter by event type when possible
- Handle individual event errors gracefully
- Log replay start and completion
### ❌ DON'T
- Don't use local time without explicit conversion
- Don't replay large time ranges without rate limiting
- Don't ignore progress tracking
- Don't replay in production without testing first
- Don't forget to handle cancellation
## See Also
- [Replay From Offset](replay-from-offset.md)
- [Rate Limiting](rate-limiting.md)
- [Progress Tracking](progress-tracking.md)
- [Event Replay Overview](README.md)
- [Projections](../projections/README.md)