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