徽章失效与 23,000 个特性的故事

2023 年 10 月 26 日 · Tobias Bieniek 代表 crates.io 团队

大约在 2023 年 10 月中旬,crates.io 团队收到了一位用户的通知,称他们 crate 的 shields.io 徽章停止工作了。问题报告者非常热心地对问题进行了调试,发现 shields.io 发送到 crates.io 的 API 请求很可能是问题所在。以下是原始问题的引用:

这个 crate 大量使用了特性标志,这导致 API 的响应载荷变得非常大。

显然,这个特定 crate 的 API 响应已经超过了 20 MB,shields.io 对此不太满意。有趣的是,这个 crate 当时只发布了 9 个版本。但是,只有 9 个已发布版本,怎么会达到 20 MB 呢?

正如上面的引用已经提到的,这个 crate 使用了特性……非常多的特性……将近 23,000 个!😱

什么样的 crate 需要这么多特性?嗯,这个 crate 为基于 Rust 的 Web 应用提供了 SVG 图标……它为每个图标使用一个特性,以便最终的 WebAssembly 捆绑包的载荷大小保持较小。

乍一看,这似乎没有什么问题。从 crate 作者的角度来看,这似乎是合理的做法,cargo 和 crates.io 都没有对此发出任何警告。不幸的是,一些内部机制对如此高的特性数量并不太满意……

crate 作者已经发现的第一个问题是:crates.io 的 API 响应变得非~~~常~~~大。另一个问题是,crates.io API 当前没有对已发布版本的列表进行分页。显然,改变这一点是一个破坏性变更,所以我们的团队一直不太愿意改变 API 在这方面的行为,尽管这种情况表明我们很可能需要在近期解决这个问题。

下一个问题是,这个 crate 的索引文件也变得很大。仅 9 个已发布版本,它就包含了 11 MB 的数据。就像 crates.io API 一样,包索引文件格式当前也没有内置的分页功能。

现在你可能会问,为什么包索引和 cargo 需要知道特性?简单的答案是:为了依赖解析。特性可以启用可选依赖项,因此当使用依赖项的特性时,它可能会影响依赖解析。我们最初的想法是至少可以从索引文件中删除所有空的特性声明(例如 foo = []),但 cargo 团队告诉我们,cargo 也依赖于这些声明在那里可用,因此出于向后兼容性的原因,这不是一个选项。

好的一面是,目前大多数 Rust 用户使用的 cargo 版本默认使用了稀疏包索引,它只下载实际使用的包的索引文件。换句话说:只有使用这个图标 crate 的用户才需要为下载所有元数据付出代价。从另一方面来说,这意味着仍然使用基于 Git 的索引的用户都在为这一个使用了 23,000 个特性的 crate 付费。

那么,我们接下来怎么办?🤔

虽然我们认为支持如此大量的特性在概念上是一个有效需求,但以 crates.io 和 cargo 当前的实现细节,我们无法支持它。在分析了单个 crate 拥有如此多特性所带来的所有这些下游影响后,我们意识到我们需要在 crates.io 上实施某种形式的限制,以防止系统崩溃。

现在是重点:2023 年 10 月 16 日,crates.io 团队部署了一项变更,将新发布 crate/版本的特性数量限制在 300 个。

……暂时如此,至少直到我们找到解决上述问题的方法为止。

我们知道有少数 crate 出于正当理由也拥有超过 300 个特性,并且我们已经给予了它们适当的豁免,但我们还是希望大家注意我们当前系统的这些限制。

我们也邀请大家参与寻找解决上述问题的方法。讨论想法的最佳地点是 crates.io Zulip 流,一旦想法更成熟一些,就会转化为 RFC

最后,我们要感谢 Charles Edward Gagnon 让我们意识到这个问题。我们也要重申,这个问题不应该归咎于作者及其 crate。在开发 crate 时很难了解这些 crates.io 的实现细节,所以如果非要说有责任,那就是我们 crates.io 团队没有更早地设置这些限制。总之,现在有了,你们都知道原因了!👋