RAG生产遇到内存泄漏,长时间运行向量服务重启策略
RAG 生产环境的向量服务跑上几天后,内存占用一路走高,重启后又能恢复,这大概率是内存泄漏或资源未释放导致的。
解决思路不是简单加内存,而是先定位内存消耗点,再制定一套带阈值的自动重启策略,让服务在失控前自我恢复。
本文按“定位—策略—执行—验证”的顺序,给出可直接复制到服务器上使用的方案。
先确认内存涨在哪一层
不要看到内存高就重启,先弄清楚是系统缓存、进程堆内存,还是显存或 GPU 缓存。
下面这几个命令建议按顺序执行。
查看内存整体情况:
free -h
查看占用最高的进程:
ps aux --sort=-%mem | head -20
查看具体进程的详细内存映射,确认堆内存和共享内存占用:
pmap -x
如果是向量检索使用 GPU,还需要确认显存占用:
nvidia-smi --query-gpu=memory.used,memory.total --format=csv
排查常见原因时,优先检查这几处:
- 客户端连接池没有释放,连接对象持续堆积;
- 向量索引或缓存对象被反复加载,旧对象未被 GC 回收;
- 日志句柄、文件句柄泄漏;
- Python 进程内使用了大对象列表,内存碎片化严重。
如果 free -h 里 available 持续下降,并且进程 RSS 只增不减,基本可以判定服务存在泄漏,需要进入下一步设计重启策略。
设计一套带阈值的自动重启方案
生产环境不建议频繁手动重启,推荐用 systemd 托管服务,配合一个监控脚本在内存超阈值时自动拉起。
这里以服务名 vector-search.service 为例。
先创建监控脚本 /usr/local/bin/check_vector_mem.sh:
#!/bin/bash
# 设置内存阈值,单位 MB,按实际物理内存调整
THRESHOLD=4096
SERVICE=vector-search.service
# 取该服务主进程的 RSS 最大值
RSS=$(ps -eo rss,cmd | grep vector-search | grep -v grep | awk '{print $1}' | sort -n | tail -1)
if [ -z "$RSS" ]; then
echo "vector-search process not found"
exit 1
fi
RSS_MB=$((RSS / 1024))
echo "current RSS: ${RSS_MB}MB, threshold: ${THRESHOLD}MB"
if [ "$RSS_MB" -gt "$THRESHOLD" ]; then
echo "memory usage exceeded threshold, restarting ${SERVICE}"
systemctl restart ${SERVICE}
fi
给脚本执行权限,并加入 crontab:
chmod +x /usr/local/bin/check_vector_mem.sh
crontab -e
添加一行,每 5 分钟检查一次:
*/5 * * * * /usr/local/bin/check_vector_mem.sh >> /var/log/vector_mem_check.log 2>&1
重启 crontab 服务后策略即生效。
特别注意:脚本里的 THRESHOLD 要结合业务流量和真实峰值内存设置,建议先观察一周正常波动,再往上加 20% 作为阈值,避免误杀。
重启前必须保存的现场和数据
自动重启虽然能缓解问题,但如果在内存临界时强制重启,可能造成向量索引未落盘、未处理完的写入丢失。
因此重启策略必须和优雅停机结合。
在 systemd 服务文件里,可以这样设置停止超时和信号:
[Service]
ExecStart=/usr/bin/python3 /app/server.py
ExecStop=/bin/kill -SIGTERM $MAINPID
TimeoutStopSec=30
KillSignal=SIGTERM
如果你的服务支持优雅退出,需要在代码里实现以下动作:
- 停止接收新请求并排空当前队列;
- 将内存中的增量索引或待写数据 flush 到磁盘或对象存储;
- 关闭连接池、数据库连接和临时文件句柄;
- 最后再调用
os._exit(0)或正常退出。
同时建议在重启前把内存快照和日志保留一份,方便事后分析泄漏点:
tar -czf /data/mem_$(date +%Y%m%d_%H%M%S).tar.gz /var/log/vector-search.log /app/tmp/
这步做完,即使自动重启触发,数据安全性也更有保障。
验证重启策略有没有生效
策略上线后,不能只看短时间内没崩,要持续观察几个维度。
查看重启记录:
journalctl -u vector-search.service --since today | grep -E "Started|Stopped|memory usage"
查看内存变化趋势,确认重启后 RSS 是否回落:
ps -eo rss,cmd | grep vector-search | grep -v grep
同时检查日志文件里是否出现预期的 flush 完成记录:
tail -n 50 /var/log/vector-search.log | grep -i "flush completed"
如果重启后内存很快又冲到阈值,说明泄漏问题比较严重,建议用 objgraph 或 pympler 做 Python 对象分析,重点排查缓存和连接池。
对于向量数据库本身的碎片问题,可以定期执行 compact 或 optimize 操作,降低底层索引对内存的占用。
避坑要点和几个常见疑问
- 不要只调大 JVM 堆或 Python 内存限制而不重启,向量服务内存泄漏往往发生在 C 扩展层,单纯调参治标不治本。
- 自动重启脚本里要排除 grep 自身进程,上面的脚本已经用了
grep -v grep,不要省略。 - 多个向量服务进程的 RSS 需要分开计算,不能混在一起,否则阈值判断不准。
有同学问:内存没有立刻下降,重启后 RSS 反而更高了,怎么回事?
这通常是启动阶段要加载索引和预热缓存,前十几分钟内存偏高属于正常,可以给监控脚本加一个 STARTUP_GRACE=300 秒的跳过逻辑。
还有人问为什么不直接用 OOM Killer。
让内核来杀进程虽然省事,但杀完不会自动拉起服务,而且系统日志会持续刷 OOM 信息,不适合生产环境,远不如在应用层做优雅重启可控。
最后再提醒一句:本篇的策略适合应急和日常防护,真正要从根上解决内存泄漏,建议结合压测环境复现问题,定位到具体模块后修复代码。
定期巡检、日志分析和自动化重启,是 RAG 生产环境稳定运行的三道防线。
如果你正在处理 182.RAG生产遇到内存泄漏,长时间运行向量服务重启策略,建议先按本文步骤完整执行,再根据自己的环境做微调;
遇到异常时优先回看避坑和高频问题部分。