个人RAG上线翻车实录-记一次 API 延迟排查

一次 API 延迟排查:从 2 分钟到 314ms,我做了这 5 步

我自己做了一个知识库,问答上线时好好的,8 秒出答案。有一天突然变成 2-5 分钟,页面直接卡死,Ctrl+C 都按不动。 用户说"之前很快"。我花了两天排查,最后把一次问答压回 314ms。 全程 5 步,每一步都在缩小嫌疑范围。

1. 症状:8 秒变 2 分钟

Vue 版前端上线后,一次问答 2-5 分钟。旧 Jinja2 版同样慢。页面完全卡死,终端 Ctrl+C 无效------说明卡的是后端请求,不是前端渲染。

2. 第一步:排除 Vue CDN

Vue 通过 CDN 加载,浏览器要阻塞等脚本下载。国内访问 unpkg.com 极慢,怀疑它。

换了 npmmirror 国内镜像,还是慢。算一笔账:脚本加载再慢,几十秒也该超时;卡死 5 分钟,问题一定在后端。

这一步的收尾是顺手把 Vue 下载到本地 static/,彻底消除 CDN 依赖------不是主因,但值得做。

3. 第二步:模型名是诱因,不是主因

之前排查 DeepSeek 400 错误时,发现模型名从 deepseek-chat 变成了 deepseek-v4-pro。改成 v4-pro 后 API 能用了,但没注意 v4-pro 慢得多。

换成 deepseek-v4-flash,API 耗时缩到 5-16s(原来约 28s)。有效果,但解释不了 2 分钟------就算 API 要 16s,中间还有 1 分多钟不知道去哪了。

4. 第三步:加计时日志,锁死 Redis

ask() 里插入打印语句,输出每步耗时(当时的调试输出,修复后已删除):

ini 复制代码
[计时] Redis查缓存: 10.3s     ← 疑点!
[计时] ChromaDB检索: 11.1s    ← 实际上只有 0.8s(含等待)
[计时] 调DeepSeek前: 32.7s    ← 中间 21 秒去哪了?
[计时] DeepSeek返回: 45.1s

5 行 print 比 1 小时瞎猜管用。ChromaDB 检索实际只要 0.8s,但"Redis 查缓存"一项就吞了 10-21 秒,ChromaDB 和 DeepSeek 之间还莫名多了 20 秒------那又是另一处 Redis 调用。

5. 第四步:临时禁用 Redis,实锤

把所有 Redis 调用替换成返回 None 的空函数,彻底跳过。

结果:ChromaDB 0.6s、DeepSeek 16s、总耗时 16s。Redis 就是罪魁祸首。

6. 第五步:修 Redis 挂起 + 启动 Redis

根因有两层:

层一:socket 超时不可靠。 代码里设了 socket_connect_timeout,但 ping() 不尊重这个参数。Redis 容器没启动时,一次连接挂起 10-21 秒才报错。

层二:一个请求摸了 4 次 Redis。 我顺着 ask() 数了一遍源码(rag_engine.py):查缓存、读会话、存会话、存缓存,四处调用点。Redis 挂起时,每一处都等满超时。

修复方案:加一个全局熔断标记------连不上就锁 60 秒,期间直接跳过 Redis。以下是 redis_cache.py 的真实源码(逐字):

python 复制代码
# 全局状态:避免每次请求都重试连 Redis
_redis_client = None
_redis_unavailable_until = 0.0  # 时间戳------在此之前不重试


def _get_client():
    """懒加载 Redis 连接------连不上 60 秒内不再重试"""
    global _redis_client, _redis_unavailable_until

    now = _time.time()
    if now < _redis_unavailable_until:
        return None  # 刚才连不上,跳过

    if _redis_client is not None:
        try:
            _redis_client.ping()
            return _redis_client
        except Exception:
            _redis_client = None

    try:
        import redis
        host = os.getenv("REDIS_HOST", "localhost")
        port = int(os.getenv("REDIS_PORT", "6379"))
        db = int(os.getenv("REDIS_DB", "0"))
        password = os.getenv("REDIS_PASSWORD", None)

        _redis_client = redis.Redis(
            host=host, port=port, db=db, password=password,
            socket_connect_timeout=0.5, socket_timeout=0.5,
        )
        _redis_client.ping()
        _redis_unavailable_until = 0.0  # 连上了,重置标记
        logger.info(f"Redis 已连接: {host}:{port}")
        return _redis_client
    except Exception:
        logger.info("Redis 未连接------60s 内不再重试")
        _redis_unavailable_until = now + 60  # 60 秒内不走连接逻辑
        return None

Redis 本身也没在跑,顺手 docker start redis-test 启动。修好后:同问题第二次命中缓存 → 314ms。

7. 时间线对比

阶段 请求耗时 根因
原始状态(旧 deepseek-chat) ~8s 正常
模型名过期 + 改 v4-pro 2-5 分钟 Redis 挂起 10-21s/次 × 多处调用 + API 28s
换 v4-flash 1.9 分钟 Redis 挂起仍未解决,API 降到 16s
临时禁用 Redis 16s 只剩 DeepSeek API 时间
Redis 修好 + 启动 5s(首次)/ 314ms(缓存命中) 正常

8. 教训

  1. 外部依赖挂了不能拖垮主流程。 Redis 是缓存,应该"可用则加速,不可用则跳过"。连不上重试 21 秒,完全违背了加缓存的目的。
  2. socket_connect_timeout 不可靠。 Python redis 库的 timeout 参数不能保证所有操作都在超时内完成。应用层防御比库参数可靠------连不上就锁 N 秒。
  3. 每一个外部依赖都是延迟源。 一个函数里调了 4 次 Redis,每次都可能挂起。缓存、日志、监控这些"辅助功能"都能在关键时刻拖慢主流程。
  4. 计时日志是最快的排查工具。 每步打印耗时,一眼看出瓶颈。
  5. 关掉它,排查就变简单了。 怀疑 Redis 就把返回值设成 None,立刻确认。修好再打开。

9. 问题速查

问题 答案
为什么 socket 超时不可靠 部分 redis-py 操作(如 ping)不严格受 socket 超时约束,挂起仍可能超过
缓存挂了怎么保证不拖垮主流程 熔断:连不上锁 N 秒,期间直接跳过缓存
排查性能问题第一步做什么 加每步计时日志,用数据定位,不靠猜

一句话总结

一次问答从 8 秒变成 2 分钟,最后是缓存拖垮了主流程。外部依赖都要当"可能挂"来写代码------可用则加速,不可用则跳过,熔断 60 秒,主流程永远不让缓存牵着走。

下一篇写我的知识库为什么搜得准:英文 Embedding 换成中文模型后,检索精度差在哪,V3 增量索引怎么做的。

相关推荐
郝学胜_神的一滴12 分钟前
C++11 工程级应用 06:自己造一个类似Python的Range迭代器
c++·后端
小蒜学长28 分钟前
基于SpringBoot + Vue的智能健身房管理系统的设计与实现(代码+数据库+LW)
java·数据库·vue.js·spring boot·后端
Sam_Deep_Thinking1 小时前
聊聊开闭原则,以及它在Spring里的样子
java·后端·spring·程序员·开闭原则
IT_陈寒1 小时前
Redis的Lua脚本居然把我的集群搞崩了!
前端·人工智能·后端
SimonKing1 小时前
开源神器 Navop:数据库+SSH+终端+AI,一个应用全搞定
java·后端·程序员
爱码猿1 小时前
SpringBoot+MybatisPlus动态数据源
java·spring boot·后端
ttwuai2 小时前
Go 后台接入 CAS 单点登录后,原来的权限怎么继续生效?
开发语言·后端·golang
小蒜学长2 小时前
springboot党建云课堂学习与管理系统(代码+数据库+LW)
java·数据库·spring boot·后端·学习
烂蜻蜓4 小时前
Flask入门教程(十二):表单处理——从基础到Flask-WTF完整实践
后端·python·flask