Ruby on Rails 框架创始人 DHH 近期尝试利用 AI 代理将 Campfire Once 从 Ruby 重写为 Rust,随后又进行了 Elixir 和 Go 版本的重写。尽管 DHH 本人不愿亲自阅读或编写 Rust 代码,但这一实验揭示了在依赖 AI 生成代码而不进行人工审查时可能产生的深层问题。

非功能性差异与架构权衡
不同语言的重写版本在处理向后兼容性和系统约束上存在显著差异,这些差异往往源于提示词(Prompt)不够具体。例如,Rust 版本并未保持 100% 的向后兼容性,为了简化缓存机制删除了 CSRF 令牌,并放弃 Redis 转而使用进程内队列处理通知。相比之下,Elixir 版本更接近原始 Rails 实现。由于这些架构决策与编程语言本身无关,单纯比较语言优劣变得困难。
代码质量与并发模型缺陷
深入代码层面可以发现多处性能隐患。在 Elixir 版本中,存在单一进程连续处理所有 SQL 查询的情况,这引发了关于高性能编码能力的质疑。而在 Rust 版本中,部分数据库操作采用异步运行时的阻塞模式。由于 Rust 的异步调度并非抢占式,长时间运行的任务(如耗时 100ms 的 SQL 查询)会阻塞同一工作线程上的其他任务。正确的做法应使用异步 I/O 操作以释放线程资源。此外,同步锁在异步运行时中的不当使用(持有时间超过 10ms)也是潜在风险点。
基准测试的误导性
传统的吞吐量基准测试忽略了系统的其他关键属性。Zach Daniels 的测试显示,在高负载下,Rust 版本的新邮件通知交付率仅为 1%,而 Elixir 版本在较低负载(约 1.7k 通知)下实现了 100% 交付。然而,这种对比并不公平:Rust 版本在 6-7k 通知量下仅达到 1% 交付率,说明其瓶颈在于请求数量超出系统承载能力,而非单纯的并发处理能力不足。测试方法本身(如客户端等待响应再发送新消息 vs 恒定速率发送)也影响了结果的可比性。
压力测试下的真实表现
针对闭环压力测试的重新评估显示,在 100 POST/s 的请求速率下,Rust 版本的事件接收率约为 14%,且无 HTTP 错误;而 Elixir 版本的接收率约为 60%,但有 23% 的 HTTP POST 请求超时,最差事件传递延迟接近 180 秒。虽然 Rust 版本丢失了大量消息,但其浏览器客户端会在重连时获取最新消息,从而在很大程度上抵消了错过事件的影响。单一指标无法全面反映用户体验。
根本原因:广播通道容量限制
Rust 版本在高负载下丢失消息的根本原因在于使用了 tokio::sync::broadcast 频道向多个连接客户端播放事件。该频道的特性是受设定容量限制,若接收器处理速度不足,会触发 RecvError::Lagged 错误。原代码中广播容量设置为 256。
- stream_capacity: 256
+ stream_capacity: 16384
将容量增加至 16384 后,在 100 req/s 的压力测试中,Rust 版本的交付率从 14% 提升至 90%。然而,这种修改可能导致更糟糕的后果:它模仿了 Elixir 的行为,允许滞后累积,而不是快速失败并强制客户端重连。作者认为,如果接收者无法及时传递事件,即使 11 秒的延迟也过长。
内存管理与失败模式
Elixir 版本在相同压力测试中内存使用量高达 1.8GB。这引出了一个核心权衡:是丢弃部分信息以避免 OOM(内存溢出)崩溃,还是承受高延迟?无论使用何种语言,开发者都必须考虑系统的失败模式和资源约束。Elixir 并非自动解决所有并发问题的银弹,缺乏界限的系统最终会受到外部资源的限制。
结论
此次重写实验表明,编程本质上仍是权衡的艺术。不能仅凭单一指标(如吞吐量或交付率)做出判断,也不能盲目相信某种语言在并发方面的优越性。构建可靠系统需要批判性思维,明确何时应该让系统失败,以及如何在延迟、吞吐量和可靠性之间找到平衡。AI 生成的代码仍需经过严格的人工审查和优化。





