1991年爱立信实验室里,几个工程师为电话交换机写了个容错系统。33年后,这套系统被用来跑Ruby on Rails——而且只需要5MB内存。

这就是QuickBEAM干的事。它把完整的Rails应用塞进Erlang虚拟机(BEAM),让JavaScript代码获得电信级的故障恢复能力。同一套代码,Node.js上跑45MB,崩溃即全挂;BEAM上跑5MB,单核崩溃秒级重启,用户无感知。

开发者Sam Ruby上周放出了演示:一个标准Rails博客应用,Turbo Streams实时同步,Action Cable协议原封不动。浏览器开两个标签页,创建文章,两边同时刷新。这部分没什么新鲜。

新鲜的是后台。故意制造一个JavaScript运行时崩溃,Node.js方案会拖垮整个进程——连接断开、状态丢失、从头重启。BEAM方案里,OTP supervisor只重启那个出问题的运行时,其他运行时继续服务,WebSocket连接保持,Turbo从断点续传。

应用层代码完全没变。Ruby源码一模一样。变的是执行目标。

浏览器用BroadcastChannel,Node.js用WebSocket,BEAM用Erlang进程组

浏览器用BroadcastChannel,Node.js用WebSocket,BEAM用Erlang进程组

三端同步机制各不相同,但用户看到的效果完全一致。浏览器端靠BroadcastChannel API实现跨标签页通信;Node.js端跑独立的WebSocket服务器;BEAM端则调用Erlang的进程组(process groups)——默认支持分布式,零外部依赖。

这套架构的核心是QuickBEAM,一个专为Erlang虚拟机设计的JavaScript运行时。它基于QuickJS-NG构建,后者是一个轻量级、标准兼容的JavaScript引擎。与Node.js把V8嵌进C++进程不同,QuickBEAM把QuickJS嵌进Erlang NIF(原生接口函数),让JavaScript直接调用BEAM的并发和容错原语。

每个QuickBEAM运行时都是一个轻量级隔离体。Elixir可以启动一个运行时池,把请求轮询分发到各个运行时,每个跑在独立的操作系统线程上。一个崩溃,OTP supervisor重启它,其他的继续干活。这就是Erlang为电信交换机设计的"let it crash"哲学——崩溃不可怕,恢复速度才是关键。

QuickJS不是V8,没有JIT编译器,纯计算性能确实慢一些。但Web应用的主要开销是I/O:数据库查询、模板渲染、HTTP响应。这部分瓶颈上,两者差距可以忽略。换来的收益很实在:运行时体积从45MB压到5MB,启动时间压到亚毫秒级,外加整个OTP生态系统的调用权。

Elixir管它最擅长的,JavaScript管业务逻辑

Elixir管它最擅长的,JavaScript管业务逻辑

完整的调用链长这样:

浏览器(Turbo、Stimulus、Action Cable客户端)↕ HTTP + WebSocket ↕ Bandit(Elixir HTTP服务器)↕ Plug路由 ↕ QuickBEAM(JavaScript运行时池)↕ Beam.callSync ↕ Elixir(:pg广播、SQLite NIF、OTP监控)

JavaScript层处理请求路由、控制器逻辑、视图渲染、模型操作——和Node.js上跑的代码完全一致。Elixir层专注它三十年来打磨的领域:并发调度、故障容错、分布式消息。

浏览器端跑的是真正的@hotwired/turbo-rails npm包,Rails官方同款Action Cable客户端。自定义元素连接到/cable端点,说着标准的Action Cable线协议。

服务器端由Elixir实现协议的另一头:Bandit处理WebSocket升级,:pg管理订阅关系,广播消息以Action Cable的JSON格式投递。当模型的broadcasts_to回调触发时,数据通过Beam.callSync('__broadcast'...)从JavaScript跨入Elixir,完成全网广播。

5MB vs 45MB,不只是数字游戏

5MB vs 45MB,不只是数字游戏

Node.js的内存 footprint 来自V8的复杂架构:优化编译器、垃圾回收器、隐藏类系统。这些对计算密集型任务有价值,对典型的CRUD Web应用是过度配置。QuickJS走另一条路:解释执行、引用计数GC、极简运行时——刚好够跑标准JavaScript,不多一分。

亚毫秒级启动意味着运行时可以被频繁创建和销毁。Elixir的进程池可以按负载动态伸缩,冷启动惩罚几乎为零。对比Node.js的启动延迟,这对Serverless场景和边缘部署是结构性优势。

OTP supervision tree是另一张牌。Node.js的cluster模块能做多进程,但进程间状态隔离、故障恢复、代码热更新都需要自己搭。BEAM把这些内建在虚拟机层面,supervisor策略可配置:one-for-one重启单个失败子进程,rest-for-one级联重启依赖链,one-for-all全量重启——按业务场景选。

分布式是默认能力。:pg模块的进程组跨节点透明,两台BEAM节点自动形成集群,广播消息自然扩散。Node.js要做到同等效果,需要引入Redis或专门的消息队列。

Rails社区会买账吗

Rails社区会买账吗

技术债是真实的。Rails生态大量依赖C扩展(nokogiri、pg、redis等),这些需要重新编译为BEAM的NIF或移植为纯JavaScript实现。QuickBEAM目前用SQLite NIF演示,关系型数据库的完整支持还在路上。

但演示本身已经说明可行性边界。Turbo、Stimulus、Action Cable——Rails 7的核心前端栈——全数跑通。这不是"理论上可以",是`npx github:ruby2js/juntos --demo blog`一行命令能复现的东西。

Sam Ruby的背景值得注意。他是Ruby核心团队成员,JSON gem维护者,W3C HTML工作组成员。这个项目的权威性不来自营销话术,来自作者对两个生态的深度理解。

Ruby语言的发明人松本行弘(Matz)说过,Ruby的设计目标是"让程序员快乐"。Elixir的发明人José Valim是Rails核心团队出身,设计目标是"高并发、高容错、低延迟"。QuickBEAM像是两条路径的交汇点——用Rails的写法,拿Elixir的运行时特性。

一个悬而未决的问题:如果5MB运行时就能跑Rails,且自带分布式和容错,Rails 8会不会官方支持BEAM作为部署目标?DHH(Rails创始人)近年对#nobuild、默认SQLite的激进转向,说明核心团队对"简化部署"有执念。QuickBEAM的方向与此暗合。

但DHH也是出了名的技术审美固执。他会把Rails绑上BEAM这艘船,还是坚持Ruby解释器的正统性?