2日前にmrubyの画像変換mrbgem下さいとつぶやいて、その後2日以内にgdとMagick++のmrbgemが開発されるこのスピード感がmruby。 https://t.co/RzTZJsX9vP https://t.co/4YkJ2PwISQ
— MATSUMOTO, Ryosuke (@matsumotory) 2015, 4月 25
というように、mrubyで画像変換してみたいなーと呟くとあっというまに画像変換mrbgemが開発されてしまうmrubyですが、そのままでは呟いた当事者としてなんだか申し訳ないので、ngx_mrubyを使ってサクっと画像変換サーバができないか試してみました。
結論としては、簡単なものはサクっとできてしましました。以降では作り方を簡単に紹介します。
こういうのが数分でできてしまうのがmod_mrubyやngx_mruby、さらにはtrusterdの良さですねぇ。
ngx_mruby + mruby-mrmagickを使う
mruby-mrmagickを使ったのは、パッと見た感じREADMEが書かれていて手っ取り早く試せそうだったからです。今ではmruby-gdなどもあって、今後色々と増えていくでしょう。
以下設定の手順です。
1. build_config.rb
に以下のリンク設定を追記
conf.gem :github => 'kjunichi/mruby-mrmagick'
この設定を追記した上でngx_mrubyをビルドします。
2. nginx.conf
にRubyスクリプトフック設定を追加
location /image {
mruby_content_handler build/nginx/html/resize.rb cache;
}
上記のように、/image
にアクセスがあったら画像変換するスクリプトをフックするようにnginxに設定します。
3. build/nginx/html/resize.rb
実装
r = Nginx::Request.new if File.extname(r.filename) == ".jpeg" type = r.var.arg_type if r.var.is_args == "?" if type == "half" new_file = r.filename + "_" + type + ".jpeg" img = Mrmagick::ImageList.new(r.filename) unless File.exist? new_file new_img = img.scale(0.5) new_img.write(new_file) end r.uri = r.uri + "_" + type + ".jpeg" end end Nginx.return Nginx::DECLINED
実際のスクリプトは上記のようにしました。単純に、URLクエリパラメータを解析して、type=half
だったら縦横それぞれ半分にリサイズするという処理です。
また、すでに半分にした画像が生成されていたら、再度リサイズせずにすでにあるリサイズ済みの画像を返します。
これで、
curl -O http://127.0.0.1/image/matsumotory.jpeg
とか
curl -O http://127.0.0.1/image/matsumotory.jpeg?type=half
とかすると、無事リサイズされた画像が生成された上で、レスポンスでも縦横半分になった画像が表示されました。
参考ベンチマーク
といわけで例のごとくとりあえずベンチマークもとってみました。
リサイズ機能無しでオリジナル画像アクセス
$ ab -c 5 -n 10000 http://127.0.0.1:58080/image/matsumotory.jpeg Document Path: /image/matsumotory.jpeg Document Length: 15350 bytes Complete requests: 10000 Failed requests: 0 Total transferred: 155878192 bytes HTML transferred: 153507955 bytes Requests per second: 8339.72 [#/sec] (mean)
リサイズ機能ありでオリジナル画像アクセス
$ ab -c 5 -n 10000 http://127.0.0.1:58080/image/matsumotory.jpeg Document Path: /image/matsumotory.jpeg Document Length: 15350 bytes Complete requests: 10000 Failed requests: 0 Total transferred: 155870000 bytes HTML transferred: 153500000 bytes Requests per second: 8052.35 [#/sec] (mean)
リサイズ機能を入れるとオリジナル画像アクセスでも約3%の性能劣化しました。Rubyのフックとその中の条件分離処理のためだろうと思います。とはいえ性能劣化は軽微です。
リサイズ機能ありでリサイズ画像アクセス
$ ab -c 5 -n 10000 http://127.0.0.1:58080/image/matsumotory.jpeg?type=half Document Path: /image/matsumotory.jpeg?type=half Document Length: 5117 bytes Complete requests: 10000 Failed requests: 0 Total transferred: 53530000 bytes HTML transferred: 51170000 bytes Requests per second: 7327.31 [#/sec] (mean)
最初の一回は画像がリサイズされて新たなファイルとして保存され、その後は保存されたファイルを返すので、全体としてRubyでその辺りの判定を行っていてもそんなにガツンとは遅くなっていないようです。
後は、nginx上で画像変換することによるCPUバウンドの処理をどうするか、また、ファイル変換等による処理のブロックをどうするかといった課題はありますが、今回の目的であるサクッと作ってみるという目的は果たせました。
まとめ
ということで、ngx_mrubyと画像変換mrbgemを使って簡単な画像変換サーバを実装してみました。こういう処理をRubyで簡単にサクッと書けてしまうのがngx_mrubyやmod_mrubyの楽しさだと思います。また、Trusterdでも同様の処理ができるので、HTTP/2な画像変換サーバも簡単にできそうです。
今後ペパボではこういった技術を調査しながらも、メリットデメリットを検証しつつ積極的に取り組んでいく予定です。こういった取り組みを行う事で、Rubyやmrubyの全社的な技術を底上げしたり、mrbgemを作れるスキルをつけることでWebサーバ(HTTP/2含む)を自由に拡張できるようになると良いですね。
また今後も所謂Middleware as Code的なお話を続けていく予定ですので、どうぞよろしくお願いします。