perf(detect-anomalies): batch snapshot loads into a single query (HIGH)
DetectAnomaliesUseCase was issuing one ISnapshotRepository.ListByEventAsync call per event each cycle, with each call rehydrating that event's bets via Include(s => s.Bets) — O(N) SQLite round-trips and N Include payloads on every detection cycle. * Add ISnapshotRepository.ListByEventsAsync(IReadOnlyCollection<EventId>, …) returning a per-event dictionary; events with no snapshots in range get Array.Empty<OddsSnapshot>() so the caller doesn't need a presence check. * Implementation uses a single .Where(s => ids.Contains(s.EventCode)) query and groups in memory. * DetectAnomaliesUseCase loads the whole batch once before the foreach, then ProcessEventAsync receives the per-event slice as a parameter. * Tests updated to stub the new method; per-event-failure test now exercises an AddAsync throw rather than a snapshot-load throw, since individual snapshot loads no longer fail per-event.
This commit is contained in:
@@ -75,13 +75,22 @@ public sealed class DetectAnomaliesUseCase
|
||||
// (O(N_events) round-trips). Reviewer W1, Phase 7.
|
||||
var existingAnomalies = await _anomalyRepo.ListAsync(ct);
|
||||
|
||||
// Single batched query for all events' snapshots — replaces the prior
|
||||
// per-event ListByEventAsync round-trip (O(N) SQLite hits + N Include(Bets)
|
||||
// payloads). Returns an empty list for events with no snapshots in range.
|
||||
var eventIds = events.Select(e => e.Id).ToList();
|
||||
var snapshotsByEvent = await _snapshotRepo.ListByEventsAsync(eventIds, from, now, ct);
|
||||
|
||||
foreach (var ev in events)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
try
|
||||
{
|
||||
newAnomalyCount += await ProcessEventAsync(detector, ev, from, now, existingAnomalies, ct);
|
||||
var snapshots = snapshotsByEvent.TryGetValue(ev.Id, out var found)
|
||||
? found
|
||||
: Array.Empty<OddsSnapshot>();
|
||||
newAnomalyCount += await ProcessEventAsync(detector, ev, snapshots, existingAnomalies, ct);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -107,13 +116,11 @@ public sealed class DetectAnomaliesUseCase
|
||||
private async Task<int> ProcessEventAsync(
|
||||
AnomalyDetector detector,
|
||||
Event ev,
|
||||
DateTimeOffset from,
|
||||
DateTimeOffset to,
|
||||
IReadOnlyList<OddsSnapshot> snapshots,
|
||||
IReadOnlyList<Anomaly> existingAnomalies,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var snapshots = await _snapshotRepo.ListByEventAsync(ev.Id, from, to, ct);
|
||||
var detected = detector.Detect(ev.Id, snapshots);
|
||||
var detected = detector.Detect(ev.Id, snapshots);
|
||||
|
||||
if (detected.Count == 0)
|
||||
return 0;
|
||||
|
||||
Reference in New Issue
Block a user