预 AlphaSpinel 2026.09.12MIT

Rails 风格的 Web 应用,编译为单个原生二进制文件。

CyberTrain 是一个 Rails 风格的 Web 应用框架,专为 matz 开发的 Ruby AOT 编译器 Spinel 原生编写。你沿用熟悉的词汇(resources、before_action、validates、form_with、脚手架),而 cybertrain build 会把控制器、模型、视图以及框架本身编译成单个原生二进制文件,产物中没有 Rack、没有运行时 gem,也没有 Ruby 解释器。

快速上手

终端 — 任意目录,之后在 blog/shell
gem install cybertrain
cybertrain new blog          # 首次运行时安装 Spinel 2026.09.12(需要几分钟)
cd blog
cybertrain g scaffold article title:string body:text
cybertrain db migrate
cybertrain server            # http://127.0.0.1:3000
cybertrain build             # 生产环境:dist/blog、dist/public/、dist/storage/、dist/tmp/

只有 cybertrain 命令需要 Ruby 3.2+(应用本身从不在 CRuby 上运行),此外还需要 git、make、curl、C 编译器和 SQLite 3 头文件:参见开始之前。设置好 CYBERTRAIN_SECRET_KEY_BASE 后,用 cd dist && ./blog migrate && ./blog 运行构建产物。

  • 服务器HTTP/1.1,无 Rack
  • 运行时无 gem,无解释器
  • 数据库经 Spinel FFI 访问 SQLite
  • CILinux 和 macOS

适合谁,不是什么

面向想要单个原生二进制文件、并且能接受一个年轻项目和 Ruby 子集的 Rails 开发者。

  • 不是 Roundhouse

    Roundhouse 把现有的 Rails 应用转译到 Spinel。CyberTrain 是一个让你直接基于它编写代码的原生框架,而不是兼容层。

  • 不兼容 Rails 或 Rack

    没有 Rack 兼容性,运行时也没有 gem:只能使用 Spinel 自己的、能在其 Ruby 子集下编译的 spin-index 包。

  • 尚不可用于生产

    CyberTrain 处于预 Alpha 阶段。示例博客由 CI 在 Linux 和 macOS 上运行测试。

你编写的代码

沿用 Rails 的词汇,Spinel 的规则全部写明。

控制器、模型、路由和视图读起来就像一个 Rails 应用。凡是 AOT 编译器迫使其有所不同的地方,差异都很小、很明确,并且有文档说明。

  • 路由

    嵌套的 resources 块需要显式接收 mapper:articles.resources :comments。

  • 控制器

    new 要写成 new_action:名为 new 的方法会遮蔽 Klass.new(ctx)。

  • 视图

    开发时模板从磁盘读取,所以修改视图无需重新构建;cybertrain build 会把它们嵌入二进制文件。模板是一套封闭的语法,而不是任意 Ruby。

  • 模型

    class Article 声明验证;列、查找方法和关联都根据 db/schema.rb 生成。

config/routes.rbruby
Cybertrain::Routes.draw do
  root "articles#index"

  # A nested block takes the mapper explicitly (Spinel's instance_eval
  # trampoline only rewires `self` for the outermost block): `articles.resources`.
  resources :articles do |articles|
    articles.resources :comments, only: [:create, :destroy]
  end
end
app/controllers/articles_controller.rbruby
# app/controllers/articles_controller.rb (index/show/edit are as plain as
# Rails' own scaffold; update mirrors create)
class ArticlesController < ApplicationController
  before_action :set_article, only: [:show, :edit, :update, :destroy]

  def new_action   # a `new` method would shadow ArticlesController.new
    @article = Article.new
  end

  def create
    @article = Article.new(article_params)
    if @article.save
      flash[:notice] = "Article was successfully created."
      redirect_to article_path(@article), status: :see_other
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def set_article
    @article = Article.find(params[:id])
  end

  def article_params
    params.require(:article).permit(:title, :body)
  end
end
app/views/comments/_comment.html.erberb
<%# locals: (comment:) %>
<div class="comment">
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>

  <%= button_to "Destroy comment", [@article, comment], method: :delete %>
</div>
app/models/article.rbruby
# app/models/article.rb -- only the first string field gets a default
# presence validation; add anything past that yourself.
class Article
  validates :title, presence: true
  validates :body, presence: true, length: { minimum: 10 }
end

现已可用

你最先会用到的那部分 Rails,为 Spinel 原生编写。

  • 原生 HTTP 服务器

    纯 Ruby 编写的 HTTP/1.1 服务器,每个连接一个绿色线程,请求和响应都带有类型。没有 Rack 层;需要 TLS 时在前面放 nginx 或 Caddy。

    TCPServer#accept

  • 路由与控制器

    resources、嵌套路由、before_action、after_action 和 rescue_from,在构建时生成为纯 Ruby。强参数和“先 flash 再重定向”的用法与 Rails 相同。

    config/routes.rb

  • 会话、flash、CSRF

    HMAC 签名的 cookie 会话、flash 消息和 CSRF 防护默认开启。表单会自动携带令牌;伪造的 POST 请求会得到 403。

    _cybertrain_session

  • 类 ERB 模板

    视图看起来就像 Rails 的 ERB,支持布局、局部视图、form_with 和 link_to。开发时从磁盘读取,所以修改后无需重新构建。cybertrain build 会把它们嵌入。

    app/views/**/*.html.erb

  • SQLite 模型

    验证、回调,以及由外键推导出的关联。通过 Spinel 的 FFI 和连接池访问 SQLite;所有查询都使用参数绑定。

    ffi_func · ffi_lib

  • 迁移与脚手架

    cybertrain generate scaffold 会写出迁移、模型、控制器和五个视图。cybertrain db migrate 先生成代码,再执行迁移、重写 db/schema.rb,然后再次生成。

    db/migrate/*.rb

  • 实时开发循环

    cybertrain server 监视你的代码,在后台重新构建,然后在同一端口和 PID 上重新 exec 新的二进制文件。构建失败时旧服务器继续运行。

    rebuild → stop → execv

  • 自带测试

    用 Cybertrain::Test 编写测试,并通过带 cookie jar 的客户端在进程内驱动整个应用。cybertrain spin test 会编译每个测试并比对其输出。

    test/<name>.rb.expected

框架与 CLI

一个名字,两个部分。

gem install cybertrain 安装的是一个命令,而不是框架。框架是一个 Spinel 包,你的应用依赖它并把它编译进来。这个 gem 提供 cybertrain 命令,它会替你创建应用、安装 Spinel 并运行 Spinel 的工具。

Spinel 包

框架

你的应用所基于的库。

包含什么

HTTP 服务器、路由和控制器、会话与 CSRF 防护、模板解释器、SQLite 模型和迁移,以及 spin run gen 背后的代码生成器。

如何获取

你不需要安装它。你的应用的 spin.toml 指定本仓库的发布标签 v0.2.1,spin lock 会把它拉取到 spin 的缓存中,并在 spin.lock 中锁定提交。不会有任何内容被复制到你的应用中。

在哪里运行

在你的应用内部。Spinel 把它和你的代码一起进行 AOT 编译;它从不在 CRuby 上运行。

在 dist/blog 中

编译在内它是 CyberTrain 中随应用交付的部分。

RubyGem

cybertrain 命令

用来创建、构建和运行你的应用的工具。

包含什么

只有 cybertrain 命令:new、generate scaffold、db migrate、server、build、spin、setup 和 doctor。框架和 Spinel 都不在这个 gem 里。

如何获取

gem install cybertrain。首次使用时,它会把锁定的 Spinel 版本 2026.09.12 构建到 ~/.cybertrain 中,除非你的 PATH 上已经有该版本。

在哪里运行

在你的机器上,运行于 CRuby 3.2 或更新版本。它会替你调用 Spinel 的 spin 工具,你无需亲自调用。

在 dist/blog 中

不交付该二进制文件无需 Ruby、gem 或 Spinel 即可运行。

两者从同一个标签发布,因此版本一致:0.2.1 版的 gem 生成的应用依赖框架 v0.2.1。要把应用迁移到新版本,先更新 gem,把 spin.toml 指向新标签,然后运行 cybertrain spin lock 和 cybertrain spin run gen。

工作原理

元编程只在构建时发生一次。

Spinel 进行 AOT 编译,没有 eval。因此,凡是 Rails 在运行时借助 method_missing 和 define_method 的地方,spin run gen 都会写出你可以阅读和提交的纯 Ruby 代码,只有视图是解释执行的。

CyberTrain 流水线db/schema.rb、config/routes.rb、app/ 和 app/views/ 是构建的输入。spin run gen 把已提交的纯 Ruby 代码写入 gen/。cybertrain build 嵌入 app/views/,并把 gen/ 和框架编译成单个原生二进制文件 dist/blog,它提供 HTTP/1.1 服务,并通过 Spinel 的 FFI 访问 SQLite。在开发环境中,该二进制文件改为从磁盘读取 app/views/,并由一条回路监视源代码:监视、重新构建、停止、execv。你编写的构建时 · spin运行时db/schema.rb表config/routes.rb路由app/控制器、模型spin run gen1gen/纯 Ruby,已提交模型路由 · URL 辅助方法view_assignsrun_callback迁移 · 视图manifest2cybertrain builddist/blog单个原生二进制框架控制器 · 模型模板解释器内嵌的视图HTTP/1.1 服务器SQLite via Spinel FFI4app/views/开发时:从磁盘读取嵌入 dist/blog35开发:监视 → 重建 → 停止 → execv仅限开发环境 · 构建失败时旧服务器继续运行 CyberTrain 流水线db/schema.rb、config/routes.rb、app/ 和 app/views/ 是构建的输入。spin run gen 把已提交的纯 Ruby 代码写入 gen/。cybertrain build 嵌入 app/views/,并把 gen/ 和框架编译成单个原生二进制文件 dist/blog,它提供 HTTP/1.1 服务,并通过 Spinel 的 FFI 访问 SQLite。在开发环境中,该二进制文件改为从磁盘读取 app/views/,并由一条回路监视源代码:监视、重新构建、停止、execv。你编写的db/schema.rb表config/routes.rb路由app/控制器、模型构建时spin run gen1gen/纯 Ruby,已提交模型路由 · URL 辅助方法view_assignsrun_callback迁移 · 视图manifest2cybertrain build运行时dist/blog单个原生二进制框架控制器 · 模型模板解释器内嵌的视图HTTP/1.1 服务器SQLite via Spinel FFI4app/views/开发时:从磁盘读取嵌入 dist/blog3开发:监视 → 重建 → 停止 → execv5仅限开发环境 · 构建失败时旧服务器继续运行
构建步骤仅开发:从磁盘读取 / 开发循环二进制文件编号与下方步骤对应。
  1. 构建时生成

    spin run gen 执行 db/schema.rb 和 config/routes.rb 以获取表和路由数据,并对 app/controllers 和 app/models 做词法扫描(用正则表达式,而不是解析器),找出 @ivar = 赋值、回调名和无参数的 def。它把可读的纯 Ruby 代码写入 gen/,其中包括 gen/views.rb,即内嵌视图表(除非为部署而构建,否则为空)。cybertrain db migrate、server 和 build 会替你运行它。

  2. 提交 gen/

    它要提交进仓库,而不是写进 gitignore,因为 spin build 和 spin test 没有可以先执行生成的钩子。CI 会重新运行 spin run gen,一旦有差异就失败,因此过时的生成代码无法合并。cybertrain spin run gen -- --check 会报告过时的文件。

  3. 解释执行视图

    app/views/**/*.html.erb 在请求时被解析,并由树遍历解释器执行。开发时,引擎从磁盘读取 app/views/,模板变化时重新解析,因此修改视图无需重新构建。cybertrain build 会把每个模板都嵌入二进制文件(spin run gen -- --embed-views);生产环境的二进制文件只从这张表渲染,每个模板只解析一次。Spinel 没有 eval,所以这是一套封闭的语法,而不是真正的 Ruby。

  4. 用绿色线程提供服务,用 SQLite 存储

    纯 Ruby 编写的 HTTP/1.1 服务器为每个连接运行一个 Spinel 绿色线程。模型通过 ffi_func/ffi_lib 访问 SQLite,使用 4 个连接的连接池(WAL、约 5 秒的 busy 处理器、开启外键)。SPINEL_WORKERS 默认为 1。

  5. 开发时重新构建

    cybertrain server 先运行 spin run gen,再运行 spin run blog(你的应用名)。开发时,二进制文件每半秒轮询一次 app/**/*.rb、config/**/*.rb、db/schema.rb 和 gen/**/*.rb(不包括视图和 gen/views.rb)。一旦有变化,就在后台运行 spin run gen && spin build blog;成功后会设置重启标志(与外部 kill -HUP 设置的是同一个标志),服务器随即停止监听、处理完未完成的请求,并在同一端口和 PID 上 execv 新的二进制文件。构建失败时,服务器继续用旧的二进制文件提供服务,并以横幅形式显示编译器输出。当 cybertrain build 持有 tmp/cybertrain-build.lock 时,会跳过重新构建。生产环境中这些都不会加载。

终端 — blog/shell
cybertrain spin run gen   # 读取 db/schema.rb + config/routes.rb,扫描 app/,写入 gen/
cybertrain build          # 内嵌 app/views 的 dist/blog,以及 dist/public/、dist/storage/、dist/tmp/

Rails 与 CyberTrain

词汇相同,规则不同,并且都已写明。

AOT 编译器会改变一些习惯。下面是你最先会遇到的几条。

Rails 与 CyberTrain 的差异(精简版)
主题RailsCyberTrain
01控制台rails console没有控制台——Spinel 没有 eval
02重新加载修改代码,正在运行的应用会自动加载Ruby 代码需要重新构建;开发时 cybertrain server 会替你完成(重新构建、停止、execv)
03new 动作def newdef new_action(new 会遮蔽 Klass.new(ctx))
04回调before_action { do_thing }(隐式 self)before_action { |c| c.do_thing }——无法对存储的块使用 instance_exec,因此回调需要显式接收控制器/记录
05嵌套路由resources :posts do resources :comments endresources :posts do |posts| posts.resources :comments end——嵌套块需要显式接收 mapper
06会话会话可以保存任何经 marshal 序列化的对象会话值只能是 String,使用 HMAC 签名的 cookie
07数据库多种数据库适配器仅支持 SQLite,通过 FFI 访问
08测试minitest / RSpecCybertrain::Test——基于反射的测试运行器无法在 AOT 编译下工作

此表并不完整。

README 中的完整表格

性能

在一个核心上,处理的请求是 Rails 的 2.6 到 12.2 倍。

对比 examples/blog 与用 Rails 8.1 编写的同一个博客:视图、路由、模型和 SQLite 模式都相同。两者都以生产模式运行,固定在同一台机器的相同 CPU 核心上,由 wrk 施加相同的负载。第二个核心对 Rails 的帮助更大:Puma 会多开一个完整的进程,而 CyberTrain 的第二个工作线程只带来 9% 到 58% 的提升,因此在两个核心上差距为 2.0 到 5.5 倍。

16 个连接下的每秒请求数CyberTrainRails 8.1
  • GET /articles列表,30 篇文章

    一个核心

    CyberTrain: 2,641

    Rails: 468

    5.6×对比 Rails

    两个核心

    CyberTrain: 3,289

    Rails: 1,036

    3.2×对比 Rails

  • GET /articles/1一篇带 10 条评论和 12 个表单的文章

    一个核心

    CyberTrain: 2,450

    Rails: 201

    12.2×对比 Rails

    两个核心

    CyberTrain: 2,662

    Rails: 486

    5.5×对比 Rails

  • POST /articles/2/comments一次带验证的插入和一次重定向

    一个核心

    CyberTrain: 1,507

    Rails: 390

    3.9×对比 Rails

    两个核心

    CyberTrain: 2,379

    Rails: 870

    2.7×对比 Rails

  • GET /style.csspublic/ 中的静态文件

    一个核心

    CyberTrain: 13,300

    Rails: 5,127

    2.6×对比 Rails

    两个核心

    CyberTrain: 20,589

    Rails: 10,311

    2.0×对比 Rails

负载运行后的内存(越少越好)CyberTrainRails 8.1
  • 系统 malloc默认分配器

    一个核心

    CyberTrain: 10.6 MB

    Rails: 125.9 MB

    11.9×更少

    两个核心

    CyberTrain: 12.0 MB

    Rails: 331.4 MB

    27.6×更少

  • jemalloc两者都预加载

    一个核心

    CyberTrain: 13.3 MB

    Rails: 119.9 MB

    9.0×更少

    两个核心

    CyberTrain: 15.1 MB

    Rails: 313.2 MB

    20.7×更少

服务器所有进程的常驻内存(从 /proc 读取的 RSS)之和,在全部负载运行结束后读取。Rails 在一个核心上运行一个 3 线程的 Puma 进程,在两个核心上运行两个工作进程。RSS 会把 Puma 工作进程共享的页面按每个进程各算一次;按在进程间分摊共享页面的 PSS 计算,两个核心上的 Rails 占用 249.1 MB(使用 jemalloc 时为 222.5 MB),而 CyberTrain 为 10.2 MB(12.3 MB)。

  • 负载下的内存

    10.6 MB

    Rails:126 MB(一个 Puma 进程,3 个线程)。

  • 从启动到首个响应

    0.02 秒

    Rails:1.3 秒。

  • 服务器上需要的东西

    2.5 MB

    一个二进制文件。Rails:Ruby、69 个 gem 和应用,共 109 MB。

  • 代价:一次构建

    3 分钟

    用 cybertrain build 编译二进制文件所需的时间。Rails 直接运行源代码。

预热之后三次 10 秒测量的中位数,环境为一台 4 vCPU 的云虚拟机,wrk 运行在服务器不使用的两个核心上:用 Spinel 2026.09.12 构建的 CyberTrain 0.2.1,对比运行在 Ruby 3.4.6(启用 YJIT)和 Puma 8.0 上的 Rails 8.1.4(一个核心时为一个 3 线程的进程,两个核心时为两个)。同一行的条形共用一个刻度。每次只发一个请求时,文章页面的响应时间为 0.4 ms,Rails 为 4.7 ms。两者都使用 jemalloc 时(Rails 的生产镜像默认预加载它;CyberTrain 只需在 spin.toml 中加一行),一个核心上为 2.8 到 14.6 倍。差距的一部分来自 Rails 默认会做而 CyberTrain 不做的工作:加密会话 Cookie,并为每个表单签发 CSRF 令牌。方法、全部数据以及如何重新运行,见 docs/benchmark.md。

开始之前

你的机器需要准备什么。

Ruby
3.2+,带 RubyGems,仅供 cybertrain 命令使用。应用本身从不在 CRuby 上运行。
Spinel
2026.09.12,未在其他版本上测试。第一个需要它的命令会把它构建到 ~/.cybertrain 中(需要几分钟,仅一次);如果 PATH 上已有该版本的 spinel,则直接使用。
工具链
cc 和 make(Spinel 会把每个程序编译为 C),以及用于构建 Spinel 的 git 和 curl。SQLite 3 头文件和库:Debian/Ubuntu 上为 libsqlite3-dev,macOS 上随 Xcode 命令行工具提供。OpenSSL 头文件是可选的。
状态
预 Alpha 阶段,版本 0.2.1,MIT 许可。示例博客由 CI 在 Linux 和 macOS 上运行测试。

cybertrain doctor 会检查这些工具和头文件以及 Spinel 的安装情况,缺少某项时会打印适用于你的平台的安装命令。cybertrain setup 按需安装 Spinel,并打印用于自行运行 spin 的 PATH 设置行;cybertrain spin ARGS... 则无需该设置即可运行它。