配套文档:
RAG生产级开发文档.md·Prompt与评测集管理规范.md·rag-starter/适用对象:SRE、值班工程师、Tech Lead 版本:v1.0
0. 使用说明
本 Runbook 是 值班手册,应满足两个标准:
- 能让一个不熟悉系统的值班同学,按步骤把故障止血。
- 每个 SOP 至少经过一次真实演练。 未演练的 SOP 标为「未演练」,不能依赖。
文档里每个告警都有对应的 SOP 编号,可以从 Grafana 告警直接跳转。
1. 系统拓扑速览
用户 → Ingress(Nginx/APISIX) → rag-api(K8s) → ┬→ TEI-Embedding(GPU)
├→ TEI-Reranker(GPU)
├→ Milvus(Cluster)
├→ Elasticsearch(Cluster)
├→ Redis(Cluster)
├→ PostgreSQL(主从)
└→ Anthropic API(外部)
离线:Airflow → Connectors → 解析/切分 → TEI-Embedding → Milvus + ES
观测:OpenTelemetry → Langfuse + Prometheus + Loki + Grafana
关键依赖外部性排序(出故障对业务影响):
- Anthropic API(不可用 = 服务整体不可用)
- Milvus(不可用 = 召回失败,可降级到 ES)
- ES(不可用 = 召回降级到纯向量)
- TEI-Embedding(不可用 = 召回失败)
- TEI-Reranker(不可用 = 跳过 rerank 仍可用)
- Redis(不可用 = 缓存失效,性能下降不致命)
- PG(不可用 = 反馈/审计停写,主链路不受影响)
2. SLO 与告警分级
2.1 SLO
| 指标 | 目标 | 错误预算(30 天) |
|---|---|---|
| 可用性 | 99.5% | 3.6 小时 |
| 首字节 P95 | < 3s | — |
| 端到端 P95 | < 15s | — |
| 越权穿透率 | 0% | 绝对红线 |
| 召回回归 Recall@10 | ≥ 0.85 | — |
2.2 告警分级
| 等级 | 响应时限 | 通知渠道 | 示例 |
|---|---|---|---|
| P0 | 5 分钟 | 电话 + 短信 + IM | 整体不可用、越权穿透、数据泄漏 |
| P1 | 15 分钟 | IM @值班 | P95 翻倍、错误率 > 5%、Anthropic 限流 |
| P2 | 1 小时 | IM | 单依赖抖动、单 Pod 重启 |
| P3 | 次日处理 | 邮件 | 慢查询、磁盘 70% |
3. 告警与 SOP 索引
| 告警名 | 级别 | SOP |
|---|---|---|
RAGAPIDown | P0 | SOP-001 |
RAGAclBreachDetected | P0 | SOP-002 |
Anthropic5xxSurge | P1 | SOP-003 |
MilvusUnavailable | P1 | SOP-004 |
ESUnavailable | P1 | SOP-005 |
TEIEmbeddingDown | P1 | SOP-006 |
LatencyP95High | P1 | SOP-007 |
Error5xxRateHigh | P1 | SOP-008 |
RecallRegression | P1 | SOP-009 |
DownvoteSurge | P1 | SOP-010 |
IndexingPipelineFailed | P2 | SOP-011 |
DailyTokenBudget80 | P2 | SOP-012 |
RedisHighLatency | P2 | SOP-013 |
DiskUsage70 | P3 | SOP-014 |
4. 关键 SOP
SOP-001: RAG API 整体不可用
判定:/healthz 失败、Ingress 5xx > 50%、用户大面积反馈。
止血步骤(目标:5 分钟内恢复):
-
快速确认范围
kubectl -n rag get pods -l app=rag-api kubectl -n rag describe deployment rag-api | tail -20 curl -s https://rag.internal/healthz -
看最近变更(80% 的故障来自最近发布)
kubectl -n rag rollout history deployment/rag-api | head -5 -
决策:
- 最近 1 小时有发布 → 立即回滚:
kubectl -n rag rollout undo deployment/rag-api kubectl -n rag rollout status deployment/rag-api --timeout=2m - 无发布 → 继续步骤 4。
- 最近 1 小时有发布 → 立即回滚:
-
检查依赖
# 看 rag-api 日志找根因 kubectl -n rag logs -l app=rag-api --tail=200 | grep -E "ERROR|CRITICAL" # 检查依赖 kubectl -n rag get pods -l 'app in (milvus,elasticsearch,redis,postgres,tei-embedding)' -
资源压力?
- OOM:临时把 replica 拉到 12,同时排查 memory leak。
- CPU 满载:检查是不是恶意流量,必要时在 Ingress 上限速。
-
依赖不可用?
- 跳转对应 SOP-004 / 005 / 006。
-
止血后:在事故频道贴 trace_id 样本、当前指标、已做操作,让全员看到状态。
事后:填《事故复盘单》模板,48 小时内召开复盘。
SOP-002: 越权穿透
判定:日志中出现 acl_violation ERROR、或用户报告看到别部门数据。
⚠️ 这是公司红线,按数据安全事故走最高级流程。
止血步骤(目标:10 分钟内阻断):
-
立即对外停服(防止扩散):
# 把 Ingress 切到维护页 kubectl -n rag apply -f deploy/k8s/maintenance-ingress.yaml -
保留现场:
- 不要重启 Pod。
- dump 最近 1 小时 rag-api 日志和 audit_log 表数据到对象存储。
kubectl -n rag logs --since=1h -l app=rag-api > /tmp/incident-$(date +%s).log psql $PG_DSN -c "COPY (SELECT * FROM audit_log WHERE created_at > NOW() - INTERVAL '2 hours') TO STDOUT WITH CSV" > /tmp/audit.csv -
拉群通报:安全 + 数据 + 法务 + 算法 + SRE,按公司数据事件协议走。
-
定位:
- 看
acl_violation日志找具体 chunk_id 和 user_id。 - 检查该 chunk 在 Milvus / ES 里的 acl 字段是否正确:
# rag-api 容器内执行 from app.core.clients import get_milvus client = get_milvus() result = client.query(collection_name="kb_chunks_v1", filter=f'chunk_id == "<...>"', output_fields=["acl"]) - 检查用户的 acl 是不是错配:联系 IAM 同学。
- 看
-
修复:
- 数据层错误:紧急重跑索引 pipeline,修正 acl 字段。
- 代码层错误:紧急 hotfix + 强制 PR 走双人审。
- 用户层错误:联系 IAM 修复。
-
恢复服务:
- 修复确认后,先用 staging 全量验证(专项 ACL 测试集)。
- 灰度 1% → 10% → 100%,每步至少 30 分钟。
事后:72 小时内提交事故报告至安全委员会,告知所有可能受影响用户。
SOP-003: Anthropic API 异常
判定:Anthropic5xxSurge 告警、generation 错误率突增。
止血步骤:
-
看错误类型:
kubectl -n rag logs -l app=rag-api --tail=500 | grep "anthropic" | grep -oE "(429|529|500|502|503)" | sort | uniq -c429:限流,跳到步骤 2。529:Anthropic 过载,跳到步骤 3。5xx:上游故障,跳到步骤 3。
-
限流 (429):
- 短期:在 LLM Gateway 把全局并发 semaphore 调低 20%。
- 中期:联系 Anthropic 提配额(提前准备好 ticket 模板)。
- 长期:把分类/改写换 Haiku,主回答保留 Sonnet。
-
上游故障 (5xx/529):
- 检查 Anthropic Status。
- 降级路径:把主模型切到 Haiku(应急配置):
注意:Haiku 答案质量下降,需要业务团队同步知会。kubectl -n rag set env deployment/rag-api CLAUDE_MODEL_MAIN=claude-haiku-4-5-20251001 kubectl -n rag rollout restart deployment/rag-api - 如果有备用通道(Bedrock / Vertex 部署的 Claude),切换流量。
-
持续超过 30 分钟无法恢复:
- 在前端展示「LLM 服务异常,仅检索可用」提示。
- 把 API 改为只返回检索结果(citation 列表),不生成答案。
SOP-004: Milvus 不可用
判定:milvus_search 错误率 > 50%、Milvus QueryNode Pod 异常。
止血步骤:
-
快速诊断:
kubectl -n milvus get pods kubectl -n milvus logs <querynode-pod> --tail=100 -
降级:在 rag-api 配置里把 hybrid 退化为纯 ES:
kubectl -n rag set env deployment/rag-api RETRIEVAL_MODE=es_only kubectl -n rag rollout restart deployment/rag-api⚠️ 此降级要求代码侧支持
RETRIEVAL_MODE配置,starter 默认不带,需提前实现。 -
恢复 Milvus:
- QueryNode 重启:
kubectl -n milvus rollout restart sts milvus-querynode - 数据节点问题:联系 DBA / 用 Milvus Operator 修复。
- 索引损坏:从快照恢复(要求 Milvus 已开启 backup)。
- QueryNode 重启:
-
恢复后:去掉降级配置,灰度切回 hybrid。
SOP-005: ES 不可用
判定:BM25 召回错误率 > 50%。
止血步骤:
-
诊断:
curl http://elasticsearch:9200/_cluster/health?prettystatus=red:分片丢失,严重。status=yellow:副本未分配,但可用。
-
降级:hybrid 退化为纯向量召回。代码侧已支持
es_search失败回退(hybrid.py中已有 try-catch)。理论上无需人工干预,但需要监控 Recall@10 是否回落到可接受区间。 -
恢复:
- 黄色:等副本自动分配,或手动 reroute。
- 红色:从快照恢复,参考 ES 官方文档。
SOP-006: TEI Embedding 不可用
判定:embed_query 错误率 > 50%。
止血步骤:
-
诊断:
kubectl -n rag get pods -l app=tei-embedding kubectl -n rag logs <tei-pod> --tail=100 curl http://tei-embedding/health -
常见原因:
- GPU OOM:批量请求过大,单实例承载不住 → 扩副本。
- 显存碎片:滚动重启。
- GPU 驱动异常:联系基础架构。
-
降级:
- 没有简单降级。Embedding 是向量召回必经。
- 唯一兜底是切到纯 BM25 ES 召回(同 SOP-004 步骤 2 的反向)。
-
预防:永远保留至少 2 副本,且分布在不同 GPU 节点。
SOP-007: 延迟突增
判定:rag_latency_seconds{quantile="0.95"} > 5s 持续 5 分钟。
排查路径(按从快到慢的顺序):
-
看 Langfuse trace 找哪一段最慢:
- Rewrite > 1s?→ Anthropic 抖动,跳 SOP-003。
- Retrieval > 2s?→ Milvus/ES 抖动,跳 SOP-004/005。
- Rerank > 2s?→ TEI-Reranker 压力,看 GPU 利用率。
- LLM > 10s?→ 上下文太长(看 token 数),或 Anthropic 慢。
-
看上下文 token 数:
kubectl -n rag logs -l app=rag-api --tail=500 | grep "context_tokens" | jq '.context_tokens' | sort -n | tail -20异常长 → 检查切分是否退化、retrieval 是否返回了超大 chunk。
-
看流量:是否突增导致排队。Grafana 看 QPS,HPA 是否扩了副本。
-
临时缓解:
- 把
RETRIEVAL_TOP_K从 50 调到 30。 - 把
RERANK_TOP_N从 8 调到 5。 - 把
CONTEXT_TOKEN_BUDGET临时调低。
- 把
SOP-008: 5xx 错误率突增
判定:rag_request_total{status=~"5.."} 占比 > 1%。
排查:
-
看错误分布:
kubectl -n rag logs -l app=rag-api --tail=1000 | grep ERROR | grep -oE 'error[^,]*' | sort | uniq -c | sort -rn | head -
常见根因:
UpstreamTimeout→ 跳 SOP-007。UpstreamUnavailable→ 跳对应依赖 SOP。pipeline_failed→ 看具体 exception,可能是代码 bug。- OOM → 扩容 + 排查 memory leak。
-
临时缓解:限流 / 扩容 / 回滚最近发布。
SOP-009: 召回质量回归
判定:nightly eval recall@10 低于阈值。
排查:
-
看是哪类 case 退化:
cat reports/full.json | jq '.cases[] | select(.recall_at_10 < 0.5) | .tags' | sort | uniq -c -
常见原因:
- 索引 pipeline 异常:某批数据没入库 → 跳 SOP-011。
- Embedding 模型换了:版本号不一致。
- 切分策略改动:chunk 边界破坏了原结构。
-
回滚:召回回归是 P1 但通常可以等 24h 修复。如果影响重大业务,走 prompt/索引版本回滚流程。
SOP-010: 用户负反馈突增
判定:down_rate = down / (up + down) > 15% 持续 1 小时。
排查:
-
看是哪类问题被踩:
SELECT al.question, COUNT(*) as cnt FROM feedback f JOIN audit_log al USING (trace_id) WHERE f.rating='down' AND f.created_at > NOW() - INTERVAL '2 hours' GROUP BY al.question ORDER BY cnt DESC LIMIT 20; -
常见根因:
- 知识库内容陈旧(业务变了文档没更新)→ 通知内容团队。
- 某类长尾问题召回不到 → 加进 hard.jsonl,立项优化。
- 最近 prompt 变更引起 → 看是不是要回滚。
-
应急响应:把高频负反馈 query 的 top-3 加入「已知问题清单」,前端展示前置提示。
SOP-011: 索引 Pipeline 失败
判定:Airflow DAG 失败、indexing_failures_total 突增。
排查:
-
看 Airflow 日志找哪个 task 失败。
-
常见原因:
- 数据源 API 限流 → 调整 connector 并发度。
- 文档格式异常 → 看 parser 异常栈,加 fallback 解析。
- TEI 不可用 → 跳 SOP-006。
- 双写一致性失败 → 检查 Milvus / ES 一致性,必要时重跑该 doc。
-
影响评估:
- 失败 < 1% 文档 → 走重试。
- 失败 > 10% → 暂停 pipeline,根因修复后再放开。
⚠️ 红线:禁止直接跳过失败 doc 标记成功,必须有兜底重试或人工介入。
SOP-012: 成本预算告警
判定:当日 token 消费达月预算 80%。
应急动作:
-
看消费分布:
SELECT prompt_version, COUNT(*) cnt, SUM((latency_ms->>'llm_ms')::int) total_llm_ms FROM audit_log WHERE created_at > NOW() - INTERVAL '1 day' GROUP BY prompt_version; -
快速降本:
- 把分类/改写从 Sonnet 切到 Haiku。
- 检查 Prompt Caching 命中率,不到 50% 必须排查(看
cache_read_input_tokens占比)。 - 启用答案缓存(如已实现)。
- 限制单用户日 token 配额。
-
中期:
- 找出高频重复 query,运营沉淀成 FAQ 直答。
- 评估能否做 query 聚类后批量缓存。
SOP-013: Redis 延迟
判定:Redis P99 > 50ms。
步骤:
- 看 slowlog:
redis-cli -h <host> SLOWLOG GET 20 - 看内存:
redis-cli -h <host> INFO memory - 应急扩容或清理过期 key。
- 缓存不可用时 rag-api 应能 graceful 降级(直查后端),需在代码侧确保。
SOP-014: 磁盘空间
判定:节点磁盘使用率 > 70%。
步骤:
- 看大头:
du -sh /var/lib/{milvus,elasticsearch,postgres}/* | sort -h | tail - 常见可清理:
- ES 老 index:
curl -X DELETE elasticsearch:9200/kb_chunks_v0-*(确认无在用) - Milvus 老 collection:要求该 collection 退役 ≥ 7 天。
- PG 老 audit_log:超过 180 天归档到对象存储。
- ES 老 index:
- 扩容 PVC。
5. 发布与回滚 SOP
SOP-R1:标准发布流程
前置检查清单:
- PR 已合并主分支
- CI 全部通过(lint / unit / smoke eval)
- 镜像已推送到 registry 并扫过漏洞
- 影响评估写入 PR 描述
- 通知值班同学和业务接口人
发布步骤:
-
预发部署:
kubectl -n rag-staging set image deployment/rag-api rag-api=<image-tag> kubectl -n rag-staging rollout status deployment/rag-api -
预发回归:
poetry run pytest tests/eval -m smoke --env=staging -
生产金丝雀(1%):
- 用 Argo Rollouts / Flagger / 手动 traffic split。
- 观察 30 分钟:错误率、P95、👎 率。
-
小流量(10%):观察 2 小时。
-
全量:观察 24 小时,期间值班不离岗。
SOP-R2:紧急回滚
触发条件(任一即触发):
- 5xx 比例 > 2%
- P95 延迟翻倍
- 👎 率 > 15%
- 越权穿透
- 评测指标回归 > 5%
步骤:
-
代码回滚(< 1 分钟):
kubectl -n rag rollout undo deployment/rag-api kubectl -n rag rollout status deployment/rag-api --timeout=2m -
配置回滚(如有):
kubectl -n rag apply -f configs/last-known-good/configmap.yaml kubectl -n rag rollout restart deployment/rag-api -
数据回滚(向量库 collection 切换):
# 把别名切回老 collection poetry run python scripts/switch_milvus_alias.py --alias kb_chunks_alias --target kb_chunks_v1 -
通知:在值班群和事故频道同步状态。
-
复盘:48 小时内开会。
6. 演练计划
未演练的 SOP 等于摆设。建议演练频率:
| 演练 | 频率 | 形式 |
|---|---|---|
| 紧急回滚 | 每月 | 在 staging 实操 |
| Anthropic 切换 | 每季 | 在 staging 把 base_url 切到模拟 503 |
| Milvus 单节点 down | 每季 | Chaos 工具 kill 一个 Pod |
| ACL 越权红蓝对抗 | 每半年 | 安全团队主导 |
| 全链路停电 | 每年 | 整个 region 切换 |
每次演练产出 演练报告:步骤是否清晰、耗时多少、卡点在哪、需要 SOP 改进什么。
7. 值班交接
每周值班轮换。交接清单:
- 当周告警列表与处理状态
- 未关闭的 P1/P2
- 下周计划发布
- 进行中的演练
- 待加进评测集的 bad case
- 待补充的 SOP
模板存在 runbook/handover-template.md,每周提交一次。
8. 联系人与外部资源
| 角色 | 工作时间 | 紧急联系 |
|---|---|---|
| RAG 算法 OnCall | IM | 手机 |
| 平台 SRE OnCall | IM | 手机 |
| Anthropic 商务 | 邮件 | 客户成功经理 |
| 数据/IAM Owner | IM | 手机 |
| 安全 OnCall | IM | 24h 值班电话 |
关键外部链接:
- Anthropic Status: status.anthropic.com/
- Milvus 故障排查: milvus.io/docs/troubl…
- 内部 Grafana: grafana.internal/d/rag
- 内部 Langfuse: langfuse.internal/
- 事故复盘模板:
runbook/postmortem-template.md
附录 A:事故复盘模板
# 事故复盘 - [日期] [简要标题]
## 一句话总结
(用户视角影响 + 根因)
## 时间线
- HH:MM - 告警触发 / 用户反馈
- HH:MM - 值班介入
- HH:MM - 定位到 XX
- HH:MM - 止血操作
- HH:MM - 服务恢复
- HH:MM - 完全修复
## 影响范围
- 持续时间:
- 影响用户:
- 影响功能:
- 数据是否泄漏:
## 根因
(5 Whys)
## 处置过程评价
做得好的:
做得不好的:
## 改进项(必须可追踪)
- [ ] [P0] 修复根因代码 - Owner: XX - DDL:
- [ ] [P1] 增加监控覆盖 - Owner: XX - DDL:
- [ ] [P2] 补充 SOP / 演练 - Owner: XX - DDL:
## 教训
(一句话,可以放进 CLAUDE.md 或 wiki 沉淀)
附录 B:值班同学的应对原则
- 先止血,再定位:用户体验优先于根因。
- 不要单独操作生产:双人复核 + 留下记录。
- 不确定就停手:宁可让告警飞一会,也不要乱操作放大故障。
- 保留现场:重启前先 dump 日志和状态。
- 及时同步:值班群 5 分钟一次状态更新,让所有人知道你在做什么。
- 学会喊人:超过 30 分钟没进展,毫不犹豫升级到 Tech Lead。
- 复盘是文化不是追责:故障是系统问题,不是个人问题。