private async Task <long> StreamEventsToProjections(ExclusiveEventStoreCatchupQuery query) { long eventsProcessedOutOfBatch = 0; foreach (var storedEvent in query.Events) { eventsProcessedOutOfBatch++; IncludeReadModelsNeeding(storedEvent); if (cancellationDisposable.IsDisposed) { break; } IEvent @event = null; var now = Clock.Now(); try { @event = await UpdateProjectorsAndCursors( query, storedEvent, eventsProcessedOutOfBatch, now); } catch (Exception ex) { var error = @event == null ? SerializationError(ex, storedEvent) : new Domain.EventHandlingError(ex, @event: @event); ReadModelUpdate.ReportFailure( error, createReadModelDbContext); } var status = new ReadModelCatchupStatus { BatchCount = query.BatchMatchedEventCount, NumberOfEventsProcessed = eventsProcessedOutOfBatch, CurrentEventId = storedEvent.Id, EventTimestamp = storedEvent.Timestamp, StatusTimeStamp = now, CatchupName = Name }; if (status.IsEndOfBatch) { // reset the re-entrancy flag running = 0; query.Dispose(); } ReportStatus(status); } return(eventsProcessedOutOfBatch); }
private async Task <long> StreamEventsToProjections(ExclusiveEventStoreCatchupQuery query) { long eventsProcessed = 0; foreach (var storedEvent in query.Events) { eventsProcessed++; IncludeReadModelsNeeding(storedEvent); if (cancellationDisposable.IsDisposed) { break; } IEvent @event = null; var now = Clock.Now(); try { // update projectors @event = storedEvent.ToDomainEvent(); if (@event != null) { using (var work = CreateUnitOfWork(@event)) { await bus.PublishAsync(@event); var infos = work.Resource <DbContext>().Set <ReadModelInfo>(); subscribedReadModelInfos.ForEach(i => { var eventsRemaining = query.ExpectedNumberOfEvents - eventsProcessed; infos.Attach(i); i.LastUpdated = now; i.CurrentAsOfEventId = storedEvent.Id; i.LatencyInMilliseconds = (now - @event.Timestamp).TotalMilliseconds; i.BatchRemainingEvents = eventsRemaining; if (eventsProcessed == 1) { i.BatchStartTime = now; i.BatchTotalEvents = query.ExpectedNumberOfEvents; } if (i.InitialCatchupStartTime == null) { i.InitialCatchupStartTime = now; i.InitialCatchupEvents = query.ExpectedNumberOfEvents; } if (eventsRemaining == 0 && i.InitialCatchupEndTime == null) { i.InitialCatchupEndTime = now; } }); work.VoteCommit(); } } else { throw new SerializationException(string.Format( "Deserialization: Event type '{0}.{1}' not found", storedEvent.StreamName, storedEvent.Type)); } } catch (Exception ex) { var error = @event == null ? SerializationError(ex, storedEvent) : new Domain.EventHandlingError(ex, @event: @event); ReadModelUpdate.ReportFailure( error, () => CreateReadModelDbContext()); } var status = new ReadModelCatchupStatus { BatchCount = query.ExpectedNumberOfEvents, NumberOfEventsProcessed = eventsProcessed, CurrentEventId = storedEvent.Id, EventTimestamp = storedEvent.Timestamp, StatusTimeStamp = now, CatchupName = Name }; if (status.IsEndOfBatch) { // reset the re-entrancy flag running = 0; query.Dispose(); } ReportStatus(status); } return(eventsProcessed); }
/// <summary> /// Runs a single catchup operation, which will catch up the subscribed projectors through the latest recorded event. /// </summary> /// <remarks>This method will return immediately without performing any updates if another catchup is currently in progress for the same read model database.</remarks> public async Task <ReadModelCatchupResult> Run() { // perform a re-entrancy check so that multiple catchups do not try to run concurrently if (Interlocked.CompareExchange(ref running, 1, 0) != 0) { Debug.WriteLine(string.Format("Catchup {0}: ReadModelCatchup already in progress. Skipping.", Name), ToString()); return(ReadModelCatchupResult.CatchupAlreadyInProgress); } EnsureInitialized(); long eventsProcessed = 0; var stopwatch = new Stopwatch(); // iterate over the events in order try { using (var query = new ExclusiveEventStoreCatchupQuery( await CreateOpenEventStoreDbContext(), lockResourceName, GetStartingId, matchEvents)) { ReportStatus(new ReadModelCatchupStatus { BatchCount = query.ExpectedNumberOfEvents, NumberOfEventsProcessed = eventsProcessed, CurrentEventId = query.StartAtId, CatchupName = Name }); Debug.WriteLine(new { query }); if (query.ExpectedNumberOfEvents == 0) { return(ReadModelCatchupResult.CatchupRanButNoNewEvents); } Debug.WriteLine(string.Format("Catchup {0}: Beginning replay of {1} events", Name, query.ExpectedNumberOfEvents)); stopwatch.Start(); eventsProcessed = await StreamEventsToProjections(query); } } catch (Exception exception) { // TODO: (Run) this should probably throw Debug.WriteLine(string.Format("Catchup {0}: Read model catchup failed after {1}ms at {2} events.\n{3}", Name, stopwatch.ElapsedMilliseconds, eventsProcessed, exception)); } finally { // reset the re-entrancy flag running = 0; Debug.WriteLine(string.Format("Catchup {0}: Catchup batch done.", Name)); } stopwatch.Stop(); if (eventsProcessed > 0) { Debug.WriteLine( "Catchup {0}: {1} events projected in {2}ms ({3}ms/event)", Name, eventsProcessed, stopwatch.ElapsedMilliseconds, (stopwatch.ElapsedMilliseconds / eventsProcessed)); } return(ReadModelCatchupResult.CatchupRanAndHandledNewEvents); }
private async Task <IEvent> UpdateProjectorsAndCursors( ExclusiveEventStoreCatchupQuery query, StorableEvent storedEvent, long eventsProcessedOutOfBatch, DateTimeOffset now) { var @event = storedEvent.ToDomainEvent(); if (@event != null) { using (var work = CreateUnitOfWork(@event)) { var errors = new ConcurrentBag <Domain.EventHandlingError>(); using (CaptureErrorsFor(@event, into: errors)) { await bus.PublishAsync(@event); } var infos = work.Resource <DbContext>().Set <ReadModelInfo>(); subscribedReadModelInfos .Where(i => errors.All(err => err.Handler != i.Handler)) .ForEach(i => { infos.Attach(i); i.LastUpdated = now; i.CurrentAsOfEventId = storedEvent.Id; i.LatencyInMilliseconds = (now - @event.Timestamp).TotalMilliseconds; i.BatchRemainingEvents = query.BatchMatchedEventCount - eventsProcessedOutOfBatch; if (eventsProcessedOutOfBatch == 1) { i.BatchStartTime = now; i.BatchTotalEvents = query.BatchMatchedEventCount; } if (i.InitialCatchupStartTime == null) { i.InitialCatchupStartTime = now; i.InitialCatchupTotalEvents = eventStoreTotalCount; } if (i.InitialCatchupEndTime == null) { if (i.CurrentAsOfEventId >= initialCatchupIsDoneAfterEventId) { i.InitialCatchupEndTime = now; i.InitialCatchupRemainingEvents = 0; } else { // initial catchup is still in progress i.InitialCatchupRemainingEvents = query.TotalMatchedEventCount - eventsProcessedOutOfBatch; } } }); work.VoteCommit(); } } else { throw new SerializationException($"Deserialization: Event type '{storedEvent.StreamName}.{storedEvent.Type}' not found"); } return(@event); }
/// <summary> /// Runs a single catchup operation, which will catch up the subscribed projectors through the latest recorded event. /// </summary> /// <remarks>This method will return immediately without performing any updates if another catchup is currently in progress for the same read model database.</remarks> public async Task <ReadModelCatchupResult> Run() { if (disposables.IsDisposed) { throw new ObjectDisposedException($"The catchup has been disposed. ({this})"); } // perform a re-entrancy check so that multiple catchups do not try to run concurrently if (Interlocked.CompareExchange(ref running, 1, 0) != 0) { Debug.WriteLine($"Catchup {Name}: ReadModelCatchup already in progress. Skipping.", ToString()); return(ReadModelCatchupResult.CatchupAlreadyInProgress); } EnsureInitialized(); long eventsProcessed = 0; var stopwatch = new Stopwatch(); // iterate over the events in order try { using (var query = new ExclusiveEventStoreCatchupQuery( await CreateOpenEventStoreDbContext(), lockResourceName, GetStartingId, q => q.Where(matchEvents, filter), batchSize)) { ReportStatus(new ReadModelCatchupStatus { BatchCount = query.BatchMatchedEventCount, NumberOfEventsProcessed = eventsProcessed, CurrentEventId = query.StartAtId, CatchupName = Name }); Debug.WriteLine(new { query }); if (query.BatchMatchedEventCount == 0) { return(ReadModelCatchupResult.CatchupRanButNoNewEvents); } Debug.WriteLine($"Catchup {Name}: Beginning replay of {query.BatchMatchedEventCount} events"); stopwatch.Start(); eventsProcessed = await StreamEventsToProjections(query); } } catch (Exception exception) { // TODO: (Run) this should probably throw Trace.WriteLine($"Catchup {Name}: Read model catchup failed after {stopwatch.ElapsedMilliseconds}ms at {eventsProcessed} events.\n{exception}"); } finally { // reset the re-entrancy flag running = 0; Debug.WriteLine($"Catchup {Name}: Catchup batch done."); } stopwatch.Stop(); if (eventsProcessed > 0) { Debug.WriteLine( $"Catchup {Name}: {eventsProcessed} events projected in {stopwatch.ElapsedMilliseconds}ms ({stopwatch.ElapsedMilliseconds/eventsProcessed}ms/event)"); } return(ReadModelCatchupResult.CatchupRanAndHandledNewEvents); }