背景
どうやらmrubyがはやっているらしく、素朴にRailsないのかな?と思っていたが、mrubyのcoreは
- requireがない
- データベースにアクセスできない
- 正規表現が使えない
というものだった。組み込み向けなので当たり前と言えば当たり前なのだけど。
ところが先日、アメミヤさんの ISUCON2015で仕込んでいたネタ(h2o + mruby)の供養をする記事 というのを読んで、これを改造すれば少しだけORMっぽいことができるのでは、と思ってやってみた。
注意点
実用性はない。
成果物
やったこと
- mruby-activerecordというgemを用意
- app以下にアプリケーションを作成
- アメミヤさんのISUCON用h2oをOSXでビルドできるように修正
mruby-activerecord
Rubyのものに名前を合わせた。
- ActiveRecord::Base
- attributesなどを提供
- ActiveRecord::Relation
- 最低限のallとwhereを提供
- ActiveRecord::Schema
- スキーマ定義からモデルを作成
例えば User.where('name = ?', 'hoge') を実行すると ActiveRecord::Relation が返ってきて、中身を評価するときにSQLを実行する、というような感じ。
app以下
app/models とか app/controllers など、何となくそれっぽい名前にした。
データベースはYAMLではなくrbで、establish_connectionを提供するようにしている。
mruby-r3のルーティングは app/config/routes.rb の中に置いた。
app/app.rbで全てをrequireしていて、h2oからはapp/app.rbを読み込む。
ビルドの修正
OSXでHomebrew環境のとき、ライブラリのパスを指定してやる必要があり、mrbgem.rakeやCMakeLists.txtを修正した。以下のような感じでmysql_configやpcre-configを呼び出す。
CMakeはautoconfよりもわかりやすいなあと思った。
+ FIND_PROGRAM(MYSQL_CONFIG mysql_config)
+ EXEC_PROGRAM(${MYSQL_CONFIG} ARGS --libs OUTPUT_VARIABLE MYSQL_LIBRARY_DIRS)
+ STRING(REGEX REPLACE "-L([^ ]*)( .*)?" "\\1" MYSQL_LIBRARY_DIRS "${MYSQL_LIBRARY_DIRS}")
+ LINK_DIRECTORIES(${MYSQL_LIBRARY_DIRS})
あとmruby-r3がr3のmasterに追従していなかったのでpullreq投げた。
※ mruby-r3のOSX対策はマージされていない。
https://github.com/firewood/mruby-r3/commit/e2c69a01f16b9ffa29c81b8f118439afd17b7e6b
を参照のこと。
データベースの準備
サンプルではmysql上にsampleデータベースのusersというテーブルがあるのが前提。以下で作れる。
このRailsで作ったschema.dbをmrubyでそのまま利用している。
$ git clone https://github.com/firewood/rails_tutorial2.git
$ cd rails_tutorial2
$ git checkout mysql
$ vi config/database.yml (※ 任意のエディタで)
$ bundle install
$ ./bin/rake db:migrate
$ ./bin/rake db:seed
ビルド
CMakeなど、h2oがビルドできる状態が前提。
$ git clone https://github.com/firewood/h2o.git
$ cd h2o
$ cmake -DWITH_MRUBY=ON .
$ make h2o
起動 (h2o)
このサンプルではポート8000でlistenする。(ベンチマークかけると死んだりする...)
$ export MYSQL_USERNAME=<mysql username>
$ export MYSQL_PASSWORD=<mysql password>
$ ./h2o -c app/app.conf
起動 (mirb)
$ ./mruby/host/bin/mirb
mirb - Embeddable Interactive Ruby Shell
> require 'app/models/user.rb'
=> true
> require 'app/config/database.rb'
=> true
> require 'app/db/schema.rb'
=> true
> User.all
=> #<ActiveRecord::Relation [#<User {"name"=>"Hoge", ...
ベンチマーク
Core i7 3.4GHzの仮想環境下のLinux。
RailsはUnicornでワーカープロセス4、mrubyはh2oでワーカースレッド4、wrkで4スレッドで同時100アクセスを指定。
DBアクセスありは User.all を発行。なしは静的なJSONを返すだけ。
Rails DBアクセスあり 800 reqs/sec
Rails DBアクセスなし 1400 reqs/sec
mruby DBアクセスあり 2000 reqs/sec
mruby DBアクセスなし 80000 reqs/sec
mrubyはサーバのワーカースレッドと一緒に初期化されるので、応答性が良い。
ちなみにmruby-r3をやめて、callメソッド内で単純な文字列を返すだけにすると120K reqs/secくらい出るが、何個かオブジェクトを生成するだけですぐに半分以下の速度に落ちる。
なので本来の目的(アプリケーションの前段など補助的な使い方)が良さそう。
感想
ちょっとだけRailsのソースを読んだりした。
車輪の再発明楽しい。