Field-mask the scoreboard self-user read (R5)

getScoreboard loaded the full user doc just to use 5 fields. Add
getUserForScoreboard using getAll with a fieldMask so only displayName,
photoUrl, tierPoints, favoriteTeamCode, and rankSnapshot are fetched,
cutting per-request bandwidth. The short-lived me-rank cache (meRankCache,
30s) already covers the rank-count side of R5.
This commit is contained in:
윤정민 2026-05-28 17:34:16 +09:00
parent fa7767d172
commit f0b01a2dc0
2 changed files with 40 additions and 2 deletions

View File

@ -36,6 +36,44 @@ export async function getUser(uid: string): Promise<User | null> {
return snap.exists ? (snap.data() as User) : null; return snap.exists ? (snap.data() as User) : null;
} }
/** 스코어보드 "me" 계산에 필요한 최소 필드만 담는 형태. */
export interface ScoreboardSelfUser {
displayName: string;
photoUrl?: string;
tierPoints?: number;
favoriteTeamCode?: TeamCode;
rankSnapshot?: RankSnapshot;
}
/**
* "me" fieldMask로 .
* user doc(streak//notifications ) 5 bandwidth를 .
*
* @returns . `null`.
*/
export async function getUserForScoreboard(
uid: string
): Promise<ScoreboardSelfUser | null> {
const ref = firestore.collection(COLLECTION).doc(uid);
const [snap] = await firestore.getAll(ref, {
fieldMask: [
"displayName",
"photoUrl",
"tierPoints",
"favoriteTeamCode",
"rankSnapshot",
],
});
if (!snap.exists) return null;
const data = snap.data() as Partial<User>;
const self: ScoreboardSelfUser = { displayName: data.displayName ?? "" };
if (data.photoUrl) self.photoUrl = data.photoUrl;
if (data.tierPoints !== undefined) self.tierPoints = data.tierPoints;
if (data.favoriteTeamCode) self.favoriteTeamCode = data.favoriteTeamCode;
if (data.rankSnapshot) self.rankSnapshot = data.rankSnapshot;
return self;
}
export interface ScoreboardUserEntry { export interface ScoreboardUserEntry {
uid: string; uid: string;
displayName: string; displayName: string;

View File

@ -1,7 +1,7 @@
import { MemCache } from "../lib/memCache"; import { MemCache } from "../lib/memCache";
import { import {
countUsersAboveTierPoints, countUsersAboveTierPoints,
getUser, getUserForScoreboard,
} from "../repositories/userRepository"; } from "../repositories/userRepository";
import { import {
readScope, readScope,
@ -51,7 +51,7 @@ export async function getScoreboard(
uid: string, uid: string,
type: ScoreboardType type: ScoreboardType
): Promise<ScoreboardResponse> { ): Promise<ScoreboardResponse> {
const user = await getUser(uid); const user = await getUserForScoreboard(uid);
if (!user) throw new HttpError(404, `user not found: ${uid}`); if (!user) throw new HttpError(404, `user not found: ${uid}`);
const teamCode = type === "team" ? user.favoriteTeamCode : undefined; const teamCode = type === "team" ? user.favoriteTeamCode : undefined;