配额窗口机制重写后,这批夜间脚本是如何拖垮你的交互流量的
一个稳定运行了十个月的 cron 任务是你系统中最危险的任务,因为其中的代码没变,你的代码也没变,唯一改变的是别人发布的、你们团队没人读的发行说明(release notes)中的一句话。那个每晚在 00:05 UTC 启动、在十分钟内清空工作队列并重新进入休眠状态的每晚 embedding 刷新任务,曾是教科书般的范例。它通过在用户醒来前占用几分钟刚重置的每分钟配额,并在当天的剩余时间内保持在每日配额之内,从而与白天的交互式流量和谐共存。接着,服务商重写了每日窗口的核算方式,保持了每分钟窗口不变,并保留了你客户端测试的所有签名。批处理任务依然稳定运行。但每晚 00:13 UTC,交互界面开始返回 429 错误。团队一直在追查一个根本不存在的、本应在一周后才开始的上游维护窗口。
Bug 从不在你的代码里。Bug 在于“每日限制”不再是前一天的意思,而你的调度程序固定在了一个与旧定义对齐的时钟边界上。这篇文章讨论的是:速率限制核算(rate-limit accounting)作为一种服务商可以在不破坏任何签名的情况下修改的契约;两个独立正确的调度是如何组合成拒绝服务模式的;以及如何通过架构手段让 cron 任务不再是一个连接到别人时钟上的定时炸弹。
每日配额不是一个名词,而是一种核算策略
当服务商告诉你每日限制是一百万 token 时,他们并没有告诉你“每日”是什么意思。他们只是给了你一个数字。这个数字是一个预算;而决定预算何时补充的策略是一个独立的对象,文档通常会对后者含糊其辞。两个服务商都可以说“每天一百万 token”,其中一个在 00:00 UTC 重置计数器,而另一个则跟踪一个滚动二十四小时桶(rolling twenty-four-hour bucket),你昨天 03:00 消耗的每个 token 都会在今天 03:00 释放。你的应用程序看到的是同样的标题数字。你的批处理任务看到的则是两个完全不同的世界。
在 2026 年,两家服务商都发布了关于分钟级 RPM 和 TPM 上限的滚动窗口文档——这是六十秒持续滑动的核算,随着时间的推移,每个请求留下的足迹都会变小。而策略模糊性存在于每日上限、每组织支出上限和突发允许量中。有些是按日历对齐的,有些是滚动的,有些曾经是前者并悄悄变成了后者。少数是根据信用池扣费的,信用池本身按日历补充,比如 Anthropic 在 2026 年 6 月转向独立的每月额度,用于自动化工作流,其扣减独立于交互式用户看到的对话窗口配额。
请将该单位视为一种策略,而不是一个名词。契约不是那个每日数字。契约是每日数字加上决定其何时补充的规则,而你只能控制其中之一。
为什么两个正确的时钟会组成一个错误的系统
你的 cron 任务和服务商的配额计数器各自运行起来都很合理。你的任务在 00:05 UTC 启动,因为那时上游数据分区保证已经结清,更早启动会导致刷新不完整。服务商的分钟窗口在每分钟的开始时重置,因为对齐挂钟分钟是运行全球速率限制计量层成本最低的方式。每个时钟独立地产生了一种有用的行为。但它们的设计初衷并非为了协同工作。
这种组合之所以能奏效,仅仅是因为两个时钟对“每日”的定义相同。当服务商将每日核算收紧为滚动二十四小时桶,同时保持分钟窗口与日历对齐时,这两个时钟就不再对称了。批处理任务慷慨地消耗了分钟配额,并在运行的前八分钟内耗尽了滚动的每日分配量,因为所有这些 token 现在都是针对接下来的二十四小时扣除的,而不是针对今天。从 00:13 开始,共享该密钥的所有其他工作负载的每日桶都是空的。八分钟的批处理任务并没有消耗八分钟的容量;它消耗了二十四小时的容量。
429 错误也没有告诉你任何有用的信息。它们指向同步界面,因为那是仍在尝试发出请求的界面。批处理任务报告运行成功,因为它在桶排空之前已经完成了工作。两个了解真相的系统在“这是谁的问题”上产生了分歧:批处理任务说“我没问题”,交互界面说“上游坏了”,没有任何一个仪表板能将这两者视为同一个事件的两个侧面。
隐式默认故障模式及其变体
这个模式有一系列变体。你的 cron 任务与供应商的重置时间之间的冲突只是更广泛 bug 类别的一个实例:其行为取决于供应商有权更改的值,而你的客户端从未明确声明该值,你的测试也从未验证过它。隐式的 max_tokens 默认值、静默的分词器(tokenizer)升级、自动调整复杂性分类器的自动路由——这些都具有相同的特征。你签署了一份以 JSON 请求和响应为表现形式的合同,但该合同的行为取决于隐藏在背后的规则,而供应商会按照自己的节奏修订这些规则。
速率限制计费是该家族中最危险的成员,因为它与时间紧密相关。响应字段的变化会在下一个请求到达时立即显现。而配额窗口计算方式的变化,只有当利用旧语义的工作负载模式再次运行时才会显现。如果你的批处理任务每晚运行,而供应商在周二发布了更改,那么在 UTC 时间周三 00:05 之前你都可能没意识到。第一晚看起来像随机噪声。第二晚看起来像是个规律。到第四晚,轮值运维(on-call)开始排查上游维护窗口,因为症状看起来就是那样,而实际的根本原因可能是发布日志(release-notes)文件中的一句话,将其归类为“改进的每日使用平滑处理”。
- https://devtk.ai/en/blog/ai-api-rate-limits-comparison-2026/
- https://www.respan.ai/articles/openai-api-rate-limits
- https://www.aiusagebar.com/blog/claude-rate-limits
- https://tokenmix.ai/blog/ai-api-rate-limits-guide
- https://cloud.google.com/blog/products/ai-machine-learning/learn-how-to-handle-429-resource-exhaustion-errors-in-your-llms
- https://aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/
- https://portkey.ai/blog/rate-limiting-for-llm-applications/
- https://www.hivenet.com/post/llm-rate-limiting-quotas
- https://www.truefoundry.com/blog/rate-limiting-ai-agents-preventing-llm-api-exhaustion
- https://www.clawpulse.org/blog/llm-api-rate-limiting-best-practices-avoid-429-errors-and-save-40-on-costs
- https://platform.sh/blog/increasing-cron-jitter
