diff --git a/src/scheduled/dailyArchive.ts b/src/scheduled/dailyArchive.ts index cd3ad62..2e6fe16 100644 --- a/src/scheduled/dailyArchive.ts +++ b/src/scheduled/dailyArchive.ts @@ -54,7 +54,8 @@ async function reconcileDayVotes( // result=true로 처리한다. if (game.status === "completed") { try { - await processGameEndWithGame(gameId, game); + // 아카이브는 루프 끝에서 유저 단위로 1회 무효화하므로 경기별 fan-out은 생략. + await processGameEndWithGame(gameId, game, { skipInvalidate: true }); const isDraw = !game.winningTeamCode; result[gameId] = { team: vote.team, diff --git a/src/services/gameResultService.ts b/src/services/gameResultService.ts index 37cdf3a..3b3cfc4 100644 --- a/src/services/gameResultService.ts +++ b/src/services/gameResultService.ts @@ -16,7 +16,8 @@ export async function processGameEnd(gameId: string): Promise<{ processed: numbe export async function processGameEndWithGame( gameId: string, - game: Game + game: Game, + opts?: { skipInvalidate?: boolean } ): Promise<{ processed: number }> { if (game.status !== "completed") throw new HttpError(409, `game status must be completed, got ${game.status}`); @@ -39,7 +40,11 @@ export async function processGameEndWithGame( await deleteGameVotes(gameId); - await Promise.all(uids.map((uid) => invalidateStats(uid).catch(() => undefined))); + // 아카이브 리컨실 경로에선 호출자(dailyArchive)가 유저 단위로 1회 무효화하므로 + // 경기마다 투표자 전원을 다시 무효화하는 fan-out을 건너뛴다(중복 제거). + if (!opts?.skipInvalidate) { + await Promise.all(uids.map((uid) => invalidateStats(uid).catch(() => undefined))); + } logger.info(`processGameEnd: ${gameId} processed ${uids.length} votes`); return { processed: uids.length };