LiteLLM memory keeps growing after OOM restart, with no apparent reclamation

此问题通常发生在 LiteLLM Proxy 版本 1.98.0 及以上、且数据库(如 Postgres)出现短暂不可用或写入失败时。优先排查是否为数据库写入失败导致的 spend log 无限入队,并通过 glibc 内存分配器参数(MALLOC_ARENA_MAX)进行缓解。

快速结论:此问题通常发生在 LiteLLM Proxy 版本 1.98.0 及以上、且数据库(如 Postgres)出现短暂不可用或写入失败时。优先排查是否为数据库写入失败导致的 spend log 无限入队,并通过 glibc 内存分配器参数(MALLOC_ARENA_MAX)进行缓解。

适用环境:部署于 Kubernetes 的 LiteLLM Proxy;问题在 1.98.0 版本引入,1.97.0 无此问题;复现环境含 Redis 响应缓存、Prometheus 及自定义回调、guardrails、MCP 服务器;真实 Postgres 与 Redis 部署。

最快修复方案:暂无确认的一步修复方案。可优先尝试通过环境变量调整 glibc 分配器参数(完整配置见解决步骤),该方案在 Issue 中被实测可将内存增长幅度减半,但未完全消除。

注意事项:该方案并非根治内存泄漏,而是缓解内存持续增长;会引入约 +11 ms 平均延迟和 +26 ms p95 延迟(在高并发压测环境中测得,实际 LLM 延迟下可忽略)。若队列从未接近 64 MB,降低 SPEND_LOG_QUEUE_MAX_BYTES 无效。

问题场景

用户以 Kubernetes 方式部署 LiteLLM Proxy,提供多模型代理服务。系统运行较长时间后,工作集内存(WSS)持续上升直至触发 OOM,容器重启后内存回落,随后再次无回收地持续增长。用户监控图中显示 WSS 在 OOM 前维持在 23-24 GiB,峰值达到约 40 GiB 后重启,回落后又稳步攀升至接近 20 GiB。

报错原文

LiteLLM memory keeps growing after OOM restart, with no apparent reclamation

原因分析

可能原因并非 Python 对象泄漏,而是 glibc 分配器的原生内存高水位棘轮效应,触发机制是失败的 spend log 数据库写入。1.98.0 版本新增了 enqueue_spend_logs(..., at_head=True) 逻辑:当数据库拒绝写入时,批次会无限入队等待重试(而不是像 1.97.0 那样丢弃)。当 store_prompts_in_spend_logs: true 时,每条队列记录携带完整 prompt 和 response,失败的每次刷新都会使存活对象图膨胀相应字节。由于默认 allow_requests_on_db_unavailable: true,客户端仍然看到成功响应,而队列在后端悄悄保留所有提示词。glibc 不会将这部分内存归还操作系统,因此内存下限随累计失败写入次数单调上升,只有重启才能重置。

这解释了为什么内存看起来与 Pod 运行时长单调相关,实际上是与累计失败写入次数相关;也解释了为什么同一数据库故障会导致所有副本同时出现内存阶梯式跳升。

环境排查

  • 确认 LiteLLM Proxy 版本是否为 1.98.0 或更高(问题在 1.98.0 引入,1.97.0 验证无此问题)。
  • 检查运行环境是否为 glibc 系 Linux 操作系统(Kubernetes 节点)。
  • 确认数据库(Postgres)在 OOM 前后是否存在连接超时、P2028 事务启动超时等写入失败记录。
  • 检查 Proxy 配置中 store_prompts_in_spend_logs 是否为 true(这会放大内存占用)。
  • 通过 Prisma 客户端检查 prisma_client.spend_log_transactions 长度和 PrismaClient.spend_log_queue_bytes 的实时值。

解决步骤

  1. 诊断确认(可选但推荐):注入 tracemallocgc 类型直方图(通过 sitecustomize.py),并对比 RSS 增量与 tracemalloc 追踪增量的差异。若 RSS 增长明显快于 Python 堆追踪增长,且 gc 直方图无对应类型增长,则指向原生分配器而非 Python 对象泄漏。
  2. 应用 glibc 分配器调优参数(可优先尝试方案):在 Kubernetes 部署的环境变量中设置以下参数:
    MALLOC_ARENA_MAX=2
    MALLOC_TRIM_THRESHOLD_=131072
    MALLOC_MMAP_THRESHOLD_=131072

    Issue 中实测在三轮完全相同的故障周期内,内存增长从 83 MiB 降至 59 MiB,即约减半效果。

  3. 评估降级或回退选项:若内存增长仍然不可接受且业务允许,可评估回滚至 1.97.0 版本(该版本在数据库写入失败时会丢弃记录而非无限入队,内存字节级稳定)。
  4. 不推荐的操作:不要单独依赖降低 SPEND_LOG_QUEUE_MAX_BYTES。默认上限为 64 MB,若实际队列远未达到该值(Issue 中实测仅 8 MiB),调低此参数反而可能让内存表现更差。

验证方法

部署上述环境变量后,观察至少一个数据库故障周期(可主动停止 Postgres 或模拟写入超时)中的内存行为。验证标准:故障恢复后 RSS 增长幅度是否显著减小、空闲流量时段内存是否保持稳定(Issue 中生产环境验证为 22 小时内 10 小时夜间窗口内存波动仅 4 MiB,且出现两次 P2028 超时均无阶梯式跳升)。最终确认内存增长是否已从“随 Pod 年龄单调增长”转变为“仅随请求量波动”。

参考来源

BerriAI/litellm #38193

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 21628

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注