PRD 참조. 첫 화면 진입마다 관심종목 수만큼 claude -p가 호출되는 비용 문제를, 자정 사전 적재 + cache-first 조회로 해소한다. backend에 이미 존재하는 스냅샷 계층(테이블·PUT/GET API)을 재사용하고, 비어 있는 writer/reader를 aggregator에 채운다.
Overview
Writer: aggregator 자정 스케줄러가 관심종목·추천을 ML fresh 계산 후 backend 스냅샷에 PUT 적재 (claude 1일 1회)
Reader: aggregator SignalDomainService를 cache-first로 전환 — Redis(L1) → MySQL 스냅샷(L2) → (miss/refresh) ML(L3)
backend·ML 코드 무변경. aggregator에 be.base-url RestClient + SnapshotGateway만 신설
Terminology
용어
정의
Snapshot
ML 시그널/추천 결과를 backend MySQL에 영속화한 캐시 (signal_snapshots, recommendation_snapshots)
SnapshotGateway
aggregator가 backend 스냅샷 PUT/GET API를 호출하는 domain 인터페이스
Cache-first
Redis(L1) → MySQL 스냅샷(L2) → ML(L3) 순으로 조회, 앞 단계 hit 시 claude 미호출
refresh
true면 캐시 무시하고 ML 강제 호출 후 적재. 기본 false
자정 적재
매일 00:00 KST 스케줄러가 ML fresh 결과를 스냅샷에 미리 채우는 작업
Define Problem
AS-IS
FE ──► Aggregator.getSignal(symbol)
└─► ML /signals/{symbol} (항상 호출) ──► claude -p
└─► Redis : ML 장애 시에만 read (비용 절감 X)
SignalApiController : refresh 파라미터 없음
backend signal_snapshots : writer·reader 없음 (미사용)
TO-BE
00:00 KST SnapshotRefreshScheduler ─► RefreshSnapshotsUseCase
└─ 관심종목 ∪ 추천 ─► ML fresh(claude) ─► SnapshotGateway.save ─► backend PUT
FE ──► Aggregator.getSignal(symbol, refresh)
refresh=false : Redis → SnapshotGateway.find(backend GET) → (miss) ML fresh + save
refresh=true : ML fresh + save (claude)
Possible Solutions
방안
설명
채택 여부
aggregator writer/reader (선택)
기존 backend PUT/GET 스냅샷 API와 미사용 be.base-url을 활용. aggregator에 게이트웨이·스케줄러·cache-first만 추가
채택 — 신규 테이블 0, backend 무변경, 의도된 설계 완성
backend writer/reader
스냅샷 도메인이 있는 backend에 ML 게이트웨이·스케줄러 신설, FE가 backend 직접 조회
sequenceDiagram
participant FE
participant DS as SignalDomainService
participant CR as SignalCacheRepository
participant Snap as SnapshotGateway
participant ML
FE->>DS: getSignal("005930", refresh=false)
DS->>CR: findSignal (Redis L1)
alt Redis hit
CR-->>DS: SignalResult
else Redis miss
DS->>Snap: findSignal (backend GET, L2)
alt Snapshot hit
Snap-->>DS: SignalResult (fromCache)
DS->>CR: saveSignal (Redis 워밍)
else Snapshot miss
DS->>ML: GET /signals (claude -p, L3)
ML-->>DS: SignalResult
DS->>CR: saveSignal
DS->>Snap: saveSignal (backend PUT)
end
end
DS-->>FE: SignalResult
ERD
신규 테이블 없음 — backend 기존 signal_snapshots·recommendation_snapshots를 재사용한다.