feat(运维): 加 oj2-api recount,把反范式计数列对回 submission 表
Some checks failed
Deploy / deploy (push) Has been cancelled

problem.submission_number / accepted_number / statistic_info 和 user_profile 的
submission_number / accepted_number / acm_problems_status 由 judge/run.ts 的
persistResult 在判题时手工加减,上线至今没人事后核对过。

已知的漂移来源是重判:routes/submission.ts 的 rejudge 把 result 打回 PENDING
就重新入队,不回退任何计数,persistResult 随后再加一次。实测重判一条提交,
problem 的 submission_number、statistic_info 和 user_profile 的 submission_number
全部虚增,而提交一条没多。

默认只读预演,--apply 才写,落库后用同一份 computePlan 复核,还剩差异就非零退出。
口径逐条照抄 persistResult:题目侧连比赛提交一起算、用户侧只算非比赛;
accepted_number 是去重到题的首次通过;acm_problems_status 通过过就恒为 ACCEPTED,
没通过过取最后一次结果。acm_problems_status 里 problems / contest_problems 之外
的顶层键原样保留 —— 来历不明的数据不该被重算顺手抹掉。

不管的:acm_contest_rank(罚时与每题尝试次数口径复杂,单独一件事)、
achievement.unlock_count(0010 之后随成就级联,漂不了)、题单进度与奖章
(走 backfill-problemsets)。

--apply 要挑没人做题的时候跑:差异在事务外算、写的是绝对值,算完到写完之间判完
的那一笔加法会被覆盖;复核会把它报成「仍有 N 处差异」并以 1 退出,不会静默。

验证:dev 库先备份计数列,测完原样还原(差异数 0)。验过收敛(--apply 后再跑报
一致)、真实重判造成的漂移精确报出 3 处且无误报、人为删掉某学生已 AC 的格子能按
「曾经 AC → ACCEPTED」恢复、注入的未知顶层键完好保留。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AeJoYc2t2d7cThVqMBYrBF
This commit is contained in:
2026-09-02 22:58:01 -06:00
parent 2c4d56b29a
commit fafeebd281
2 changed files with 293 additions and 1 deletions

View File

@@ -13,6 +13,7 @@
* oj2-api sql-child # SQL 判题子进程,由服务自己 spawn不该手动调
* oj2-api migrate # 执行待办的数据库迁移,部署时由 docker/deploy.sh 调
* oj2-api backfill-problemsets # 把题单进度与奖章订正到与规则一致,默认只读预演
* oj2-api recount # 把题目/用户的计数列重算回与 submission 一致,默认只读预演
*
* 用动态 import 而非顶层 import这几个模块都有导入即执行的副作用
* Bun.serve、连 Redis 开消费者),静态导入会让 sql-child 也把整个服务拉起来。
@@ -43,6 +44,11 @@ switch (command) {
allowRevoke: args.includes("--allow-revoke"),
}))
}
// 同上,一次性的数据订正。反范式计数列被重判等操作带偏之后拿它对账。
case "recount": {
const { recount } = await import("./scripts/recount")
process.exit(await recount({ apply: process.argv.slice(3).includes("--apply") }))
}
case "sql-child": {
const { runSqlChild } = await import("./judge/sql/child")
await runSqlChild()
@@ -63,6 +69,6 @@ switch (command) {
}
}
default:
console.error(`未知子命令:${command}\n可用serve | worker | migrate | backfill-problemsets | healthcheck | sql-child`)
console.error(`未知子命令:${command}\n可用serve | worker | migrate | backfill-problemsets | recount | healthcheck | sql-child`)
process.exit(2)
}