nginx_small_lightを使ったリアルタイム画像リサイズの仕組み
こんにちは、オールアバウトの筋トレエンジニア芸人の@musclemikiyaです。
今回は、オールアバウトの画像リサイズの仕組みについて紹介したいと思います。
紹介するのは、以前まで使用していた非リアルタイム版と、今年構築したリアルタイム版の2種類です。後者で使用している仕組みは、既に多数の導入事例が報告されていますが、構成や負荷に関する情報があまりなかったので、参考になれば嬉しいです!
サービス紹介
以下はオールアバウトで運営している、画像を使用しているサービスです。
- 総合情報サイトAll About
- CafeSnap (iOS/Android向けアプリ)
- All Aboutまとめコンテンツ
- All About News Dig
- For M
- All About Japan
- その他いろいろ
旧構成(SQSを使用した非同期リサイズ)
以前は下記の構成で、PHPのアプリケーションとImageMagickでバッチ処理で画像をリサイズして保存する、という仕組みでした。
負荷を懸念してリアルタイム変換は行っていませんでしたが、リサイズ処理にPHPを使用していたということもあり、サーバースペックの高いものを使用する必要がありました。
また変換が完了するまではオリジナルのサイズを返してしまうため、スマートフォンのアプリなどではクラッシュの原因になることも度々ありました。
※EC2(m3.large) x 2 + EC2(m3.xlarge) x 1
新構成(リアルタイムリサイズ)
新しい構成ではnginx_small_lightとImageMagickでリアルタイム変換を実現しています。nginx.conf内でURLの整形処理なども行えるため、旧構成時に使用していたURL構成を維持したまま移行することができました。
※EC2(c4.large) x 2
パフォーマンス
- ベンチマークでは1台あたり50req/sec以上
- 現状1台あたりピークで20req/secで稼働中(CPU負荷10~30%)
- メモリは全然使わないので4GBで十分
仕様
使っているもの
- nginx1.8.1
- nginx_small_light
- ImageMagick
- http_perl_module
特徴
サービスごとのオリジンサーバーの設定
urlのパスにサービス名を指定し、nginx.conf内で下記のように、backendとサービスごとのオリジンサーバーを設定しています。
... if ($service = "hoge") { set $backend "http://hoge.s3.amazonaws.com/content/"; } if ($service = "fuga") { set $backend "http://fuga.s3.amazonaws.com/content/"; }
変換サイズの制限
nginx.confではできない、数値比較などはhttp_perl_moduleを使用して、下記のようにバリデーションを行っています。
package validator; use nginx; use strict; sub handler { my $r = shift; my $max_size = $r->variable("small_light_maximum_size"); my $width = $r->variable("width"); my $height = $r->variable("height"); my $mode = $r->variable("mode"); # サイズ制限 if ($width > $max_size || $height > $max_size) { return 0; } return 1; } ...
転送量を減らすための工夫
一番気をつけなければいけないのが、画像の転送量です。基本的にCDNなどは転送量に対しての従量課金になるので、大きな画像を配信するか大量の画像を配信すればするほどお金がかかります。
リアルタイムでリサイズできる利点としては、表示に合わせて適切な画像サイズに変換できることです。登録時に適切なサイズにリサイズするというのも考えられますが、登録するシステムが複数あったりと、あまり現実的ではありません。下記に配信するコツを紹介します。
適切なサイズ指定
最もリアルタイムのリサイズの恩恵を受けられるのが、表示する箇所に合わせて最適なサイズを選択できるということです。下記に示したように、サイズが大きく変化することがわかります。
height(px) | width(px) | サイズ(KB) | オリジナル比(%) | |
---|---|---|---|---|
オリジナル | 1994 | 1994 | 583 | 100 |
変換後 | 1000 | 1000 | 308 | 53 |
変換後 | 500 | 500 | 88.8 | 15 |
画質のコントロール
下記にnginx_small_lightのパラメータを記載しましたが、この中の"q=90"の部分が画質になります。この部分をコントロールすることで画質を調整することができます。
small_light(dw=800,dh=600,da=l,jpeghint=y,q=90,of=jpg)
画質とサイズの対比
クオリティ(%) | サイズ(KB) | オリジナル比(%) |
---|---|---|
100 | 135 | 100 |
95 | 56.4 | 42 |
90 | 35.9 | 27 |
80 | 22.9 | 17 |
70 | 17.9 | 13 |
※画質比較はGithubなどを使用すると、実際に並べて比較などがしやすくなります。
(2-up/Swipe/Onion Skinを切り替えるとよい)
適切な拡張子を使用する
JPEG画像を使用する機会が多いかと思いますが、まれにPNGを使わなければいけないこともあるかと思います。しかし、下記の表を見ても分かる通り、デザイン上の制約がない限りはPNGではなくJPEGを使用したほうが良さそうです。
同じ画像をpngとjpgで変換した結果
拡張子 | クオリティ(%) | サイズ(KB) | オリジナル比(%) | |
---|---|---|---|---|
オリジナル画像 | png | 100 | 1500 | 100 |
画質のみ調整 | png | 65 | 1100 | 73 |
jpgに変換 | jpg | 100 | 350 | 23 |
適切なExpiresヘッダの設定
先ほどのoriginと同じように、expiresもサービスごとに適切に設定することで、ブラウザやCDNのキャッシュをうまく活用することができます。
add_header Cache-Control "public"; set $expires 1h; if ($service = "hoge") { set $expires 1M; } if ($service = "fuga") { set $expires 1w; } expires $expires;
まとめ
いかがだったでしょうか?以外にシンプルな構成でかなりのリクエストを捌けていることがわかると思います。
アクセスの多いサイトでは、デバイスのサイズやコンテンツに合わせて画像を最適化することで、UXの向上だけでなく、コストも大幅に削減できる場合があるので、画質やサイズなどを適切にチューニングすることをオススメします!!