什么时候看这份文档? 收到 Alertmanager/Prometheus 告警、前端出现异常 SSE error、eval gate 失败、或值班时需要快速定位故障根因时,从告警速查表找到对应场景直接跳转。
本 runbook 覆盖 raguide 生产(demo 量级 docker compose 单机部署)的 9 条告警规则、9 个故障场景及对应降级路径,是
infra/prometheus/rules.yml的配套操作手册。
| SLO | 目标 | 对应告警 |
|---|---|---|
| 可用性 | 99% | BackendDown |
| 错误率 | <5% | HighErrorRate |
| 检索 p95 | <10s | HighRetrievalP95 |
| 告警名 | 级别 | 含义 | 场景锚 |
|---|---|---|---|
| BackendDown | critical | 后端 /metrics 不可达超过 1 分钟,SLO 可用性破坏 |
场景1 |
| HighErrorRate | critical | 请求错误率 >5% 持续 5 分钟,最常见原因是 LLM Provider 上游异常 | 场景1 |
| HighRetrievalP95 | warning | 检索 p95 延迟 >10s 持续 5 分钟,Qdrant 或 TEI 异常 | 场景2 |
| TokenSpike | warning | token 消耗速率异常飙升(>2000 tokens/s 滑动均值),可能触发成本熔断 | 场景6 |
| SafetyCapTriggered | warning | 工具循环安全帽 10 分钟内触发 >3 次,Agent 行为异常信号 | 场景7 |
| CRAGFallbackSurge | warning | CRAG SearXNG 兜底激增(15 分钟 >5 次),检索质量劣化信号 | 场景2 |
| LiveQualityDrop | warning | 线上 done_hook judge 加权分 <0.6 持续 15 分钟,输出质量漂移 | 场景7 |
| CostBudget80 | warning | 本月成本已达预算 80%,需关注是否需要调整预算 | 场景6 |
| CostBudgetExhausted | critical | 本月成本已超预算,服务已熔断,前端收到 BUDGET_EXHAUSTED SSE error |
场景6 |
触发告警: BackendDown、HighErrorRate
- Prometheus alerts 页(
localhost:9099/alerts)出现 HighErrorRate / BackendDown firing - 后端日志出现
LLMError/UPSTREAM_LLM_ERROR关键字 - 前端对话无响应或持续收到 error SSE
# 1. 检查后端健康端点
curl -s localhost:8000/healthz
# 2. 查看后端日志,过滤 LLM 错误
# 后端日志 = 运行 uvicorn 的终端;若用 nohup/重定向则:
grep -E "LLMError|UPSTREAM_LLM_ERROR|error" <你的日志文件>
# 3. 检查 Prometheus 错误率指标
curl -s 'localhost:9099/api/v1/query?query=sum(rate(raguide_trace_status_total{status="error"}[5m]))/sum(rate(raguide_trace_status_total[5m]))'- 登录 admin「模型设置」面板(web 侧栏 →「模型设置」对话框,无独立路由),切换至备用 Provider(M7 已落地:AnthropicAdapter / ConfigurableOpenAIAdapter / DeepSeekAdapter 三选)
- 若 Provider 密钥过期,在
.env更新对应 API Key 后重启后端:Ctrl-C 终止 uvicorn 进程,再执行cd apps/backend && uv run uvicorn raguide_backend.main:app --reload --port 8000 - 若为短暂上游抖动,等待 FallbackChain 自动恢复(见降级路径)
FallbackChain(apps/backend/src/raguide_backend/llm/registry.py,M2a):Provider 列表按优先级排列,当前 adapter 连续失败时自动 fallover 至下一个 adapter,无需人工干预。FallbackChain 遵循 Chain of Responsibility 模式,错误不会上浮直至所有 adapter 均失败。
# 演练场景1:将 .env 中的 LLM API Key 设为无效值 → 看告警 → 恢复
# 注意:仅在 dev 环境演练,勿在有真实流量时操作
# 模拟步骤:
# 1. 备份并临时替换 API Key
# 2. 重启后端: Ctrl-C 终止 uvicorn 进程后重新运行
# cd apps/backend && uv run uvicorn raguide_backend.main:app --reload --port 8000
# 3. 发一条测试查询,观察 localhost:9099/alerts HighErrorRate 进 pending→firing
# 4. 后端终端输出应出现: ops.alert firing name=HighErrorRate (webhook 链路通)
# 5. 恢复 API Key 后重启后端触发告警: HighRetrievalP95、CRAGFallbackSurge
- 检索 p95 延迟突破 10s 告警
- CRAG SearXNG 兜底频率激增(Qdrant dense 路失败→兜底 Web 搜索)
- 前端查询响应明显变慢
# 1. 查看 Qdrant 容器日志
docker logs raguide-qdrant --tail 50
# 2. Qdrant 健康检查
curl -s localhost:6333/healthz
# 3. 检查容器状态
docker ps | grep raguide-qdrant
# 4. 检索延迟 p95 当前值
curl -s 'localhost:9099/api/v1/query?query=histogram_quantile(0.95,sum+by+(le)(rate(raguide_retrieval_latency_seconds_bucket[5m])))'# 重启 Qdrant 容器
docker restart raguide-qdrant
# 等待健康检查通过(通常 10-30s)
until curl -sf localhost:6333/healthz; do sleep 2; done && echo "Qdrant ready"三路 fanout 容错(apps/backend/src/raguide_backend/retrieval/concurrent_fanout.py):dense(Qdrant)/ BM25(Tantivy)/ Image 三路并发,任意一路失败不阻塞其他两路结果。Qdrant 挂时 BM25 稀疏检索仍提供结果,RRF fusion 对缺失路自动降权。CRAG SearXNG 作最终兜底。
# 演练场景2:停 Qdrant → 看告警 → 恢复
docker stop raguide-qdrant
# localhost:9099/alerts 等 HighRetrievalP95/CRAGFallbackSurge 进 pending→firing
# 后端日志应出现: ops.alert firing name=... (webhook 链路通)
docker start raguide-qdrant触发告警: 后端日志告警(无独立 Prometheus 规则;Redis 故障 fail-open 不产生错误率告警)
- 后端日志出现
cost_guard.add fail-open或cost_guard.spend fail-open - 成本 Prometheus Gauge(
raguide_cost_monthly_cny)归零或停止更新 - 注意:Redis 挂时服务不中断(fail-open 设计),用户无感知
# 1. 测试 Redis 连通性
docker exec raguide-redis redis-cli ping
# 2. 查看 Redis 容器日志
docker logs raguide-redis --tail 50
# 3. 检查容器状态
docker ps | grep raguide-redis
# 4. 查看后端 fail-open 日志
# 后端日志 = 运行 uvicorn 的终端;若用 nohup/重定向则:
grep "cost_guard" <你的日志文件># 重启 Redis 容器
docker restart raguide-redis
# 验证恢复
docker exec raguide-redis redis-cli ping
# 预期输出: PONG
# 验证月成本 key(Redis 恢复后新写入开始,历史少记无法追回)
docker exec raguide-redis redis-cli GET "raguide:cost:monthly:$(date +%Y-%m)"CostGuard.add / CostGuard.monthly_spend 均以 fail-open 模式处理 Redis 异常(apps/backend/src/raguide_backend/billing/cost_guard.py):异常时仅 logger.warning,服务继续正常运行,计费暂停累计。Redis 连接超时 2s,不拖慢后端启动。
注意: Redis 挂期间的 LLM 调用成本不会被记录,恢复后月累计会偏低。如需准确计费,管理员可参考 Langfuse trace 中的 token 消耗手动补偿预算。
# 演练场景3:停 Redis → 确认 fail-open 日志 → 恢复
docker stop raguide-redis
# 发一条聊天查询
# 后端日志应出现: cost_guard.add fail-open / cost_guard.spend fail-open
# 服务仍正常响应(fail-open 设计)
docker start raguide-redis触发告警: CRAGFallbackSurge(间接),检索结果变稀(无独立 Prometheus 规则)
- 后端日志出现 Tantivy 相关错误
- 检索结果数量明显减少(BM25 稀疏路缺失,仅剩 dense/image)
- CRAGFallbackSurge 告警(检索质量下降→兜底频率上升)
# 1. 查看 Tantivy 容器日志
docker logs raguide-tantivy --tail 50
# 2. 检查容器状态
docker ps | grep raguide-tantivy# 重启 Tantivy 容器
docker restart raguide-tantivy
# 若重启无效(索引损坏),需重建索引:
# M6 经验:重置索引目录后通过 KB 管理页全量重摄入
# 1. 停容器
docker stop raguide-tantivy
# 2. 清空索引数据(谨慎!不可逆)
# docker run --rm -v raguide_tantivy_data:/data alpine rm -rf /data/*
# 3. 重启容器(自动初始化空索引)
docker start raguide-tantivy
# 4. 在 KB 管理页(前端 /kb)触发全量重摄入,或通过 API 批量重摄三路 fanout 容错(apps/backend/src/raguide_backend/retrieval/concurrent_fanout.py):Tantivy 挂时自动降级为 dense-only 检索,BM25 路失败不阻塞 Qdrant dense 和 Image 路结果。RRF fusion 对缺失路自动降权,结果质量下降但服务可用。
# 演练场景4:停 Tantivy → 确认 dense-only 降级 → 恢复
docker stop raguide-tantivy
# 发一条检索查询,观察后端日志(BM25 路报错,但查询仍返回结果)
# CRAGFallbackSurge 可能进 pending(视检索结果质量)
docker start raguide-tantivy触发告警: HighRetrievalP95(间接,rerank 超时拉高 p95)
- 后端日志出现
TEI rerank timed out(10s 超时) - 检索 p95 延迟升高
- 响应中 evidence 顺序回退至 RRF fusion 原始排序
# 1. 查看 TEI 容器日志
docker logs raguide-tei --tail 50
# 2. 检查容器状态
docker ps | grep raguide-tei
# 3. 过滤后端 rerank 相关日志
# 后端日志 = 运行 uvicorn 的终端;若用 nohup/重定向则:
grep -iE "rerank|tei" <你的日志文件># 重启 TEI 容器(CPU 版镜像,启动需 30-60s 加载模型权重)
docker restart raguide-tei
# 等待模型加载完成
until docker logs raguide-tei 2>&1 | grep -q "Ready"; do sleep 5; done && echo "TEI ready"Reranker fallback(apps/backend/src/raguide_backend/retrieval/post_retrieval/reranker.py):TEI 返回空或超时时,自动取 RRF fusion 原始顺序的 top-5 结果(finding-017,M2c)。重排失败不阻断整个检索响应,仅损失重排质量提升。
# 演练场景5:停 TEI → 确认 fallback top-5 日志 → 恢复
docker stop raguide-tei
# 发一条需要检索的查询
# 后端日志应出现: Reranker: TEI returned empty; falling back to top-5 of fused order
docker start raguide-tei触发告警: CostBudget80(预警)、CostBudgetExhausted(已熔断)、TokenSpike(上游异常飙升)
- CostBudget80 firing:本月累计成本已达预算 80%,需关注
- CostBudgetExhausted firing:已超预算,所有新请求返回
BUDGET_EXHAUSTEDSSE error - 前端对话框收到错误提示:「本月 LLM 预算已用尽,服务暂停。请联系管理员在「模型设置→预算」调整。」
# 1. 查看当前预算配置
curl -s -H "X-Admin-Token: $ADMIN_TOKEN" localhost:8000/v1/admin/budget-config
# 2. 查看本月 Redis 累计成本(月 key 格式: raguide:cost:monthly:YYYY-MM)
docker exec raguide-redis redis-cli GET "raguide:cost:monthly:$(date +%Y-%m)"
# 3. 检查 Prometheus 成本 Gauge
curl -s 'localhost:9099/api/v1/query?query=raguide_cost_monthly_cny'
curl -s 'localhost:9099/api/v1/query?query=raguide_cost_budget_cny'
# 4. 查看 token 消耗速率(TokenSpike 时)
curl -s 'localhost:9099/api/v1/query?query=sum(rate(raguide_llm_tokens_total[5m]))'# 方案A: 调高月预算上限(PUT 即热生效,无需重启)
curl -s -X PUT \
-H "X-Admin-Token: $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"monthly_budget_cny": 200.0, "enabled": true}' \
localhost:8000/v1/admin/budget-config
# 方案B: 临时关闭熔断(紧急时)
curl -s -X PUT \
-H "X-Admin-Token: $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"monthly_budget_cny": 0.0, "enabled": false}' \
localhost:8000/v1/admin/budget-config
# 确认配置已生效(BudgetConfig 进程内缓存 PUT 路径同步更新,无需重启)
curl -s -H "X-Admin-Token: $ADMIN_TOKEN" localhost:8000/v1/admin/budget-config成本熔断本身是设计行为(apps/backend/src/raguide_backend/api/agent.py):超预算时返回单条 BUDGET_EXHAUSTED SSE error,零 LLM 调用,服务优雅拒绝。BudgetConfig 支持 PUT 热生效(apps/backend/src/raguide_backend/billing/budget_config.py),调整预算后立即恢复服务,无需重启。
# 演练场景6:设极低预算 → 发查询观察 BUDGET_EXHAUSTED → 恢复
# 步骤1: 设预算为 0.01 CNY(必触发)
curl -s -X PUT \
-H "X-Admin-Token: $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"monthly_budget_cny": 0.01, "enabled": true}' \
localhost:8000/v1/admin/budget-config
# 步骤2: 发一条聊天查询,前端应收到 BUDGET_EXHAUSTED error SSE
# 步骤3: localhost:9099/alerts 观察 CostBudgetExhausted 进 firing
# 步骤4: 恢复正常预算
curl -s -X PUT \
-H "X-Admin-Token: $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"monthly_budget_cny": 100.0, "enabled": true}' \
localhost:8000/v1/admin/budget-config触发告警: SafetyCapTriggered(行为异常)、LiveQualityDrop(质量漂移)
- eval CI 运行
pytest packages/eval或python tests/eval/run_gold_set.py返回 GATE FAILED - Prometheus
raguide_eval_dimension_score{dim="weighted"} < 0.6持续 15 分钟 tests/eval/_reports/目录下最新报告显示加权分低于 baseline
# 1. 优先排除机器睡眠污染(最常见的假阳性)
# 检查报告中 case_errors 数量
ls -lt tests/eval/_reports/ | head -5
# 读最新报告,检查 case_errors > 0 和日志时间线是否有大断层
# 2. 若有污染迹象(case_errors > 0 / 时间断层),用 caffeinate 重跑
caffeinate -is python tests/eval/run_gold_set.py
# 3. 无污染时,读报告失败表的 judge_reasoning 定位真实退化
cat tests/eval/_reports/$(ls -t tests/eval/_reports/ | head -1) | grep -A 5 "FAILED\|judge_reasoning"
# 4. 检查 Langfuse trace 中失败 case 的详细调用链
# Langfuse UI: localhost:3001
# 5. 检查 SafetyCapTriggered 对应的工具循环 trace
curl -s 'localhost:9099/api/v1/query?query=increase(raguide_safety_cap_triggered_total[10m])'原则:勿盲目使用 --force-baseline。--force-baseline 无条件覆盖 baseline,仅用于首次建基线或有意重置场景。--update-baseline 已在 GATE FAILED 时自动拒写。
-
机器睡眠污染(case_errors > 0 或时间线有大断层):
caffeinate -is python tests/eval/run_gold_set.py # 若重跑 GATE PASSED,用 --update-baseline 更新 baseline python tests/eval/run_gold_set.py --update-baseline -
真实退化(排除污染后仍 GATE FAILED):
- 读
judge_reasoning定位具体维度(task_completion/output_quality/process_quality/efficiency/safety) - 在 Langfuse trace 中复现失败 case 的调用链
- 修复对应组件后重跑评测
- 读
-
有意重置 baseline(架构重大变更后):
# 确认当前分数合理后再执行(不可逆!) python tests/eval/run_gold_set.py --force-baseline
eval gate 失败不影响生产服务本身,仅阻断 CI pipeline。--update-baseline 仅在 GATE PASSED 时写入,防止退化 baseline 被意外固化。线上质量漂移(LiveQualityDrop)由 done_hook LLM-as-Judge 实时监控,独立于 eval CI。
# 演练场景7:触发 LiveQualityDrop 告警
# (在 dev 环境)临时将 judge 评分阈值改低,或注入低质量响应
# 观察 localhost:9099/alerts LiveQualityDrop 进 pending→firing
# 恢复后告警应自动 resolve(15 分钟窗口)
# 验证 eval gate 保护:
python tests/eval/run_gold_set.py
# GATE FAILED 时确认 baseline 未被更新(--update-baseline 自动拒写)触发告警: 无独立告警,依赖前端观察和后端日志
- 首次查询(后端刚启动后)SSE 流提前断开,前端无 token 输出
- 后端日志未出现 embedder warm 完成标记
- 再次发送同一查询正常响应(冷启动一次性问题)
finding-027(M5):embedder(bge-m3 文本 + JinaCLIP-v2 图像)冷加载约需 23s,曾在首查询时阻塞事件循环导致 SSE 提前断开。现已修复:lifespan 后台预热 + 检索路径 off-thread(concurrent.futures 线程池),embedder 初始化不再阻塞 async 事件循环。
# 1. 检查后端启动日志,确认 embedder 预热完成时间
# 后端日志 = 运行 uvicorn 的终端;若用 nohup/重定向则:
grep -E "warm|embed|preload|startup" <你的日志文件>
# 2. 检查后端健康端点(预热未完成时可能返回 503)
curl -s localhost:8000/healthz
# 3. 查看 TEI 容器状态(embedding 依赖 TEI)
docker logs raguide-tei --tail 20 2>&1 | grep -E "Ready|Error|loaded"# 若首查询 SSE 中断(预热未完成):
# 等待 30-60s 后重试(预热为后台异步任务,不阻断健康检查)
# 若预热长时间未完成,重启后端触发重新预热:
# 重启后端: Ctrl-C 终止 uvicorn 进程后重新运行
# cd apps/backend && uv run uvicorn raguide_backend.main:app --reload --port 8000
# 监控预热进度(在 uvicorn 终端观察输出):
# 若用 nohup/重定向则: tail -f <你的日志文件> | grep -E "warm|embed|ready"当前实现为 lifespan 后台预热(非阻塞):后端启动即可接受请求,embedder 在后台异步加载;首查询若 embedder 未就绪,检索路径会等待就绪或走 CRAG SearXNG 兜底(视具体实现)。预热完成后所有后续查询不受影响。
# 演练场景8:模拟冷启动
# 重启后端,立即发送首条查询,观察 SSE 是否正常
# 重启后端: Ctrl-C 终止 uvicorn 进程后重新运行
# cd apps/backend && uv run uvicorn raguide_backend.main:app --reload --port 8000
# 等待 5s(不等预热完成)后立即发查询
# 后端终端输出应出现 embedder 预热相关日志
# 查询应能正常响应(lifespan 预热已修复 finding-027)触发告警: 无独立 Prometheus 规则;依赖前端登录失败提示和后端日志观察。
- 前端登录/注册返回 503
AUTH_DISABLED(业主无法登录拿 admin JWT) - 现有 Postgres 库升级后,登录/注册或评测候选审批报缺表 / 缺列错误(
users表或eval_candidate_cases.signals列不存在) - 注意:
AUTH_DISABLED不影响游客功能——匿名查询、对话、检索照常(统一身份回退X-Owner-Id)
JWT_SECRET由 env 配置:config.jwt_secret读 envJWT_SECRET。留空时/v1/auth/register与/v1/auth/login直接返回 503AUTH_DISABLED,登录态相关功能(业主面板等)不可用,但游客零回归。- 库升级幂等 DDL:
infra的init.sql只在 Postgres 数据卷首次初始化时执行;对已存在的库,建表/加列靠main.pylifespan 的幂等 DDL ——CREATE TABLE IF NOT EXISTS users+ALTER TABLE eval_candidate_cases ADD COLUMN IF NOT EXISTS signals JSONB(均 try/except 包裹,非致命,失败仅告警不阻断启动)。
# 1. 确认 JWT_SECRET 是否配置(只看是否为空,勿回显完整值到日志/截图)
grep -c '^JWT_SECRET=.\+' apps/backend/.env # 1 = 已配置非空, 0 = 缺失/为空
# 2. 复现 503 AUTH_DISABLED(JWT_SECRET 为空时)
curl -s -o /dev/null -w '%{http_code}\n' -X POST localhost:8000/v1/auth/login \
-H 'Content-Type: application/json' -d '{"username":"x","password":"y"}'
# 预期: JWT_SECRET 空 → 503; 已配置 → 401(防枚举的统一失败码)
# 3. 确认升级 DDL 已生效(users 表 + signals 列存在)
docker exec raguide-postgres psql -U raguide -d raguide -c "\d users"
docker exec raguide-postgres psql -U raguide -d raguide -c \
"SELECT column_name FROM information_schema.columns WHERE table_name='eval_candidate_cases' AND column_name='signals';"
# 4. 查后端启动日志,确认 lifespan 幂等 DDL 段
# 后端日志 = 运行 uvicorn 的终端;若用 nohup/重定向则:
grep -iE "users|signals|lifespan|DDL|migrate" <你的日志文件># AUTH_DISABLED: 在 .env 配置 JWT_SECRET 后重启后端(凭据只走 .env,永不入库/日志/回显)
# 1. 编辑 apps/backend/.env,设置 JWT_SECRET=<足够长的随机串>
# 2. 重启后端: Ctrl-C 终止 uvicorn 进程后重新运行
# cd apps/backend && uv run uvicorn raguide_backend.main:app --reload --port 8000
# 缺表/缺列: 重启后端触发 lifespan 幂等 DDL(对已存在库 ADD COLUMN/CREATE TABLE IF NOT EXISTS)
# 若 lifespan DDL 因权限等被 try/except 吞掉,手动补:
docker exec raguide-postgres psql -U raguide -d raguide -c \
"ALTER TABLE eval_candidate_cases ADD COLUMN IF NOT EXISTS signals JSONB;"JWT_SECRET 缺失只熔断登录态功能(register/login → 503 AUTH_DISABLED),统一身份层对游客回退 X-Owner-Id,匿名访问零回归。lifespan 幂等 DDL 的 try/except 非致命:DDL 失败不阻断后端启动,只是登录/候选审批相关功能不可用,修复后重启即恢复。
JWT_SECRET 与各 provider API Key 只走 .env,永不入库、永不进日志、永不回显(DB 仅存 env 变量名)。诊断时只检查 secret 是否为空,勿打印完整值,勿提交到 git。
| 服务 | 宿主端口 | 容器端口 | 容器名 |
|---|---|---|---|
| 后端 FastAPI | 8000 | — | 宿主进程(uvicorn) |
| Prometheus | 9099 | 9090 | raguide-prometheus |
| Alertmanager | 9093 | 9093 | raguide-alertmanager |
| Grafana | 3002 | 3000 | raguide-grafana |
| Langfuse | 3001 | 3000 | raguide-langfuse-web |
| Qdrant | 6333 | 6333 | raguide-qdrant |
| Redis | 6379 | 6379 | raguide-redis |
| PostgreSQL | 5432 | 5432 | raguide-postgres |
| SearXNG | — | — | raguide-searxng |
| MinIO | — | — | raguide-minio |
| Tantivy | — | — | raguide-tantivy |
| TEI (Reranker/Embed) | — | — | raguide-tei |
常用 UI 入口:
- Prometheus alerts:
http://localhost:9099/alerts - Alertmanager:
http://localhost:9093 - Grafana 看板:
http://localhost:3002 - Langfuse trace:
http://localhost:3001 - Qdrant dashboard:
http://localhost:6333/dashboard
计入成本累计(被熔断保护)的调用:
- 所有经由
/v1/agent/runAPI 的线上 serving 请求(IntentAgent / RetrievalAgent / AdvisorAgent LLM 调用) - done_hook LLM-as-Judge 评分调用
不计入成本累计(不被熔断影响)的调用:
- eval CLI(
tests/eval/run_gold_set.py)走 in-processrun_graph,不经 API 路由,不经CostGuard.add,不被熔断拦截 - 管理后台连通性探测(probe_endpoint)
少记风险: 请求流中途异常时,已消耗的 token 成本可能漏记(方向=少记,不会误熔断)。
| 限制 | 描述 | 影响 |
|---|---|---|
| cost Gauge 单 worker 语义 | raguide_cost_monthly_cny Prometheus Gauge 为进程内单例,多 worker 部署下各 worker 独立记录 |
当前 demo 单 worker,不影响;多 worker 时需改为 Redis-backed Gauge |
| incrbyfloat+expire 非原子 | 两次 Redis 调用间崩溃可导致月 key 无 TTL 永存 | 月 key 名含年月,下月 key 不同,计费逻辑不受影响;仅该 key 不自动过期(需手动清理或接受永存) |
| 月 key 本地时区 | raguide:cost:monthly:YYYY-MM 使用服务器本地时区 |
跨月瞬间约 1s 窗口内 add 与 exhausted 可能读写不同 key,fail-open 语义下可接受;多实例部署需统一 UTC |
| eval 污染风险 | 机器睡眠可导致 gold-set 评测超时,产生假 GATE FAILED | 参见场景7诊断,用 caffeinate -is 规避 |
| JWT_SECRET 缺失 | env JWT_SECRET 为空时 register/login 返回 503 AUTH_DISABLED |
仅登录态功能不可用,游客零回归(统一身份回退 X-Owner-Id);参见场景9 |
| 升级旧库需幂等 DDL | init.sql 只在数据卷首次初始化跑;已存在库的 users 表 / eval_candidate_cases.signals 列靠 lifespan 幂等 DDL(CREATE/ALTER ... IF NOT EXISTS,try/except 非致命) |
重启后端触发;DDL 被吞时手动补列;参见场景9 |