• 44
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

今年の秋頃に将来nginxでJavaScriptが書けるようになるという話が出ましたが、nginxには元々サードパーティモジュールの中でも有名なngx_luaがあります。また、Advent Calendarを見るにngx_mrubyも盛り上がってきているようです。

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

ngx_lua

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

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

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が簡単でオススメです。

ngx_luaとngx_mrubyのベンチマーク

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

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

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

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

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

ちなみにngx_mrubyでPCREベースの正規表現を利用できない理由はこちら

データ共有の仕組み

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.capturengx.socket.tcpなんかがそれにあたります。

ngx.location.capture

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

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を使って書かれています。

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

これは主に開発者視点の話ですが、ngx_mrubyでノンブロッキング処理をサポートするにはnginxのイベント駆動エンジンにべったりと乗っかる必要があります。だから例えば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から操作しにくいので今のままだと難しいかも