feat: Snapshot-Offset 限额策略替换 BUFFER 盲猜
核心改动:不再用 BUFFER 猜滞后窗口消耗,而是用同步时点快照 直接计算累计限额。 - BUFFER=300000 → SMALL_BUFFER=50000(仅覆盖非消耗性跳降) - get_effective_limit: limit = B0 - SMALL_BUFFER,不依赖 current_usage - 同步循环:写 balance_at_sync + usage_at_sync 替代 remaining/stable_limit - 突发检测/低额度判断:读 balance_at_sync 替代 remaining - volc_consume.py: vls.BUFFER → vls.SMALL_BUFFER - debug/debug_volc_sync.py: 同步更新 - docs/volc-quota-notes.md: 新增 §7.6 v6 设计文档,更新各节口径
This commit is contained in:
+98
-44
@@ -7,9 +7,8 @@
|
||||
> - 2026-08-12 对话澄清(扣费口径、余额 API 滞后、限额方案演进)
|
||||
> - 2026-08-12 生产日志观察(deepseek-v4-flash-ga-260731)
|
||||
>
|
||||
> 代码现状:v5 = 恢复 v2 骨架(`stable_limit = usage + balance − BUFFER`),2026-08-12 修正;
|
||||
> v4(balance−BUFFER 冻结快照)当天实施后即发现误拦已回退,见 §7.3/§7.5。
|
||||
> 工作区在 HEAD `c6c1224` 之上含 v5 修正。
|
||||
> 代码现状:v6 = Snapshot-Offset(`limit = balance_at_sync + usage_at_sync − SMALL_BUFFER`),
|
||||
> 2026-08-12 修改;BUFFER 盲猜法已废弃,见 §7.6。
|
||||
|
||||
---
|
||||
|
||||
@@ -89,9 +88,9 @@
|
||||
- 观察结论:AvailableAmount 不随今日消耗实时递减(至少滞后 20 万级,上限未知)。
|
||||
- **后果**:一切「余额随消耗实时递减」的公式(v2 的 `usage + balance` 池恒定假设)在
|
||||
滞后下失效——usage 涨而 balance 不动 → 池膨胀 → 限额虚高 → 放行到扣费区。
|
||||
- 对策方向:以**最近同步快照**的余额为当日免费池上限(本地 usage 实时累计撞线),
|
||||
快照频率由同步间隔(30min 常规 + 低余额 5/10min + 突发阈值触发)保障;
|
||||
滞后窗口内的消耗用 BUFFER 覆盖(见 §7.3)。
|
||||
- 最终对策:Snapshot-Offset(§7.6),不再依赖 `usage + balance` 守恒,而是用同步时点
|
||||
的快照 `balance_at_sync + usage_at_sync = B0` 直接计算累计限额,两次同步间常数,
|
||||
实时 usage 自然抵消滞后窗口内的消耗。
|
||||
|
||||
---
|
||||
|
||||
@@ -106,19 +105,18 @@ Set Redis limit:dynamic:volcengine:remaining:deepseek-v4-flash-ga-260731 = 1,258
|
||||
📊 [Usage] deepseek-v4-flash-ga-260731:ba0383d3: +38083 (Today: 1,279,904 | Rem: 1,558,307 | Limit: 2,000,000)
|
||||
```
|
||||
|
||||
字段口径:
|
||||
字段口径:
|
||||
|
||||
| 字段 | 含义 | 计算 |
|
||||
|---|---|---|
|
||||
| Bal | 火山 API 余额(ListResourcePackages AvailableAmount 之和) | 同步快照 |
|
||||
| Rem(Checking 行) | 入 Redis 的剩余 | Bal − BUFFER |
|
||||
| Rem(Usage 行) | 展示用余额 | Redis remaining + BUFFER = Bal(展示与 API 一致) |
|
||||
| Bal | 火山 API 余额(ListResourcePackages AvailableAmount 之和) | 同步快照 |
|
||||
| BalAtSync | 同步时写入 Redis 的余额快照 | = Bal |
|
||||
| UsageAtSync | 同步时写入 Redis 的累计消耗 | Redis `daily_usage` 求和 |
|
||||
| Today | 本地今日采集量 | Redis `daily_usage` 逐请求累加 |
|
||||
| Limit | 生效限额 | min(2M, stable_limit) |
|
||||
| StableLimit(v2) | 动态限额 | usage + balance − BUFFER(随 usage 膨胀,见 §7.1) |
|
||||
| Limit | 生效限额 | min(2M, balance_at_sync + usage_at_sync − SMALL_BUFFER) |
|
||||
|
||||
**Today > Rem 的原因**:Today 是单日流量,Rem 是存量余额快照,两套口径本就不该比较。
|
||||
火山网页「今日采集量 1,696,135/2,000,000」对应 Today 与 2M 配额,与余额无关。
|
||||
**Today > BalAtSync 的原因**:Today 是单日流量,BalAtSync 是存量余额快照,两套口径本就不该比较。
|
||||
火山网页「今日采集量 1,696,135/2,000,000」对应 Today 与 2M 配额,与余额无关。
|
||||
|
||||
**扣费结论(2026-08-12 15:09 实测修正)**:14:59:51 同步 U=1,696,135 + B=596,869,
|
||||
守恒反推今日初始免费池 B0 = 2,293,004 → 今日消耗 1,696,135 < B0 → **当日无扣费**。
|
||||
@@ -134,14 +132,14 @@ Set Redis limit:dynamic:volcengine:remaining:deepseek-v4-flash-ga-260731 = 1,258
|
||||
| key | 读写方 | 说明 |
|
||||
|---|---|---|
|
||||
| `daily_usage:{北京日期}:{model}:{hash}` | LiteLLM 成功回调 INCR + 消耗任务 INCR | 当日采集量,TTL 48h,北京 00:00 滚动 |
|
||||
| `limit:dynamic:volcengine:remaining:{model}` | 守护线程写 | 余额 − BUFFER(同步频率驱动、展示) |
|
||||
| `limit:dynamic:volcengine:stable_limit:{model}` | 守护线程写 | 稳定限额(v5:usage+balance−BUFFER) |
|
||||
| `limit:dynamic:volcengine:balance_at_sync:{model}` | 守护线程写 | 同步时点 API AvailableAmount 快照 |
|
||||
| `limit:dynamic:volcengine:usage_at_sync:{model}` | 守护线程写 | 同步时点 Redis 累计消耗快照 |
|
||||
| `consume:last_run:{日期}` | 消耗任务 | 23:00 当日去重,TTL 48h |
|
||||
|
||||
### 6.2 关键日志
|
||||
|
||||
- `🚫 [PRE-BLOCK] {model} daily limit reached ({usage}+{reserve}/{limit})`:请求前闸门 429
|
||||
- `📊 [Usage] {model}:{hash}: +{tokens} (Today: X | Rem: Bal | Limit: Y)`:成功记账
|
||||
- `📊 [Usage] {model}:{hash}: +{tokens} (Today: X | BalAtSync: Y | Limit: Z)`:成功记账
|
||||
|
||||
---
|
||||
|
||||
@@ -187,40 +185,96 @@ stable_limit = max(0, balance − BUFFER) # ❌ 错误:把「剩余余
|
||||
> 30 万 BUFFER → 若滞后窗口内流量大,v2 会多放行到虚高 stable;可调大(如 50 万,env)
|
||||
- **部署**:v5 已改(工作区),需重启 litellm 容器加载;重启在生产主机由用户执行
|
||||
|
||||
### 7.5 v5(2026-08-12 修正,当前):恢复 v2 骨架
|
||||
### 7.5 v5(2026-08-12 修正,已废弃):恢复 v2 骨架
|
||||
|
||||
```
|
||||
stable_limit = max(0, usage + balance − BUFFER)
|
||||
```
|
||||
|
||||
- 与 v2(§7.1)公式相同,区别在**论证与认识**:v2 数学正确(B 收敛后 = B0 − U),
|
||||
不需要冻结快照;v4 的「剩余当上限」误拦由「usage + balance 桥接」天然避免
|
||||
- 实测验证(14:59:51 同步 + 15:09:51 生产日志):U=1,696,135、B=596,869 →
|
||||
stable=1,993,004 → 今日剩余可用 296,869(账上 59.7 万用到剩 30 万 BUFFER)✓
|
||||
- 防扣费语义不变:usage 撞 stable = 剩余恰为 BUFFER 时停,扣费结构性杜绝;
|
||||
balance < BUFFER(U=0)→ stable=0 → 全拦
|
||||
- 保留:`get_effective_limit` 兜底链(stable → FALLBACK_LIMIT 90 万)、pre-block reserve、
|
||||
软踢/恢复、低余额加密同步(600s/300s)
|
||||
- 与 v2(§7.1)公式相同,区别在**论证与认识**:v2 数学正确(B 收敛后 = B0 − U),
|
||||
不需要冻结快照;v4 的「剩余当上限」误拦由「usage + balance 桥接」天然避免
|
||||
- 实测验证(14:59:51 同步 + 15:09:51 生产日志):U=1,696,135、B=596,869 →
|
||||
stable=1,993,004 → 今日剩余可用 296,869(账上 59.7 万用到剩 30 万 BUFFER)✓
|
||||
- 防扣费语义不变:usage 撞 stable = 剩余恰为 BUFFER 时停,扣费结构性杜绝;
|
||||
balance < BUFFER(U=0)→ stable=0 → 全拦
|
||||
- **已废弃原因**:BUFFER 盲猜滞后窗口消耗,高强度击穿低强度浪费。被 §7.6 Snapshot-Offset 替代。
|
||||
|
||||
### 7.6 v6(Snapshot-Offset,2026-08-12 当前):宝塔防扣费
|
||||
|
||||
```
|
||||
limit = balance_at_sync + usage_at_sync − SMALL_BUFFER(5万)
|
||||
= B0 − SMALL_BUFFER
|
||||
```
|
||||
|
||||
#### 核心思路
|
||||
|
||||
不再猜滞后消耗,而是利用一个事实:**同步时点的 balance + usage = 当日初始免费池 B0(B0 是常数,一天内不变)**。
|
||||
|
||||
**limit 是累计限额(从 0 到上限),不是剩余余额**。`_check_request_block` 用 `current_usage + reserve >= limit` 撞线,`current_usage` 自然抵消了滞后窗口内的增量消耗。
|
||||
|
||||
#### 为什么不需要 BUFFER
|
||||
|
||||
旧方案用 BUFFER 猜「滞后窗口内消耗了多少」——猜不准。新方案直接记录同步时点的 `balance_at_sync` 和 `usage_at_sync`,闸门读实时 `current_usage` 去减,精确抵消:
|
||||
|
||||
```
|
||||
# 旧:盲猜
|
||||
limit = usage + balance - BUFFER(300k)
|
||||
|
||||
# 新:精确抵消
|
||||
limit = balance_at_sync + usage_at_sync - SMALL_BUFFER(5k)
|
||||
```
|
||||
|
||||
**SMALL_BUFFER(5 万)只覆盖非消耗性余额跳降**(资源包过期、API 未结算残差),不是消耗量。
|
||||
|
||||
#### 不变量
|
||||
|
||||
**永远不扣费**:`usage + reserve >= B0 - SMALL_BUFFER` 撞线时,剩余实时余额 = `B0 - usage` = `B0 - (B0 - SMALL_BUFFER - reserve)` = `SMALL_BUFFER + reserve` > 0。扣费在余额归零时才发生,结构性杜绝。
|
||||
|
||||
#### 执行流程
|
||||
|
||||
**同步线程**(每 5~30min / 突发触发):
|
||||
1. 拉 API → 取到 balance
|
||||
2. 读 Redis 取 current_usage
|
||||
3. 写入 `balance_at_sync` = balance, `usage_at_sync` = current_usage (TTL 24h)
|
||||
4. 不再写入 `remaining`/`stable_limit` 旧键
|
||||
|
||||
**请求闸门**:
|
||||
1. 读 `balance_at_sync` + `usage_at_sync`
|
||||
2. `limit = balance_at_sync + usage_at_sync - SMALL_BUFFER`
|
||||
3. `usage + reserve >= limit` → 429
|
||||
|
||||
#### 与旧方案对比
|
||||
|
||||
| 场景 | 旧(BUFFER 30万) | 新(Snapshot-Offset) |
|
||||
|---|---|---|
|
||||
| 高强度消耗 | 滞后窗口内消耗 > 30 万 → 击穿扣费 | 实时 usage 撞 B0 线,不放行 |
|
||||
| 低强度消耗 | 浪费 30 万额度(实际只用了 5 万) | 只留 5 万 SMALL_BUFFER |
|
||||
| 新包到账 | 下次同步 stable limit 跳升 | 下次同步 B0 跳升,一致 |
|
||||
| 包过期 | 余额跳降,limit 偏高至下次同步 | 同左,但 SMALL_BUFFER=5 万风险窗口小 |
|
||||
|
||||
#### 遗留风险
|
||||
|
||||
1. **同步时点 B0 本身滞后**:API 的 AvailableAmount 在同步时点就已经滞后了 L0。这个量无法通过抵消消除。唯一缓解:同步频率(低余额 5min) + 突发检测。
|
||||
2. **多包同时过期 > SMALL_BUFFER**:概率低,无法绝对防御。可调大 SMALL_BUFFER 或监控包过期时间。
|
||||
|
||||
---
|
||||
|
||||
## 8. 排障速查(今日结论汇总)
|
||||
## 8. 排障速查(今日结论汇总)
|
||||
|
||||
1. 看到 Today > Rem 不是异常,先分清口径(§5);判扣费用「守恒初始池」(U+B 反推),
|
||||
不要用任意时点快照(13:36 滞后快照曾误判「超余额扣费」,实际无扣费)
|
||||
2. 余额显示不动 ≠ 余额充足,是 API 滞后;同步快照 + BUFFER 兜滞后窗口
|
||||
3. 判断「还能免费采多少」:`(usage + balance − BUFFER) − 今日已采`(v5 已落地)
|
||||
4. 消耗任务 23:00 直连 Ark 绕过 LiteLLM 闸门(防自杀),其自身 clamp:`plan ≤ 2M − usage`、
|
||||
`plan ≤ 余额快照 − 已烧 − BUFFER`,停止条件三闭环
|
||||
5. 余额滞后收敛后守恒成立(B = B0 − U);30 天过期蒸发是余额下跌的另一来源,
|
||||
与消耗扣减区分(如 08-12 余额 1,558,307 → 596,869,其中约 50 万为滞后修正 + 过期)
|
||||
1. 看到 Today > BalAtSync 不是异常,先分清口径(§5);判扣费用「守恒初始池」(BalAtSync+UsageAtSync 反推),
|
||||
不要用任意时点快照(13:36 滞后快照曾误判「超余额扣费」,实际无扣费)
|
||||
2. 余额显示不动 ≠ 余额充足,是 API 滞后;Snapshot-Offset 不再依赖 BUFFER 覆盖,而是直接用
|
||||
`balance_at_sync + usage_at_sync - current_usage` 实时抵消
|
||||
3. 判断「还能免费采多少」:`balance_at_sync + usage_at_sync - SMALL_BUFFER - 今日已采`(v6)
|
||||
4. 消耗任务 23:00 直连 Ark 绕过 LiteLLM 闸门(防自杀),其自身 clamp:`plan ≤ 2M − usage`、
|
||||
`plan ≤ 余额快照 − SMALL_BUFFER`,停止条件三闭环
|
||||
5. 余额滞后不改变 B0 守恒;30 天过期蒸发是余额下跌的另一来源,
|
||||
与消耗扣减区分(如 08-12 余额 1,558,307 → 596,869,其中约 50 万为滞后修正 + 过期)## 9. 待确认/风险清单
|
||||
|
||||
---
|
||||
|
||||
## 9. 待确认/风险清单
|
||||
|
||||
- [ ] 余额 API 滞后上限实测(决定 BUFFER 是否调大,现 30 万;13:36 实测高估 ~50 万)
|
||||
- [x] v4 误拦发现与回退(2026-08-12 15:09 生产日志实证,§7.3)
|
||||
- [x] v5 恢复 v2 骨架(2026-08-12,§7.5;待生产主机重启生效)
|
||||
- [ ] 消耗任务口径是否同步收紧(可选:其停止条件与闸门同一把尺子已满足)
|
||||
- [ ] 空窗期(0:00–返还到账)行为观察:v5 下余额红线自动收紧,确认无异常误杀
|
||||
- [ ] 余额 API 滞后上限实测(决定是否需要进一步压缩 SMALL_BUFFER 或保留 5 万)
|
||||
- [x] v4 误拦发现与回退(2026-08-12 15:09 生产日志实证,§7.3)
|
||||
- [x] v5 废弃(2026-08-12,§7.5;被 v6 Snapshot-Offset 替代)
|
||||
- [x] v6 实施(2026-08-12,§7.6;待生产主机重启生效)
|
||||
- [ ] 消耗任务口径同步收紧(已改为 vls.SMALL_BUFFER,确认与闸门一致)
|
||||
- [ ] 空窗期(0:00–返还到账)行为观察:v6 下 B0 自动收紧,确认无异常误杀
|
||||
- [ ] 数据库泄漏:512 亿 token 消耗记录(08-12 一天 deepseek-v4 烧了 512 亿)导致 Redis 内存膨胀;触发监控/清理机制
|
||||
Reference in New Issue
Block a user