Files
OJ2/docker/clear-sessions.sh
yuetsh 01d7924faa ops(会话): 加一次性脚本清掉全部会话,闭掉存量会话吊销不掉的空窗
反向索引 user-sessions:<uid> 是 498fc1c 才加的,在那之前签发的会话不在索引里,
revokeUserSessions 靠 SMEMBERS 找不到它们 —— 改密码、重置密码、禁用账号对这批会话
统统无效,只能等最长一个 SESSION_TTL_SECONDS(默认 7 天)自然过期。学生密码是明文
存着给老师查的,改密码正是密码泄露后唯一的补救手段,这个空窗不留。代价是所有人重新
登录一次。跑过一次就不用再跑,此后签发的会话都带索引。

**两个站点都要跑。** 机房和服务器共用一个数据库,但各有各的 Redis,会话存在各自的
Redis 里,只清一边等于只解决一半。脚本结尾会提醒这件事。

只删 session:* 和 user-sessions:*,不用 FLUSHALL:同一个 Redis 里还装着 BullMQ 的
判题队列,清掉等于把还在队列里的提交全丢了,那些 submission 会永远停在 PENDING。
删完会核对 bull:* 的键数没变,变了就报错让人工检查。

扫描用 SCAN 不用 KEYS(KEYS 会阻塞住整个 Redis,而这上面还挂着所有人的会话读写),
xargs 分批 DEL 避免顶到命令行长度上限。

拿一次性容器验过:1200 条 session + 50 条索引全删(跨过分批边界),30 个 bull 键和
throttling 桶原样不动;空库上重跑是幂等的;容器名写错时报错退出不做任何事。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XvmqDsZNyUo9P3sFQtoWVB
2026-09-07 07:45:47 -06:00

79 lines
3.3 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
#
# 一次性运维动作:清掉所有会话,强制全员重新登录。
#
# ## 为什么要跑
#
# 会话的反向索引 `user-sessions:<uid>` 是 498fc1c 才加的。在那之前签发的会话不在索引
# 里revokeUserSessions 靠 SMEMBERS 找不到它们 —— 也就是说**改密码、重置密码、禁用
# 账号对这批会话统统无效**,只能等最长一个 SESSION_TTL_SECONDS默认 7 天)自然过期。
#
# 学生密码是明文存着给老师查的,改密码正是密码泄露之后唯一的补救手段,这个空窗不能留。
# 代价只是所有人重新登录一次。
#
# 跑过一次就不用再跑了:此后签发的会话都带索引。
#
# ## 两个站点都要跑
#
# 机房和服务器**共用一个数据库,但各有各的 Redis**(见 compose.school.yml 头部)。
# 会话存在各自的 Redis 里,只清一边等于只解决一半。
#
# ## 为什么不是 FLUSHALL
#
# 同一个 Redis 里还装着 BullMQ 的判题队列(`bull:*`。FLUSHALL 会把还在队列里的提交
# 一起丢掉,那些 submission 会永远停在 PENDING只能超管逐条重判 —— 而重判还会把
# user_profile 的反范式计数带偏(见 apps/api/src/scripts/recount.ts
# 这里只删 `session:*` 和 `user-sessions:*`。
#
# ## 用法
#
# docker/clear-sessions.sh # 容器名默认 oj-redis
# CONTAINER=oj2-redis docker/clear-sessions.sh # 本机 dev
# YES=1 docker/clear-sessions.sh # 跳过确认
#
set -euo pipefail
CONTAINER="${CONTAINER:-oj-redis}"
say() { printf '\n\033[1;36m==> %s\033[0m\n' "$*"; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
die() { printf '\n\033[1;31m❌ %s\033[0m\n\n' "$*" >&2; exit 1; }
docker exec "$CONTAINER" redis-cli ping >/dev/null 2>&1 \
|| die "连不上容器 $CONTAINER 里的 redis用 CONTAINER=... 指定容器名)"
# 用 SCAN 而不是 KEYSKEYS 会阻塞住整个 Redis而这上面还挂着判题队列和所有人的
# 会话读写。xargs 分批是因为单条 DEL 传太多 key 会顶到命令行长度上限;每批 DEL 返回
# 删掉的个数,累加起来就是总数。
purge() {
docker exec "$CONTAINER" redis-cli --scan --pattern "$1" 2>/dev/null \
| xargs -r -n 400 docker exec "$CONTAINER" redis-cli del \
| awk '{ sum += $1 } END { print sum + 0 }'
}
count() { docker exec "$CONTAINER" redis-cli --scan --pattern "$1" 2>/dev/null | wc -l; }
say "容器 $CONTAINER"
before_sessions=$(count 'session:*')
before_index=$(count 'user-sessions:*')
before_bull=$(count 'bull:*')
printf ' session:* %s\n' "$before_sessions"
printf ' user-sessions:* %s\n' "$before_index"
printf ' bull:* %s不动\n' "$before_bull"
if [ "${YES:-}" != "1" ]; then
read -rp $'\n 删掉上面的会话、让所有人重新登录?[y/N] ' answer
[ "$answer" = "y" ] || [ "$answer" = "Y" ] || die "已取消,什么都没做"
fi
say "清理"
ok "session:* 删掉 $(purge 'session:*')"
ok "user-sessions:* 删掉 $(purge 'user-sessions:*')"
after_bull=$(count 'bull:*')
[ "$after_bull" = "$before_bull" ] \
|| die "判题队列的键数变了($before_bull$after_bull),这不该发生,请人工检查"
ok "bull:* 仍是 $after_bull 个,判题队列没被动过"
say "完成 —— 另一个站点的 Redis 也要跑一遍"