人間とウェブの未来

「ウェブの歴史は人類の歴史の繰り返し」という観点から色々勉強しています。

ngx_mrubyを使った簡単な画像変換サーバを数分で実装してみた

というように、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的なお話を続けていく予定ですので、どうぞよろしくお願いします。