Cache KBO live game list with TTL and game-hour gate

- gatherUserContext가 채팅 매 호출마다 오늘 라이브 일정을 fetch하던 것을 60초 인메모리 캐시로 공유 — 같은 시간창 호출이 1회 크롤을 공유
- KST 경기 시간대(14시 이후) 밖이면 라이브 merge를 건너뛰어 하루 대부분의 불필요한 외부 KBO 크롤 제거
- 동시 사용자 증가 시 외부 엔드포인트 부하·응답 레이턴시 완화
This commit is contained in:
윤정민 2026-06-26 23:06:08 +09:00
parent 603486d629
commit fc285a54c6

View File

@ -205,6 +205,44 @@ function liveKey(
* @param series (/ ). `getGameList` .
* @returns · . .
*/
/**
* TTL (: ymd|series).
*
* `gatherUserContext` `getGameList`
* ( {{todaySchedule}}), KBO
* . 1 fetch를
* ( 30) .
* / , best-effort .
*/
const LIVE_GAMELIST_TTL_MS = 60_000;
const liveGameListCache = new Map<string, { at: number; games: GameListRecord[] }>();
/**
* KST merge를 .
*
* KBO 14:00()~23:30 KST에 .
* "예정" "종료" fetch가 .
* (ymd) `ymd !== today` .
*/
// 서버 로컬시간 = KST(.env의 TZ=Asia/Seoul). 위의 formatYmd(new Date())와 동일 기준.
const KST_GAME_WINDOW_START_HOUR = 14;
async function getLiveGameListCached(
ymd: string,
series: string | undefined
): Promise<GameListRecord[]> {
const key = `${ymd}|${series ?? ""}`;
const hit = liveGameListCache.get(key);
const now = Date.now();
if (hit && now - hit.at < LIVE_GAMELIST_TTL_MS) {
console.log(`[kbo-merge] live cache hit key=${key} age=${now - hit.at}ms`);
return hit.games;
}
const result = await getGameList(ymd, series);
liveGameListCache.set(key, { at: now, games: result.games });
return result.games;
}
async function mergeLiveIntoSchedule(
ymd: string,
games: ScheduleGame[],
@ -220,11 +258,17 @@ async function mergeLiveIntoSchedule(
return games;
}
// 경기 시간대 밖이면 라이브 정보가 변하지 않으므로 외부 호출 없이 즉시 반환한다.
const hour = new Date().getHours();
if (hour < KST_GAME_WINDOW_START_HOUR) {
console.log(`[kbo-merge] skip: off-hours kstHour=${hour}`);
return games;
}
// 실시간 경기 목록 조회. 네트워크/파싱 실패 시 원본 일정으로 graceful degradation.
let live: GameListRecord[];
try {
const result = await getGameList(ymd, series);
live = result.games;
live = await getLiveGameListCached(ymd, series);
console.log(`[kbo-merge] fetched live count=${live.length}`);
} catch (e) {
// 실패를 호출자에게 전파하지 않는다 — 일정 화면이 깨지는 것보다 점수 미반영이 낫다.