perf(排名): 一个班同时打开排名、班级对抗、按人数排的题目列表时只查一次库

这几样谁来看都一样,原来每次请求现算:全服全部时间的排名本机 16 核就要 250ms,
服务器两核还要判题。services/cache.ts 进程内缓存 10 秒,同时到达的相同请求等同一个结果:
- 排名榜面的原始数据(「我」排第几、前后一名照旧每次现算):250ms → 6ms
- 班级对抗 / 详情 / PK 共用的这学期数据:40ms → 1ms
- 题目列表按做对、做过人数排序的全表人数:45ms → 20ms
40 个人同时打开全服排名(缓存是冷的)一共 342ms。代价是做对之后最多晚 10 秒上榜。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-16 14:47:24 +08:00
1 parent 307b0725d0
commit fb11e86151
5 files changed
+66 -4

No files matched your search

+19 -2
View File
@@ -29,6 +29,7 @@ import { db, schema } from "../db"
import { JudgeStatus } from "../judge/status"
import { sampleUser, rounded } from "../routes/helpers"
import { termStart, weekStart } from "../time"
import { cached } from "./cache"
/**
* 排名页和班级详情、班级 AI 分析共用的口径(routes/ranking.ts、routes/ai.ts):
@@ -173,7 +174,20 @@ const PLATEAU_MIN = 5
/** 班级详情的每周人均列最近几周 */
const DETAIL_WEEKS = 6
/** 有班级的学生,这学期的做对时刻 —— 班级对抗和班级详情都从这一份算 */
/**
* 排名、班级对抗这些「谁来看都一样」的数据缓存多久(services/cache.ts)。10 秒:
* 一个班同时打开时只查一次;学生做对一道题马上去看排名,最多晚 10 秒看到。
*/
const SHARED_TTL = 10_000
/** 排名榜面那群人的做对时刻:同一个范围、同一个时间段谁来看都一样,「我」的部分由调用方现算 */
export function rankEntrants(scope: RankScope, group: string | null, since: string | null) {
return cached(`rank-entrants:${scope}:${group}:${since}`, SHARED_TTL, () =>
loadEntrants(audienceWhere(scope, group), since),
)
}
/** 有班级的学生,这学期的做对时刻 —— 班级对抗、班级详情、班级 PK 都从这一份算 */
async function classedEntrants() {
const term = termStart()
const where = and(
@@ -181,7 +195,10 @@ async function classedEntrants() {
isNotNull(schema.user.className),
ne(schema.user.className, ""),
)!
return { term, entrants: await loadEntrants(where, term) }
return {
term,
entrants: await cached(`classed-entrants:${term}`, SHARED_TTL, () => loadEntrants(where, term)),
}
}
function battleFrom(entrants: Entrant[]) {