DHH,Ruby on Rails 的创造者,让 AI 把 Campfire Once 从 Rails 重写成 Rust。
按原文说法,他自己不想读,也不想写 Rust,于是让机器代劳。之后又有了 Elixir 和 Go 版本。
听上去像一条 AI 编程的完美广告:大神动嘴,模型动手,多语言重写一键完成。
但开发者 Piotr Sarnacki 拆开一看,发现这事没那么爽。
Rust 版在高压测试里,新消息通知送达率一度只有 1%。
更刺激的是,把配置从 256 改到 16384,送达率又能从约 14% 拉到约 90%。
这不是爽文吗?
作者却泼了盆冷水:这个版本可能更糟。因为事件送达延迟从 11 秒飙到 130 秒以上。系统没有快速失败,反而把用户拖进漫长等待。
一句话:AI 能替你写代码,但不能替你决定系统该怎么失败。
你的 prompt 太模糊,AI 就替你抛硬币
老实讲,这个实验最有意思的地方,不在 Rust,也不在 Elixir。
它像一个显微镜,照出了很多人用 AI agent 时的真实状态:需求只写一半,细节全靠模型脑补。
原文提到一个很狠的判断:
if your prompt is not specific enough, many decisions are a coin flip.
翻译过来就是:如果你的提示不够具体,很多决定就是抛硬币。
软件工程里,大量问题根本没有唯一答案。
要不要保持向后兼容?
更在意延迟,还是吞吐?
高负载下能吃多少内存?
崩溃之后,丢掉新内容通知能不能接受?
你可能觉得这些都不重要。可只要你不写清楚,LLM 就会在实现时悄悄替你回答。
Rust 版就是这么干的。
它没有保持 100% 向后兼容,比如去掉了 CSRF token,让缓存更容易。它还放弃了 Redis,改用进程内队列处理通知。
Elixir 版则更接近 Rails 原版。
你看,表面上是“Rust 重写”和“Elixir 重写”的对比。实际上,很多差异根本不是语言造成的。
AI 听到的是“用某种语言重写”。但真正影响结果的,是那些没被说出口的约束。
这点我挺认同。很多团队以为 AI 差在智商,其实先差在需求。项目经理给一句模糊口号,工程师都会跑偏,何况模型。
不读代码,坑不会自动消失
接下来更尴尬。
有人发现,Elixir 版里出现了单进程顺序处理所有 SQL 查询。连读请求也不能并发。这个抱怨很合理。
DHH 似乎觉得,这会让 Elixir 看起来不太体面:AI 没能写出高性能代码。
但作者转头看了 Rust 版,发现也不轻松。
Rust 版里,一些数据库操作并不是 async。
问题在于,Rust 的 async runtime 采用协作式调度。一个任务不让出线程,同一个 worker 上的其他任务就只能干等。
如果一条 SQL 查询要等 100ms,你还用阻塞方式卡在那里,其他任务就会陪跑。
锁也一样。
async 环境里,更稳妥的做法是使用 async 锁。若用同步锁,还拿在手里 10ms,同线程上的任务都会等它,而它自己并没干活。
所以作者说得很直白:编程没有被“解决”,你仍然需要知道自己在做什么。
这里有个细节值得咂摸。
DHH 是顶级工程师。原系统也不是凭空冒出来的玩具。评论区甚至有人说,这已经是 LLM 的理想场景,因为 90% 的思考早已由原系统开发者完成。
连这种局都能暴露问题,普通项目凭什么觉得可以闭眼交付?
说实话,AI 可以帮你跳过敲代码的环节,但跳不过系统运行时的报复。
服务器不会因为你没读代码,就对你温柔一点。
1% 送达率,打脸的可能不是 Rust
基准测试这段,是全文最像连续剧的部分。
最初,Zach Daniels 测了高负载下的新帖通知送达率。结果发现,Rust 版在重压下只有 1% 成功送达。
这数字太难看了。
Elixir 版呢?
在约 1.7k 通知里做到 100% 送达。Rust 版面对的是 6-7k 通知,只送出 1%。
于是,Elixir 社区有人迅速得出结论:Elixir 并发更强,Rust 协作调度不行。
但作者提醒,先别急着开香槟。
DHH 和 Zach 的测试都是 closed-loop。
简单说,就是 N 个客户端收到响应后,立刻发下一条。它测的是系统单位时间能吞多少请求。
现实世界未必长这样。
真实压力常常来自大量用户同时行动,不会等前一个人完成。
所以作者改用恒定到达率重测。
100 POST/s 下,Rust 客户端收到约 14% 事件,Elixir 约 60%。
看起来 Elixir 还是赢。
可再看另一面:这个流量下,Rust 没有 HTTP 错误。Elixir 约 23% 的 HTTP POST 请求超时。
最糟的事件送达延迟接近 180 秒。
这就有意思了。
Rust 在事件滞后时,会断开客户端。浏览器重连后会拉取最新消息,丢失事件的影响被大幅削弱。
Elixir 则可能让用户等一条新消息等上几分钟。
哪个体验更好?
是后台悄悄重连,还是盯着屏幕等 3 分钟?
单一指标真的会骗人。
送达率很重要,但错误率、延迟、内存、用户感知,也都在系统里投票。
一行配置救回 90%,但作者说这更糟
真正的反转在这里。
Rust 版为什么丢消息?
它用了 tokio::sync::broadcast 通道,把事件广播给多个连接。这个通道有容量上限。接收端处理不过来,就会出现 RecvError::Lagged。
重写版把容量设为 256。
Elixir 那边,GenServer 负责事件投递,默认邮箱没有上限。
于是作者改了一行:
- stream_capacity: 256
+ stream_capacity: 16384
在 100 req/s 下,事件送达率从约 14% 提到约 90%。
如果只看这张表,Rust 似乎反超了。
但作者说,这个版本反而更差。
原因是,它把“快速失败”变成了“慢慢积压”。
容量拉大后,pMAX 事件送达延迟从 11 秒升到 130 秒以上,行为开始接近 Elixir。
个人觉得,这里点破了工程里一个很残酷的真相:能送达,不等于值得等。
对实时聊天来说,11 秒已经太长。用户要的是新消息,不是考古。
如果接收方来不及消化事件,让它断开重连,重新拉取最新消息,可能比硬撑着排队更合理。
这就是失败设计。
更妙的是,Elixir 也不是天然免疫。
作者提到,在 100 req/s 压测中,Elixir 版内存跑到 1.8GB。
问题就变成了:你宁愿丢一些消息,还是宁愿被 OOM 杀掉?
没有魔法。
Elixir 很擅长并发,写并发通常更容易,但它也要付出内存、速度和控制力的代价。rustler 的存在,本身就说明问题。
任何语言都要面对失败模式和边界条件。
你把边界留白,系统就会自己找一个边界。通常是更贵的那种。
AI 没有解决编程,它只是把思考外包的账单提前寄来
这件事最容易引发争吵,因为每个人都想站队。
Rust 粉可以说,一行配置就能改善。
Elixir 粉可以说,默认行为更稳。
AI 粉可以说,这只是早期问题。
但作者真正想说的,不在语言优劣。
他反复强调几件事:你仍要批判性思考;你要知道自己在做什么;不要基于单一指标下结论;想做可靠系统,必须知道何时失败;基准测试很难。
这些句子放在 AI 时代,突然变得有点刺耳。
过去,代码写错了,至少是人在写。
现在,很多人希望 AI 生成、AI 翻译、AI 优化,最后自己只负责点确认。
评论区有条观点很到位:如果你给团队不完整需求,结果可能也差不多。
AI 只是把这个问题放大了。
它更快,更自信,也更不会主动追问你:这个系统到底能不能丢消息?
老实讲,我并不反对 AI 写代码。它确实能省下大量机械劳动。
但这个案例提醒的是另一种风险:我们把“生成代码”误认成“完成系统”。
代码只是表面。
系统包括兼容策略、队列边界、背压、超时、重连、内存、失败语义,以及用户到底能忍多久。
这些东西,prompt 里没写,模型就会自己补。
补得看起来像那么回事,跑起来才知道代价。
所以,真正被这次实验戳破的,是“不用读代码也没关系”的幻觉。
如果连读代码都嫌麻烦,谁来确认系统什么时候该失败,用户什么时候该重连,内存什么时候该报警?
【锐评】:AI 能替你敲代码,但替不了你决定系统什么时候该体面地崩。
参考链接:
https://itsallaboutthebit.com/i-am-sorry-but-you-still-have-to-think/