Add getAll+diff to syncGamesForMonth to skip no-op writes
- KBO에서 가져온 경기 일정을 기존 DB 데이터와 비교해 변경된 항목만 Firestore에 저장 - 변경 없는 경기의 반복 write를 제거해 월간 write 비용 대폭 감소
This commit is contained in:
parent
e76b752a41
commit
12f21803bc
@ -52,26 +52,63 @@ export function toGameDoc(year: number, g: ScheduleGame): Game | null {
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 새로 도출한 game doc이 기존 문서와 동일한지 비교한다(merge write가 no-op이 될지 판정).
|
||||
*
|
||||
* `merge: true`로 쓰므로 새 doc에 **포함된 필드만** 비교한다. 새 doc이 생략한 필드
|
||||
* (예: scheduled 경기의 `winningTeamCode`)는 merge가 건드리지 않으므로 비교 대상에서 제외한다.
|
||||
* `time`은 Timestamp이므로 millis로 비교한다.
|
||||
*/
|
||||
function gameDocUnchanged(existing: Partial<Game>, doc: Game): boolean {
|
||||
for (const [key, value] of Object.entries(doc) as Array<[keyof Game, unknown]>) {
|
||||
if (key === "time") {
|
||||
const a = existing.time?.toMillis();
|
||||
const b = (value as Timestamp).toMillis();
|
||||
if (a !== b) return false;
|
||||
} else if (existing[key] !== value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 특정 연도·월의 KBO 경기 일정을 fetch하여 Firestore `games` 컬렉션에 upsert한다.
|
||||
*
|
||||
* - `merge: true`로 기존 문서의 수동 필드(예: 외부에서 채운 `winningTeamCode`)를 보존한다.
|
||||
* - gameId가 없는 일정 엔트리는 스킵.
|
||||
* - 사전 `getAll` read로 기존 값과 비교하여 **변경이 있는 문서만** write한다 —
|
||||
* 대부분 불변인 일정 데이터를 매일 같은 값으로 덮어쓰는 무의미한 write를 줄인다.
|
||||
* (값이 동일하면 `onGameCompleted` 트리거도 점화되지 않으므로 후속 영향 없음.)
|
||||
* - gameId가 없는 일정 엔트리는 스킵. 더블헤더 등으로 gameId가 중복되면 마지막 값으로 합친다.
|
||||
*
|
||||
* @returns upsert한 문서 개수
|
||||
* @returns 실제로 write가 일어난 문서 개수
|
||||
*/
|
||||
export async function syncGamesForMonth(year: number, month: number): Promise<number> {
|
||||
const result = await fetchScheduleFromKbo({ year, month });
|
||||
const batch = firestore.batch();
|
||||
let count = 0;
|
||||
|
||||
// gameId 기준 dedupe (마지막 엔트리 우선) — 순차 set의 "last wins"와 동일.
|
||||
const byGameId = new Map<string, Game>();
|
||||
for (const g of result.games) {
|
||||
const doc = toGameDoc(year, g);
|
||||
if (!doc) continue;
|
||||
batch.set(firestore.collection(COLLECTION).doc(g.gameId as string), doc, {
|
||||
merge: true,
|
||||
});
|
||||
count++;
|
||||
byGameId.set(g.gameId as string, doc);
|
||||
}
|
||||
if (byGameId.size === 0) return 0;
|
||||
|
||||
const entries = [...byGameId.entries()];
|
||||
const refs = entries.map(([gameId]) => firestore.collection(COLLECTION).doc(gameId));
|
||||
const snaps = await firestore.getAll(...refs);
|
||||
|
||||
const batch = firestore.batch();
|
||||
let count = 0;
|
||||
snaps.forEach((snap, i) => {
|
||||
const [gameId, doc] = entries[i];
|
||||
const existing = snap.exists ? (snap.data() as Partial<Game>) : null;
|
||||
if (existing && gameDocUnchanged(existing, doc)) return;
|
||||
batch.set(firestore.collection(COLLECTION).doc(gameId), doc, { merge: true });
|
||||
count++;
|
||||
});
|
||||
|
||||
if (count > 0) await batch.commit();
|
||||
return count;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user