Fold rank snapshot into the daily judgment transaction (W4)
dailyArchive wrote users/{uid} twice per cron run: once for rankSnapshot
and once for the judgment. Extract the read-only computeRankSnapshot, then
pass the pre-judgment snapshot into judgeDay so applyDailyJudgmentTx merges
it into the same patch as the judgment write. The snapshot is computed from
current (pre-judgment) tierPoints before the transaction, preserving the
"rank BEFORE judgment" semantics; on the idempotent guard-skip path no
write occurs (and stale re-snapshotting is avoided). snapshotRankForUser is
kept as a thin wrapper for its existing callers/tests.
This commit is contained in:
parent
6fc00b2952
commit
38a2f78647
@ -191,6 +191,12 @@ export async function applyDailyJudgmentTx(
|
||||
* 판정 분기를 적용한다. 휴장일은 제외하고 판단되어 들어온다.
|
||||
*/
|
||||
streakBrokenIn: boolean;
|
||||
/**
|
||||
* 판정 직전(=현재 tierPoints 기준) rank 스냅샷. 제공되면 동일 트랜잭션 patch에
|
||||
* `rankSnapshot`으로 함께 기록해 별도 write를 절약한다. "판정 전 rank" 의미를
|
||||
* 보존하려면 호출자가 트랜잭션 호출 전에 계산해 넘겨야 한다.
|
||||
*/
|
||||
rankSnapshot?: RankSnapshot;
|
||||
computePoints: (streakAfter: number) => number;
|
||||
}
|
||||
): Promise<DailyJudgmentResult> {
|
||||
@ -241,6 +247,8 @@ export async function applyDailyJudgmentTx(
|
||||
},
|
||||
lastJudgedDate: date,
|
||||
};
|
||||
// 판정 전 rank 스냅샷을 같은 patch에 합쳐 user doc write를 1회로 줄인다.
|
||||
if (input.rankSnapshot) patch.rankSnapshot = input.rankSnapshot;
|
||||
tx.set(ref, patch, { merge: true });
|
||||
|
||||
return {
|
||||
|
||||
@ -6,13 +6,13 @@ import { invalidateStats } from "../services/statsService";
|
||||
import { judgeDay, judgeWeekIfNeeded } from "../services/judgmentService";
|
||||
import {
|
||||
precomputeScoreboardCache,
|
||||
snapshotRankForUser,
|
||||
computeRankSnapshot,
|
||||
} from "../services/rankSnapshotService";
|
||||
import { todayKst } from "../types/dateString";
|
||||
import { getGame, createGameDayCache } from "../repositories/gameRepository";
|
||||
import { deleteUserVoteGame } from "../repositories/voteRepository";
|
||||
import { processGameEndWithGame } from "../services/gameResultService";
|
||||
import type { VoteHistoryDoc } from "../types/panit";
|
||||
import type { RankSnapshot, VoteHistoryDoc } from "../types/panit";
|
||||
import {
|
||||
dayOfWeekKst,
|
||||
daysAgoKst,
|
||||
@ -131,16 +131,18 @@ export async function runDailyArchive(
|
||||
|
||||
// 리컨실 결과 모든 경기가 cancelled로 제거된 경우에도 skip 판정은 남겨
|
||||
// 스트릭 유지 로직이 동작하도록 judgeDay를 호출한다.
|
||||
// 판정 전에 현재 rank를 rankSnapshot에 기록 — 판정 후 변화량을 delta로 노출하기 위함.
|
||||
// 판정 전 rank를 계산해(=현재 tierPoints 기준) judgeDay 트랜잭션에 함께 기록한다.
|
||||
// 판정 트랜잭션과 같은 patch로 묶여 user doc write가 1회로 준다("판정 전 rank" 보존).
|
||||
let rankSnapshot: RankSnapshot | null = null;
|
||||
try {
|
||||
await snapshotRankForUser(uid, date);
|
||||
rankSnapshot = await computeRankSnapshot(uid, date);
|
||||
} catch (err) {
|
||||
logger.error(`snapshotRank failed uid=${uid} date=${date}`, err);
|
||||
logger.error(`computeRankSnapshot failed uid=${uid} date=${date}`, err);
|
||||
}
|
||||
// voteHistory 기록은 judgeDay가 1회 수행한다(판정 필드 포함). 별도 선기록은 생략.
|
||||
// judgeDay가 doc을 영속화한 뒤에야 userVotes를 제거해 데이터 유실을 막는다.
|
||||
try {
|
||||
await judgeDay(uid, date, { data }, gameCache);
|
||||
await judgeDay(uid, date, { data }, { gameCache, rankSnapshot });
|
||||
} catch (err) {
|
||||
logger.error(`judgeDay failed uid=${uid} date=${date}`, err);
|
||||
// 판정 실패 시에도 data만은 보존(기존 동작 유지) — userVotes를 곧 지우기 때문.
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
streakBonus,
|
||||
thresholdsFor,
|
||||
} from "../constants/judgment";
|
||||
import type { DailyJudgment, VoteHistoryDoc } from "../types/panit";
|
||||
import type { DailyJudgment, RankSnapshot, VoteHistoryDoc } from "../types/panit";
|
||||
import { addDaysKst, tuesdayOf, type DateString } from "../types/dateString";
|
||||
|
||||
/**
|
||||
@ -54,14 +54,16 @@ export async function hasMissedGameDayBetween(
|
||||
* @param uid - 유저 ID
|
||||
* @param date - 판정 대상 날짜 (KST)
|
||||
* @param voteDoc - 해당 날짜의 voteHistory 도큐먼트 내용(판정 필드 미포함)
|
||||
* @param opts.gameCache - 여러 유저를 처리할 때 날짜별 games read를 공유하는 캐시
|
||||
* @param opts.rankSnapshot - 판정 직전 rank 스냅샷. 제공 시 판정 트랜잭션에 함께 기록한다.
|
||||
*/
|
||||
export async function judgeDay(
|
||||
uid: string,
|
||||
date: DateString,
|
||||
voteDoc: VoteHistoryDoc,
|
||||
gameCache?: GameDayCache
|
||||
opts?: { gameCache?: GameDayCache; rankSnapshot?: RankSnapshot | null }
|
||||
): Promise<void> {
|
||||
const fetch = gameCache ?? createGameDayCache();
|
||||
const fetch = opts?.gameCache ?? createGameDayCache();
|
||||
const games = await fetch.listByDate(date);
|
||||
const completedCount = games.filter((g) => g.status === "completed").length;
|
||||
const threshold = thresholdsFor(completedCount);
|
||||
@ -90,6 +92,7 @@ export async function judgeDay(
|
||||
correctCount,
|
||||
completedCount,
|
||||
streakBrokenIn,
|
||||
rankSnapshot: opts?.rankSnapshot ?? undefined,
|
||||
computePoints: (streakAfter) => correctCount * 10 + streakBonus(streakAfter),
|
||||
});
|
||||
|
||||
|
||||
@ -23,14 +23,14 @@ import type {
|
||||
*
|
||||
* `tierPoints === 0`이면 스냅샷을 남기지 않는다.
|
||||
*/
|
||||
export async function snapshotRankForUser(
|
||||
export async function computeRankSnapshot(
|
||||
uid: string,
|
||||
date: DateString
|
||||
): Promise<void> {
|
||||
): Promise<RankSnapshot | null> {
|
||||
const user = await getUser(uid);
|
||||
if (!user) return;
|
||||
if (!user) return null;
|
||||
const tierPoints = user.tierPoints ?? 0;
|
||||
if (tierPoints <= 0) return;
|
||||
if (tierPoints <= 0) return null;
|
||||
|
||||
const overallAbove = await countUsersAboveTierPoints(tierPoints);
|
||||
const snapshot: RankSnapshot = {
|
||||
@ -47,6 +47,15 @@ export async function snapshotRankForUser(
|
||||
snapshot.teamCode = user.favoriteTeamCode;
|
||||
}
|
||||
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
export async function snapshotRankForUser(
|
||||
uid: string,
|
||||
date: DateString
|
||||
): Promise<void> {
|
||||
const snapshot = await computeRankSnapshot(uid, date);
|
||||
if (!snapshot) return;
|
||||
await firestore
|
||||
.collection("users")
|
||||
.doc(uid)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user