一个徽章坏了,顺带扯出了 2.3 万个 feature

2023 年 10 月中旬,crates.io 团队收到一封用户反馈:他们维护的某个 crate,放在 README 里的 shields.io 徽章不显示了。

这件事乍看毫不起眼。shields.io 徽章时不时出问题很正常,原因五花八门。但这次的问题链,一旦顺着拉下去,牵出的东西让整个 crates.io 团队都没想到。

本文基于 Rust 官方博客 2023 年 10 月 26 日发布的《A tale of broken badges and 23,000 features》整理撰写,作者为 Tobias Bieniek,代表 crates.io 团队。

原文地址


一个徽章,一条 20 MB 的 API 响应

这位用户在提 Issue 时已经自己做了初步定位,发现问题的根源在 shields.iocrates.io 发出的那个 API 请求:

这个 crate 大量使用了 feature flag,导致 API 响应的体积极度膨胀。

具体来说,shields.io 调用的是 crates.io/api/v1/crates/{crate_name} 接口------这个接口会一次性返回该 crate 所有已发布版本的完整元数据,包括每个版本的全部 feature 列表。

问题是,这个 crate 叫做 icondata,它的 API 响应已经超过了 20 MBshields.io 拿到这个响应之后直接超时,徽章自然也就坏了。

20 MB 是什么概念?这个 crate 当时一共才发布了 9 个版本


9 个版本,为什么会有 20 MB?

答案是:这个 crate 有将近 2.3 万个 Cargo feature。

icondata 是一个为 Rust 网页应用提供 SVG 图标的库。它把每一个图标都对应设计成一个独立的 Cargo feature,这样用户在编译时只需要启用自己用到的图标,最终打包出的 WebAssembly 产物就不会把成千上万个用不到的图标都塞进去。

从 crate 作者的角度看,这个设计完全合理。SVG 图标库本来就有成千上万个图标,按需引入是最自然的做法,Cargo 的 feature 机制也正是为此而生。cargo 不会报错,crates.io 也不会给出任何警告。

但没有人告诉这个 crate 的作者:2.3 万个 feature,已经把 crates.io 的某些内部机制压垮了。


问题一:API 响应永远不分页

crates.io/api/v1/crates/{name} 接口有一个长期的设计问题:它返回所有版本的完整数据,没有分页

对于只有几个版本、每个版本 feature 寥寥无几的普通 crate,这不是问题。但 icondata 有 2.3 万个 feature,哪怕只有 9 个版本,每次 API 调用都需要把 9 × 2.3 万条 feature 数据一并吐出来,响应体积随之爆炸。

团队知道分页是正确的方向,但这是一个破坏性变更------已有无数工具和集成依赖当前的响应结构。这个问题被搁置了很久,直到这次事件才让团队意识到必须提上日程。


问题二:索引文件也在膨胀

crates.io 的稀疏索引(sparse index)是另一个受害者。

Cargo 在解析依赖时,需要从索引中获取每个 crate 的元数据。索引文件中存储的内容包括该 crate 每个版本的依赖和 feature 列表。对于普通 crate,这个文件很小,Cargo 可以快速拉取和解析。

icondata 的索引文件因为包含 2.3 万个 feature 的重复记录,体积同样极为庞大。每次有用户或 CI 环境需要解析这个 crate 的依赖时,都需要下载和处理这个异常大的文件。


问题三:数据库查询承压

crates.io 的后端使用 PostgreSQL 存储 crate 的元数据。feature 数据以关联记录的形式存储在数据库表中,查询某个 crate 的完整信息时,需要通过 JOIN 把这些记录拼回来。

2.3 万条 feature 记录的存在,让原本为普通规模设计的查询开始出现性能问题。这类查询对绝大多数 crate 可以在毫秒级完成,但遇到 icondata 这种异常情况,耗时会急剧上升。


团队的应对

面对这次事件,crates.io 团队采取了几项措施。

立即生效的限制:新发布的 crate 版本,feature 数量上限为 300 个。 这一限制已记录在 Cargo 的官方文档中。对于有特殊需求的 crate,团队表示会逐案评估是否给予豁免。

这个数字的选择有一定的工程判断成分:300 个 feature 对绝大多数用例来说绰绰有余,同时也能将 API 响应体积和数据库查询压力控制在可接受的范围内。

API 分页的问题,团队明确表示将被列为近期必须解决的工作,这次事件提供了足够的推动力。


一个值得深想的细节

这件事有一个值得单独拿出来说的侧面:整个过程中,icondata 的作者没有做任何"错误"的事。

用 feature 来控制编译粒度,是 Rust 生态里成熟的做法。cargo 文档里有这个模式,众多知名 crate 也都在用。没有任何工具链工具会在你的 feature 数量超过某个阈值时发出警告,crates.io 在接受发布时也不会提示任何异常。

问题出在基础设施的隐性假设上:API 的设计者从未预料到会有 crate 拥有数万个 feature;数据库查询的设计者也没有为这种极端情况预留余量。这类问题不到被触发,几乎不可能在事前被发现。

对于写基础设施代码的人来说,这是一个标准的"边界条件"教训:系统在正常范围内运行良好,在正常范围之外,假设就开始逐一失效。


如果你现在就想用 v0 只包含需要的图标......

icondata 最终将自己拆分为多个子 crate,每个子 crate 对应一个图标库(如 Bootstrap Icons、Remix Icons 等),每个子 crate 各自包含该库的图标 feature。这样每个 crate 的 feature 数量就控制在了合理范围内,同时保留了按需引入的设计意图。

这是一个在遭遇系统限制之后的工程权衡:不改变设计理念,但改变实现的边界划分。


小结

一个坏掉的 shields.io 徽章,揭开了 crates.io 在超出预期规模时的多个薄弱点:无分页的 API、膨胀的索引文件、未能为极端情况设计的数据库查询。

团队的修复是务实的:先设一个上限防止同样问题再次发生,再逐步处理更深层的架构问题。现在,Cargo 的文档里白纸黑字写着:每个 crate 最多 300 个 feature,特殊情况逐案审批。

这条规则的背后,是 2.3 万个 SVG 图标和一个坏掉的版本徽章。


相关推荐
止语Lab24 分钟前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活24 分钟前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困30 分钟前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn30 分钟前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
AI小白Lin31 分钟前
33 个 AI 专家全票通过?那问题才刚开始
人工智能·架构
用户2986985301436 分钟前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户772831049084039 分钟前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金1 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛1 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构