跳转至

03 · 降低心跳成本

本文系统讲清楚:心跳到底烧什么、按什么因子放大,以及哪些手段能把它压下去。


1. 心跳成本的三个独立因子

flowchart LR
    Total["每小时心跳<br/>请求数"]
    F1["① 频率<br/>3600 / TTL"]
    F2["② 模型数<br/>未禁用心跳的 deployment"]
    F3["③ Pod 放大<br/>多 Pod fallback"]
    F4["④ 单 deployment 内放大<br/>retry × wildcard fallback"]

    F1 --> Total
    F2 --> Total
    F3 --> Total
    F4 --> Total

    style F1 fill:#fef
    style F2 fill:#eff
    style F3 fill:#fee
    style F4 fill:#ffe

公式(量级估算):

请求数/小时 ≈ (3600 / TTL) × deployment数 × Pod数 × wildcard扇出 × (1 + 期望retry次数)

举例:本项目 prod 早期配置(TTL=300、28 deployment、9 Pod、无 wildcard、健康模型 retry≈0):

3600/300 × 28 × 9 × 1 × 1 = 3024 请求/小时 ≈ 252 请求/5分钟

观察值是 242/5 分钟,与公式一致(242 ≈ 252,差几个属于失败被 timeout 吃掉)。


2. 压制手段(按 ROI 排序)

2.1 ★★★ 拉大 TTL(频率因子)

最简单、收益最大、不改代码不改模型配置:

# k8s deployment env
- name: DEFAULT_SHARED_HEALTH_CHECK_TTL
  value: "1800"                      # 30 分钟
- name: DEFAULT_SHARED_HEALTH_CHECK_LOCK_TTL
  value: "120"

改完必须 rolling restart 所有 Pod02 §2.2)。

效果:5min → 30min,请求量 ÷ 6

📌 注意:这个手段只对 SHARED 模式有效。如果你 redis_usage_cache=None 走 DIRECT 路径,需要改 YAML 的 health_check_interval(默认 300)。最快验证当前走哪条路径见 04 §1.

2.2 ★★★ 给贵模型加 disable_background_health_check(模型数因子)

model_list 里的 deployment 不是每个都需要每 30 分钟主动巡检。一份合理的策略:

模型类型 心跳策略
关键链路、调用量大、便宜 保留心跳,正常巡检
备用模型、调用量小、便宜 保留心跳
贵模型(gpt-4o / Claude Opus / Gemini Pro) 禁用心跳改打便宜替代
通配符模型 禁用心跳(扇出代价不可控)
测试模型、灰度模型 禁用心跳

YAML:

model_list:
  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
    model_info:
      disable_background_health_check: true

UI 录入的模型直接 SQL 批量改:

UPDATE "LiteLLM_ProxyModelTable"
SET model_info = jsonb_set(
    coalesce(model_info, '{}'::jsonb),
    '{disable_background_health_check}',
    'true'::jsonb
)
WHERE model_name IN (
    'gpt-4o', 'gpt-4o-2024-08-06',
    'claude-opus-4-7', 'claude-sonnet-4-6',
    'gemini-2-5-pro'
);

效果:参与轮询的 deployment 数从 28 → 10,请求量 × 10/28 ≈ ÷ 2.8

改完不需要重启 proxy,下一个 tick 自动生效(01 §2 关键事实#2)。

2.3 ★★ 让贵模型改打便宜替代(单次成本因子)

如果"完全跳过"风险不可接受(你确实想知道这个 deployment 是否还活着),用 health_check_model 把心跳指向同 endpoint 的便宜模型:

model_list:
  - model_name: gpt-4o
    litellm_params:
      model: openai/gpt-4o
      api_base: https://eastus.openai.azure.com
      api_key: os.environ/AZURE_KEY
    model_info:
      mode: chat
      health_check_model: gpt-4o-mini   # 同 endpoint 上的 mini

前提health_check_model 必须能用同样的 api_base / api_key 访问,且它的可用性能反映原模型可用性。Azure / Bedrock 等同 endpoint 多模型场景非常合适。

效果:单次心跳成本从 ~\(0.001(gpt-4o)→ ~\)0.00003(gpt-4o-mini),单次成本 ÷ 30

2.4 ★★ 用 embedding mode 心跳(单次成本因子)

embedding 调用通常比 chat 调用便宜 1-2 个数量级,且某些 provider 的 embedding 端点稳定性能反映 chat 端点。

model_list:
  - model_name: my-chat-model
    litellm_params:
      model: openai/gpt-4o
    model_info:
      mode: embedding
      health_check_model: text-embedding-3-small

⚠️ 注意:这是个 hack。如果 chat 服务挂了但 embedding 服务还活着,你会得到误报"healthy"。仅推荐对成本极敏感、对探测精度可放松的场景。

2.5 ★ 把通配符模型禁用心跳(扇出因子)

01 §5.4 提到,wildcard openai/* 一次心跳最多触发 3 个 fallback 模型 × 4 次 retry = 12 次真实调用

如果 model_list 里有通配符 deployment,建议直接:

model_list:
  - model_name: openai-anything
    litellm_params:
      model: openai/*
    model_info:
      disable_background_health_check: true   # ★ 别让它自动 ping

通配符模型一般是给用户自由路由用的,自己跳过心跳损失不大。

2.6 ★ 减少 num_retries 的放大(retry 因子)

litellm_settings.num_retries: 3 是给业务请求设的——失败时自动重试,不希望透传错误。但心跳不需要这个语义:失败就是失败,下个周期再试。

目前 LiteLLM 没有"per-call 关闭 num_retries"的官方开关。两个折衷办法:

方案 A:改源码(侵入性小)

litellm/proxy/health_check.py:99-105 改成:

task = run_with_timeout(
    litellm.ahealth_check(
        {**model["litellm_params"], "num_retries": 0},   # ★ 心跳强制 0 重试
        mode=mode,
        prompt=DEFAULT_HEALTH_CHECK_PROMPT,
        input=["test from litellm"],
    ),
    timeout,
)

效果:失败 deployment 单次心跳调用从 4 次 → 1 次。如果你有 5 个不健康 deployment,每次 burst 减少 15 次请求。

方案 B:保持现状

如果你的 deployment 平时都很健康(极少 retry),num_retries 的放大可以忽略,不必动源码。

2.7 ★ 缩短 health_check_timeout(间接降低成本)

model_info:
  health_check_timeout: 20    # 默认 60,缩到 20

收益不是减少请求,而是: - 缓慢/挂死的 deployment 会更快被判失败 - leader pod 的总耗时变短,减少 follower fallback 触发概率(04 多-pod-fallback-放大) - 整体 health check 周期更短,DB / 内存负载更低


3. 常见误区

3.1 ❌ "health_check_details: false 能省钱"

_health_endpoints.py:1657details 只控制 /health API 返回的字段(隐去 messages / api_key / 等),和真实 ping 数量完全无关

改成 false 唯一的好处是 _save_background_health_checks_to_db 写入的 JSON 更小。

3.2 ❌ "把 health_check_interval 设得很大就行"

在 SHARED 模式下,health_check_interval 控制的是 tick 间隔,不是真实 ping 间隔。tick 间隔大了反而响应延迟变长

  • 假设 health_check_interval=300TTL=1800
  • cache 在 T+1800 过期
  • 但要等到下一个 tick(最坏 T+2100,多了 5 分钟空档)才会真正打模型补缓存

建议组合:health_check_interval=30(小)、TTL=1800(大)。

3.3 ❌ "use_shared_health_check: true 就一定走 SHARED 路径"

实际还要 redis_usage_cache != None。后者要求 litellm_settings.cache: truecache_params.type: redis 且 Redis 真的连得上(01 §4)。

经常踩的坑:

表象 实际
YAML 写了 use_shared_health_check: true manager 是否创建还要看 redis_usage_cache
redis=True ping 通了 那是脚本里的连接测试,不代表 litellm.cache.cache 是 RedisCache
启动日志没有 _init_cache 报错 不一定 OK,要看 [HC-DEBUG-E] 类型的明确确认

排查方法见 04 §3.

3.4 ❌ "增加 retry 能让心跳结果更准"

心跳的目的是 判断当前可用性。retry 反而模糊了"瞬时不可用"和"可用"的边界——有些 deployment 第一次失败、第二次成功,本质是部分不健康,retry 后被记成 healthy 反而误导。

把心跳的 num_retries 设 0 是更诚实的做法。


4. 推荐组合

4.1 成本敏感型(推荐生产基线)

目标:把心跳成本压到原来的 1/40 量级。

# proxy_server_config.yaml
litellm_settings:
  cache: true
  cache_params:
    type: redis
    host: os.environ/REDIS_HOST
    port: os.environ/REDIS_PORT
    password: os.environ/REDIS_PASSWORD

general_settings:
  background_health_checks: true
  use_shared_health_check: true
  health_check_interval: 30
  health_check_details: false
# k8s deployment
env:
  - name: DEFAULT_SHARED_HEALTH_CHECK_TTL
    value: "1800"
  - name: DEFAULT_SHARED_HEALTH_CHECK_LOCK_TTL
    value: "180"     # 必须大于一次完整 health check 总耗时,prod 28 模型实测 12-30s
-- DB: 给所有贵模型禁用心跳
UPDATE "LiteLLM_ProxyModelTable"
SET model_info = jsonb_set(coalesce(model_info, '{}'::jsonb),
                           '{disable_background_health_check}', 'true'::jsonb)
WHERE model_name IN (
    'gpt-4o', 'gpt-4-turbo',
    'claude-opus-4-7',
    'gemini-2-5-pro'
);

预期收益(vs 默认配置 28 deployment + 9 Pod + TTL=300,且本 fork 已修复 fallback 放大 bug):

因子 原(修复前 + 默认 TTL) 新(修复 + TTL=1800 + 贵模型跳过) 倍率
频率(/小时) 12 2 × 1/6
deployment 数 28 10(贵模型跳过) × 10/28
Pod 放大 9(fallback bug) 1(已修复) × 1/9
总计 ~3024 / 小时 ~20 / 小时 ÷ ~150

🔧 修复说明:本 fork 已把 follower 抢锁失败后的 sleep 2 + 自打 改成 polling 到 lock_ttl 超时,消除 N×deployment 放大。详见 04 §5

4.2 极简型(无后台心跳)

general_settings:
  background_health_checks: false

故障感知完全靠业务请求自然发现 + Router cooldown 机制。

适用场景: - 流量足够大,每个 deployment 每分钟都有真实请求经过 - 上游 SLA 高,不需要自己额外巡检


5. wildcard 的隐性放大

health_check_helpers.py:36-46 对通配符模型的处理:

if len(cheapest_models) > 1:
    fallback_models = cheapest_models[1:]   # 选 2 个 fallback

model_params["model"] = cheapest_models[0]
model_params["fallbacks"] = fallback_models
model_params["max_tokens"] = 10
await acompletion(**model_params)

acompletion 会按 fallbacks 顺序尝试,每个失败的尝试还会被 num_retries 放大:

flowchart LR
    Start[一次 wildcard 心跳]
    M1[尝试 cheapest_models 0]
    M2[失败 → 尝试 fallback 1]
    M3[失败 → 尝试 fallback 2]

    Start --> M1
    M1 -- 失败 + 3 次 retry --> R1[共 4 次调用]
    M1 -- 成功 --> S1[共 1 次调用]
    R1 --> M2
    M2 -- 失败 + 3 次 retry --> R2[再 4 次]
    M2 -- 成功 --> S2[累计 4+1=5 次]
    R2 --> M3
    M3 -- 任意 + 3 次 retry --> R3[最多再 4 次<br/>累计 12 次]

最坏情况一个 wildcard deployment 一次心跳产生 12 次真实模型调用

最佳建议:通配符模型直接 disable_background_health_check: true。心跳本来就不适合用通配符——首选不一定能反映"用户实际会用到"的子模型可用性。


下一步