Cache KBO live game list with TTL and game-hour gate
- gatherUserContext가 채팅 매 호출마다 오늘 라이브 일정을 fetch하던 것을 60초 인메모리 캐시로 공유 — 같은 시간창 호출이 1회 크롤을 공유 - KST 경기 시간대(14시 이후) 밖이면 라이브 merge를 건너뛰어 하루 대부분의 불필요한 외부 KBO 크롤 제거 - 동시 사용자 증가 시 외부 엔드포인트 부하·응답 레이턴시 완화
This commit is contained in:
parent
603486d629
commit
fc285a54c6
@ -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) {
|
||||
// 실패를 호출자에게 전파하지 않는다 — 일정 화면이 깨지는 것보다 점수 미반영이 낫다.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user