Add getAll+diff to syncGamesForMonth to skip no-op writes

- KBO에서 가져온 경기 일정을 기존 DB 데이터와 비교해 변경된 항목만 Firestore에 저장
- 변경 없는 경기의 반복 write를 제거해 월간 write 비용 대폭 감소
This commit is contained in:
윤정민 2026-05-28 17:19:17 +09:00
parent e76b752a41
commit 12f21803bc

View File

@ -52,26 +52,63 @@ export function toGameDoc(year: number, g: ScheduleGame): Game | null {
return doc; 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한다. * · KBO fetch하여 Firestore `games` upsert한다.
* *
* - `merge: true` (: 외부에서 `winningTeamCode`) . * - `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> { export async function syncGamesForMonth(year: number, month: number): Promise<number> {
const result = await fetchScheduleFromKbo({ year, month }); 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) { for (const g of result.games) {
const doc = toGameDoc(year, g); const doc = toGameDoc(year, g);
if (!doc) continue; if (!doc) continue;
batch.set(firestore.collection(COLLECTION).doc(g.gameId as string), doc, { byGameId.set(g.gameId as string, doc);
merge: true,
});
count++;
} }
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(); if (count > 0) await batch.commit();
return count; return count;
} }