快速结论:这个报错通常出现在 LiteLLM 代理实例在高流量下启动预算窗口计数器(Redis 冷启动、TTL 过期)时,LiteLLM_SpendLogs 表缺少 (api_key, startTime) 复合索引导致顺序扫描排空数据库 CPU,进而触发 Prisma P2028 事务超时。优先排查 LiteLLM_SpendLogs 表上是否存在覆盖索引(covering index),而不是单纯依赖默认的 startTime 单列索引。
适用环境:LiteLLM v1.93.0(litellm-database 镜像)、Postgres 16(AWS RDS,2 vCPU 实测受影响)、3 个代理实例、Redis 启用。Python/CUDA/显卡版本在 Issue 中未提及。
最快修复方案:暂无确认的一步修复方案。维护者在评论中表示“索引不是最终方案,会重构 spend 计算方式并引入 budget windows 表”,但该重构尚未落地。可优先尝试的缓解手段是:手动在 Postgres 上创建覆盖索引(见解决步骤),已在生产环境实测将查询从约 170 ms 降到 65 ms(dominant key 场景),selective key 从约 170 ms 降到亚毫秒级。
注意事项:Prisma 的 schema DSL 不支持 INCLUDE 列(覆盖索引),必须通过 litellm-proxy-extras 中的原生 SQL 迁移来创建,不能直接写进 schema.prisma。另外只有一个普通复合索引 (api_key, startTime) 是不够的——当某个 key 占表数据 ~97% 时,规划器会放弃索引改走顺序扫描,所以必须用覆盖索引把 spend 列也包含进去。此修复是缓解手段,不是 Issue 作者和 LiteLLM 维护者认可的最终架构方案。
问题场景
LiteLLM 代理实例(3 个)在正常流量下处理预算窗口(budget_duration)逻辑时,Redis 中的预算计数器冷启动(新 pod、TTL 过期或低流量期后恢复),触发 SpendCounterReseed.window_from_spend_logs() 在请求路径上执行回填查询。该查询在 LiteLLM_SpendLogs 表上按 api_key 和 startTime 聚合 spend,由于缺少合适索引,Postgres 对全表做顺序扫描,将 2 vCPU 的 RDS 实例 CPU 打满到 ~94%,导致并发的 spend 更新事务无法在 60 秒超时内启动,最终返回 Prisma P2028 错误。
报错原文
DB read/write call failed: 504: {"is_panic":false,"message":"Transaction API error: Unable to start a transaction in the given time.","meta":{"error":"Unable to start a transaction in the given time."},"error_code":"P2028"}
[Non-Blocking]LiteLLM Prisma Client Exception - update spend logs: 504 ...
File "litellm/proxy/db/db_spend_update_writer.py", line 1136, in _commit_spend_updates_to_db
async with prisma_client.db.tx(timeout=timedelta(seconds=60)) as transaction:
-- 压垮 DB 的 tokenized 查询(RDS Performance Insights 定位):
SELECT "api_key", SUM("spend")
FROM "LiteLLM_SpendLogs"
WHERE "api_key" = $1 AND "startTime" >= $2
GROUP BY "api_key"
原因分析
触发查询本身来自 litellm/proxy/db/spend_counter_reseed.py 的 SpendCounterReseed.window_from_spend_logs(),经由 proxy_server.py 的 _authoritative_floor_spend() 调用,运行在请求路径上。schema.prisma 中 LiteLLM_SpendLogs 只有 startTime、(startTime, request_id)、end_user、session_id 四个索引,没有 api_key 索引(team_id 也没有)。当窗口起点 $2 覆盖表的大部分数据时(如日/周窗口),startTime 索引选择性不足,Postgres 退化为顺序扫描逐行过滤 api_key,成本随表大小线性增长。多个 pod 同时冷启动时形成 reseed 风暴,进一步放大 DB 压力。
后续生产实测补充了一个关键细节:即便加了普通复合索引 (api_key, startTime),对于占表 ~97% 行数的 dominant key(单 key 约 353,554 / 356k 行),规划器仍会放弃索引走并行顺序扫描,因为通过索引取 97% 的表数据比顺序扫描更慢。只有覆盖索引(INCLUDE ("spend"))才能同时解决 selective key 和 dominant key 两种情况:聚合可以完全从索引计算(index-only scan),无需回表拉取宽行(messages、response、metadata)。
环境排查
- LiteLLM 版本:确认是否 v1.93.0 或相近版本(
litellm-database镜像)。 - Postgres 版本:确认是否为 16(AWS RDS)或兼容版本。
- 实例规格:确认 DB 实例 vCPU 数(Issue 实测为 2 vCPU,CPU 饱和是 P2028 的直接诱因)。
- 代理实例数:确认是否为多实例部署(Issue 为 3 个),多实例同时冷启动会放大 reseed 风暴。
- Redis:确认已启用,且预算窗口计数器由 Redis 管理。
- 表数据分布:查询
LiteLLM_SpendLogs中各api_key的行数占比,判断是否存在 dominant key(Issue 中一个 key 占 97%)。 - 确认当前
LiteLLM_SpendLogs上已有的索引列表,排除是否已存在api_key或(api_key, startTime)索引。
解决步骤
- 第一步:在 Postgres 上创建覆盖索引(可优先尝试)。直接在 RDS 上执行(
CONCURRENTLY避免锁表阻塞写入):CREATE INDEX CONCURRENTLY "LiteLLM_SpendLogs_api_key_startTime_spend_idx" ON "LiteLLM_SpendLogs" ("api_key", "startTime") INCLUDE ("spend");如果也用到 Team 维度的预算窗口,可同步创建:
CREATE INDEX CONCURRENTLY "LiteLLM_SpendLogs_team_id_startTime_spend_idx" ON "LiteLLM_SpendLogs" ("team_id", "startTime") INCLUDE ("spend"); - 第二步:验证执行计划。用
EXPLAIN ANALYZE跑 Issue 中的查询,确认是否命中 Index Only Scan 且 Heap Fetches 很少(Issue 实测为 17)。 - 第三步:跟进官方 schema 变更。由于 Prisma schema DSL 不支持
INCLUDE列,需要在litellm-proxy-extras中通过原生 SQL 迁移来维护该索引。注意:schema.prisma里加@@index([api_key, startTime])只能生成普通复合索引,对 dominant key 无效(规划器会忽略)。 - 第四步:关注上游重构。LiteLLM 维护者已表示将重构 spend 计算方式并引入专门的 budget windows 表,此索引是临时缓解手段,后续应跟进版本更新。
验证方法
创建索引后,用 EXPLAIN ANALYZE 复现 Issue 中的聚合查询,确认执行计划从 Parallel Seq Scan(~170 ms)变为 Parallel Index Only Scan(~65 ms,selective key 为亚毫秒级)。同时观察 RDS CloudWatch 的 CPU 利用率是否回落到正常水位,确认 P2028 报错不再出现在 Slack 告警或 db_exceptions 日志中。建议在低峰期操作,并在创建索引后执行一次 VACUUM (ANALYZE) 更新统计信息(Issue 实测即在此操作后测量)。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![[bug]: InvokeAI v6.14.0-RC1 Crashed while generating Krea-2 Image](https://www.chat-gpts.plus/wp-content/uploads/2026/09/9444-d6bdc60c-768x403.jpg)
![[Question]: Shared embedded chat URL fails to access documents after logout or when accessed by other users](https://www.chat-gpts.plus/wp-content/uploads/2026/09/15895-cf3f7033-768x403.jpg)
