# 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(); // 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)