SeibroRollingSweep.cs 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455
  1. namespace Application.Helpers;
  2. /// <summary>
  3. /// SEIBro per-키 rolling 수집의 공용 오케스트레이션 (순수 로직, DB 비의존 — 테스트 가능). KrxBackfill 의 자매 헬퍼 —
  4. /// KrxBackfill 이 "날짜 스윕"(기간 파라미터, 전시장 반환)이라면 이쪽은 "키 스윕"(ISIN/발행회사번호별 개별 호출)이다:
  5. /// • 대상 = (키, 마지막 갱신시각) 목록. 미처리(LastUpdatedAt == null) 를 먼저, 그다음 오래된(stale) 순으로 처리한다
  6. /// → 여러 날에 걸친 실행으로 전체 유니버스가 자동 보강·순환 갱신된다 (resumable).
  7. /// • fetch 횟수가 maxPerRun 에 도달하면 즉시 멈춘다 → 하루 배치가 카테고리 quota(SeibroQuota) 상한 안에서 조금씩 채운다.
  8. /// • quota 보호를 위해 호출 사이에 delayMs 만큼 쉰다 (첫 호출 전에는 쉬지 않는다 — KrxBackfill 동일).
  9. /// • 동률(같은 LastUpdatedAt·둘 다 null)은 입력 순서를 보존한다 (stable sort).
  10. /// 반환값은 이번 실행에서 실제 fetch 한 키 수.
  11. /// </summary>
  12. public static class SeibroRollingSweep
  13. {
  14. /// <param name="targets">대상 키 + 마지막 갱신시각 (null = 미처리 → 최우선)</param>
  15. /// <param name="fetchAndUpsert">해당 키를 수집·upsert (부작용 담당)</param>
  16. /// <param name="maxPerRun">이번 실행 최대 fetch 수 (quota 상한). 0 이하면 fetch 안 함</param>
  17. /// <param name="delayMs">fetch 호출 사이 지연(ms). 0 이하면 지연 없음</param>
  18. public static async Task<int> RunAsync<TKey>(
  19. IReadOnlyList<(TKey Key, DateTime? LastUpdatedAt)> targets,
  20. Func<TKey, CancellationToken, Task> fetchAndUpsert,
  21. int maxPerRun,
  22. int delayMs,
  23. CancellationToken ct)
  24. {
  25. if (maxPerRun <= 0)
  26. {
  27. return 0;
  28. }
  29. // 미처리(null) 우선, 그다음 오래된 순 — OrderBy 는 stable 이라 동률은 입력 순서 유지
  30. var ordered = targets.OrderBy(c => c.LastUpdatedAt.HasValue ? 1 : 0).ThenBy(c => c.LastUpdatedAt ?? DateTime.MinValue);
  31. var fetched = 0;
  32. foreach (var (key, _) in ordered)
  33. {
  34. ct.ThrowIfCancellationRequested();
  35. if (fetched > 0 && delayMs > 0)
  36. {
  37. await Task.Delay(delayMs, ct);
  38. }
  39. await fetchAndUpsert(key, ct);
  40. fetched++;
  41. if (fetched >= maxPerRun)
  42. {
  43. break;
  44. }
  45. }
  46. return fetched;
  47. }
  48. }