動的サムネイル生成
サムネイルは、ユーザアップロード画像を扱うアプリではほぼ間違いなく使われると思う。
サムネイルの生成には、先に作っとくか、後で作るかの2つの方式があるように思う。(気が向いた時とかナシとすると)
先に作っとく
サムネイルを先に作る。 つまりは画像アップロードをキータイミングとして画像処理をかけ、 参照するときには静的な画像を見る方法。
メリットは参照に処理を挟まないので高速に動作することが期待できること。 しかしデメリットとして後から画像サイズを変更しようとすると、画像処理が全部やり直しになる。 また、近年ではCDNを使えば静的画像の参照が早かろうがあまり効果が無いことが多い。
後で作る
そこで参照側からパラメーターを与えて、リクエストが来た時に画像処理をかける方法をみてみる。
一見参照するたびに画像生成するのは効率が悪すぎる印象があるけど、 その分ちょくちょくサムネイルサイズを変更できるし、なんなら1pxずつ大きさの違うサムネイルも生成できて自由度が大幅に広がる。 また、CDNでキャッシュすればデメリットになる画像生成のタイミングも大幅に減らすことができる。
Railsで画像生成
RailsでのファイルアップローダーといえばPaperclipやCarrierwaveなんかが人気だ。
しかし、これらは先に作る方式でサムネイルを生成している。
あとから生成する方式をとっているアップローダだとRefileがある。
しかしRefileの場合、
- 一旦画像をまるごとコピーする。
- コピーに画像処理をする
- 処理した画像を返す
という処理を画像アクセスが来る度にRailsで行っている。 minimagickの使い方も毎回mogrifyを呼んでいて微妙だ。convert一発なら画像処理とコピーが同時に行えるし高速化オプションを付けることもできる。
Refileの実装で実測してみると、僕の使っている割と新しいMBPでも3.5MBの画像1枚を300x300にリサイズするのに1s程かかってしまう。 10枚あればプロセス数しだいだが、Webrick等のシングルプロセスだと10sかかる計算になる。これはしんどい。
Railsで画像処理をしない
そもそも画像処理をRailsフレームワークでやるのはミスマッチすぎる。
よってRailsで画像処理をするのはどの方法でも筋悪と感じる。
ならばRails以外でやればいい。
今回はnginxで画像処理をさせてみた。
nginxならばurlからアクセスできるデーモンプロセスとしてすでに立っているし前例も多い。
nginxをフロントに置いて、いつものアクセスはRailsに渡す。 また、指定したサブドメインなら画像処理を走らせるようにしたい。
画像処理は相変わらずimagemagickを使うのが早そうだけど、nginxで処理を書こうとするとC言語でモジュールを書くことになる。 するとビルドしてデバッグしてパス名からパラメーターを解析して……なんてことをC言語でやらないといけなさそうで気が重い。
ngx_mruby
そこでやっと本題のngx_mrubyを使ってみることにした。 ngx_mrubyならnginxにやらせたい処理をmrubyで書ける。 その上リロードもいらずにコードが反映させることもできて高速な開発が期待できる。
書いてみた
そして出来上がったのがこんな感じのコード。
アクセスはこんな感じ。 アクセスパラメータを変えれば即座に画質に変えることができる。
GET http://img.localhost/{{filename}}/fit/300/300
こんなに雑にやりたいことが書けて非常に気持ちがいい。 細かい文字列の分解や連結なんかがひじょうにうまく書ける。
mrubyで書けないなら別のデーモンプロセスを作るか……。とも考えられるが、 これなら3.5MBの画像を300x300に変換するのに100ms程で済む。 もちろんExifも消している。
これで役割も分担できて動作もかいてき。 僕としては新しいプログラミングへの道が開けた気もしている。
最も大事なことは簡単にできることだ。
僕自身nginxについてはほとんどわかっていない。
にも関わらずたった数時間でnginxモジュールが書けてしまった。
この簡単さがやばい。
課題
今回の画像ファイルは全てローカルにあり、nginxとrailsが同じサーバーにあることが前提となっている。 目標はS3に画像ファイルをアップロードしておきたいが、 S3にアップロードしたりmrubyでS3からダウンロードしたりはまだできていない。
おまけ
mgx_mrubyではmruby_file_statという拙宅のコードも使えます。 こちらはファイルのstat情報をオブジェクトにまとめたものですが、Ruby2.2までの機能は全て網羅しているつもりです。 よければお使いくださいませ。 かしこ。