ngx_mrubyとngx_lua

今年の秋頃に[将来nginxでJavaScriptが書けるようになるという話](http://www.infoworld.com/article/2838008/javascript/nginx-has-big-plans-for-javascript.html)が出ましたが、nginxには元々サードパーティモジュールの中でも有名な[ngx_lua](https://github.com/openresty/lua-nginx-module)があります。また、Advent Calendarを見るに[ngx_mruby](https://github.com/matsumoto-r/ngx_mruby)も盛り上がってきているようです。

そこで両者の比較エントリを書いてみようと思います。

## ngx_lua

まずngx_mrubyについてはこのカレンダーでさんざん語られてるのでngx_luaについて。
ngx_luaは簡単に言うとngx_mrubyと同じようなこと(あるいはそれ以上のこと)がLuaでできるnginxのサードパーティモジュールです。

例えば「Hello, World!」はこんな感じで書けます。

```nginx
location /lua {
    content_by_lua "
        ngx.say('Hello, World!')
    ";
}
```

### ngx_luaで利用できるLua処理系

ngx_luaではLua5.1とLua5.1互換のLuaJITが利用できます。逆に言うとLua5.2以上だとビルドできないので注意しましょう。ngx_luaでLuaJITを利用したい場合は[OpenResty](http://www.openresty.com/)が簡単でオススメです。

## ngx_luaとngx_mrubyのベンチマーク

両者の簡単なベンチマークについては @matsumotory  が既に書いていて[ngx_mrubyの方が高速](http://qiita.com/matsumotory/items/aa30b4925680c2e6b159)です(ところでstaticファイルの配信との比較もありますが、文字列返すだけのベンチマークと比較するのはあまり意味がない気がします)。このベンチマークにはLuaJITとの比較がないので試しに手元で計ってみましたが、それでもngx_mrubyの方が高速でした。mrubyとLuaJITではLuaJITの方が圧倒的に速いので、両者の速度の違いはモジュールの実装に起因するものと考えられます。(ngx_luaはハンドラ内で毎回コルーチンを起動するので多分そのオーバーヘッドが大きい)

## ngx_mrubyとngx_luaでかけられるフック

両者ともにnginxのいろいろな実行フェーズにフックをかけれますが、かけられる数だけならngx_mrubyの方が多いです。(ただ自分で実装しておいてなんですが、post_readとserver_rewriteのフェーズのフックは今思うといらなかった気がします)

 * [ngx_mruby Directives](https://github.com/matsumoto-r/ngx_mruby/wiki/Directives#directives)
 * [ngx_lua Directives](https://github.com/openresty/lua-nginx-module#directives)

## 利用可能な正規表現エンジン

ngx_mrubyとngx_luaとでは利用できる正規表現エンジンに違いがあります。
ngx_mrubyはmrbgems化されているものであれば基本的に利用できますが、PCRE(e.g. [mruby-regexp-pcre](https://github.com/iij/mruby-regexp-pcre))は利用できません。ngx_luaはLua標準の正規表現とPCREが利用できます。

ちなみにngx_mrubyでPCREベースの正規表現を利用できない理由は[こちら](http://bokko.hatenablog.com/entry/2013/10/13/142154)。

## データ共有の仕組み

ngx_luaには`ngx.shared.DICT`のようにデータを共有メモリに保存して
ワーカープロセス間で共有したり、`ngx.ctx`のようにリクエストの処理コンテキスト毎にデータを受け渡すしくみがあります。ngx_mrubyにはまだないようです。

## ngx_luaにあってngx_mrubyにないもの

さてここまで説明した感じだとngx_mrubyとngx_luaにはあまり差がない、あるいは高速な分ngx_mrubyの方が良いと思うかもしれません。実際一般的にはLuaよりmrubyの方が書きやすいという人もいるでしょうし、より高速というだけでも十分対抗できると思います。また現在は基本的な機能の扱いについてはほとんど差がないと言ってもいいでしょう。(足らなかったらmrbgems足せば大体事足りるし)

ただ、昔ngx_luaとGoでRTB広告を配信してた時期にngx_mrubyに足りないと思っていたものが一つあります。それがノンブロッキング処理のサポートです。

通常nginxのようなイベント駆動型のWebサーバはそのアーキテクチャの特性上prefork型のWebサーバと比べて非常に少ないワーカー数で稼働させますが、プログラムがブロッキングするケースが増えてくるとワーカーの数を増やさなければならず段々prefork型のサーバに近づいていってしまい、パフォーマンスが悪くなる問題があります。なので、ノンブロッキング処理は非常に重要です。(`worker_processes`が200のnginxとか見たことある？)

ngx_luaだと`ngx.location.capture`や`ngx.socket.tcp`なんかがそれにあたります。

### ngx.location.capture

`ngx.location.capture`の説明は[このへん](http://qiita.com/kz_takatsu/items/e94805a8e3cc285f9b33#3-4)がわかりやすいですが、簡単に言うとnginxのサブリクエストをノンブロッキングに処理できます。さらに言うとプログラム自体はブロッキング処理的に記述できるというのがミソです。前職の広告配信の例で言うと外部の広告サーバにngx_luaからリクエストを飛ばす際に重宝していた機能です。こんな感じに。(エラー処理とか細かいチューニング設定は省いてます)

```nginx
location /external_ad {
    proxy_pass http://external_ad/;
}

location /ad {
    content_by_lua '
    ...
    res = ngx.location.capture("/external_ad");  
    ...
    ';
}
```

### ngx.socket.tcp

`ngx.socket.tcp`はnginxのノンブロッキングTCPソケットをLuaでラップしたものです。memcachedやMySQL、Redisなんかにつなぎに行くドライバ↓がありますが、これらはすべて`ngx.socket.tcp`を使って書かれています。

 * [lua-resty-memcached](https://github.com/openresty/lua-resty-memcached)
 * [lua-resty-mysql](https://github.com/openresty/lua-resty-mysql)
 * [lua-resty-redis](https://github.com/openresty/lua-resty-redis)

## ngx_mrubyでノンブロッキング処理をサポートするには

これは主に開発者視点の話ですが、ngx_mrubyでノンブロッキング処理をサポートするにはnginxのイベント駆動エンジンにべったりと乗っかる必要があります。だから例えば[mruby-uv](https://github.com/mattn/mruby-uv)は使えません。mrubyのステートマシン上ではなくnginx上のイベントエンジンで処理する必要があるためです。

また、これを実装するにはコルーチンが必須で、mrubyだとmruby-fiberを使う感じになるのかなと思います。ただ、LuaはCからコルーチンを直接操作できる(Matz曰く「心臓が見えている」)のに対して、mrubyの場合fiberがmrbgemsになっててCから直接触るのが難しいのでそのあたりを解決する必要がありそうです。

## まとめ

 * 単純なベンチマークならngx_mrubyの方が速い
 * ノンブロッキングのサポートが重要ならngx_lua(Lua5.1よりLuaJITの方がおすすめ)
 * ngx_mrubyだとmrubyのfiberがLuaに比べてCから操作しにくいので今のままだと難しいかも