goldenChat base source add
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* Upbit 캔들 REST 요청 중복 제거(deduplication) + 단기 캐시
|
||||
*
|
||||
* 멀티차트 모드에서 동일한 종목/타임프레임을 여러 슬롯이 동시에 요청하면
|
||||
* 단 1개의 HTTP 요청만 실행하고 나머지는 같은 Promise를 공유한다.
|
||||
* 데이터는 CACHE_TTL(30초) 동안 메모리에 보관해 재요청을 방지한다.
|
||||
*
|
||||
* ┌ 슬롯0 ─── fetchUpbitCandlesCached('KRW-BTC', '1D', 300) ───┐
|
||||
* ├ 슬롯1 ─── fetchUpbitCandlesCached('KRW-BTC', '1D', 300) ───┼─▶ fetchUpbitCandles 1회만 실행
|
||||
* └ 슬롯2 ─── fetchUpbitCandlesCached('KRW-BTC', '1D', 300) ───┘
|
||||
*/
|
||||
import type { OHLCVBar, Timeframe } from '../types';
|
||||
import { fetchUpbitCandles } from './upbitApi';
|
||||
|
||||
// ─── 과거(이전 시점) 데이터 전용 캐시 ─────────────────────────────────────────
|
||||
// 역사 캔들은 변하지 않으므로 일반 30초보다 긴 TTL 을 사용한다.
|
||||
const HISTORY_CACHE_TTL = 5 * 60_000; // 5분
|
||||
|
||||
const historyCache = new Map<string, { data: OHLCVBar[]; fetchedAt: number }>();
|
||||
const historyPending = new Map<string, Promise<OHLCVBar[]>>();
|
||||
|
||||
function historyKey(market: string, timeframe: Timeframe, count: number, before: string) {
|
||||
return `${market.toUpperCase()}:${timeframe}:${count}:${before}`;
|
||||
}
|
||||
|
||||
/** 캐시 유효 기간 (ms) */
|
||||
const CACHE_TTL = 30_000;
|
||||
|
||||
interface CacheEntry {
|
||||
data: OHLCVBar[];
|
||||
fetchedAt: number;
|
||||
}
|
||||
|
||||
// 완료된 요청 캐시 (market:timeframe:count → entry)
|
||||
const dataCache = new Map<string, CacheEntry>();
|
||||
|
||||
// 진행 중인 요청 (중복 방지용, market:timeframe:count → Promise)
|
||||
const pendingMap = new Map<string, Promise<OHLCVBar[]>>();
|
||||
|
||||
function cacheKey(market: string, timeframe: Timeframe, count: number): string {
|
||||
return `${market.toUpperCase()}:${timeframe}:${count}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 캐시·in-flight 중복 제거를 적용한 캔들 요청.
|
||||
* - 동일 인자로 동시 호출 시: 첫 번째 요청 Promise 를 공유한다.
|
||||
* - CACHE_TTL 이내 재호출 시: 캐시된 데이터를 즉시 반환한다.
|
||||
* - invalidate=true 로 호출 시: 캐시를 무효화하고 새 요청을 실행한다.
|
||||
*/
|
||||
export async function fetchUpbitCandlesCached(
|
||||
market: string,
|
||||
timeframe: Timeframe,
|
||||
count: number,
|
||||
invalidate = false,
|
||||
): Promise<OHLCVBar[]> {
|
||||
const key = cacheKey(market, timeframe, count);
|
||||
|
||||
// 캐시 무효화 요청이면 기존 항목 삭제
|
||||
if (invalidate) {
|
||||
dataCache.delete(key);
|
||||
pendingMap.delete(key);
|
||||
}
|
||||
|
||||
// 유효한 캐시가 있으면 즉시 반환
|
||||
const cached = dataCache.get(key);
|
||||
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
// 이미 진행 중인 요청이 있으면 공유
|
||||
const pending = pendingMap.get(key);
|
||||
if (pending) return pending;
|
||||
|
||||
// 새 요청 실행
|
||||
const promise = fetchUpbitCandles(market, timeframe, count)
|
||||
.then(data => {
|
||||
dataCache.set(key, { data, fetchedAt: Date.now() });
|
||||
pendingMap.delete(key);
|
||||
return data;
|
||||
})
|
||||
.catch(err => {
|
||||
// 실패 시 pending 제거 → 다음 호출이 재시도할 수 있도록
|
||||
pendingMap.delete(key);
|
||||
throw err;
|
||||
});
|
||||
|
||||
pendingMap.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
|
||||
/** 특정 종목의 캐시 항목을 모두 삭제 (종목 변경 시 호출) */
|
||||
export function invalidateMarketCache(market: string): void {
|
||||
const prefix = market.toUpperCase() + ':';
|
||||
for (const key of [...dataCache.keys(), ...pendingMap.keys()]) {
|
||||
if (key.startsWith(prefix)) {
|
||||
dataCache.delete(key);
|
||||
pendingMap.delete(key);
|
||||
}
|
||||
}
|
||||
// 역사 캐시도 함께 삭제
|
||||
for (const key of [...historyCache.keys(), ...historyPending.keys()]) {
|
||||
if (key.startsWith(prefix)) {
|
||||
historyCache.delete(key);
|
||||
historyPending.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 시각 이전 캔들 페치 (캐시·in-flight 중복 제거 적용).
|
||||
*
|
||||
* @param before - ISO 8601 UTC exclusive (예: '2024-01-01T00:00:00')
|
||||
* candleEndpoint 에서 'Z' 를 붙여 요청하므로 'Z' 없이 전달한다.
|
||||
*/
|
||||
export async function fetchUpbitCandlesBeforeCached(
|
||||
market: string,
|
||||
timeframe: Timeframe,
|
||||
count: number,
|
||||
before: string,
|
||||
): Promise<OHLCVBar[]> {
|
||||
const key = historyKey(market, timeframe, count, before);
|
||||
|
||||
const cached = historyCache.get(key);
|
||||
if (cached && Date.now() - cached.fetchedAt < HISTORY_CACHE_TTL) {
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
const pending = historyPending.get(key);
|
||||
if (pending) return pending;
|
||||
|
||||
const promise = fetchUpbitCandles(market, timeframe, count, before)
|
||||
.then(data => {
|
||||
historyCache.set(key, { data, fetchedAt: Date.now() });
|
||||
historyPending.delete(key);
|
||||
return data;
|
||||
})
|
||||
.catch(err => {
|
||||
historyPending.delete(key);
|
||||
throw err;
|
||||
});
|
||||
|
||||
historyPending.set(key, promise);
|
||||
return promise;
|
||||
}
|
||||
Reference in New Issue
Block a user