オールアバウトTech Blog

株式会社オールアバウトのエンジニアブログです。

nginx_small_lightを使ったリアルタイム画像リサイズの仕組み

こんにちは、オールアバウトの筋トレエンジニア芸人の@musclemikiyaです。
今回は、オールアバウトの画像リサイズの仕組みについて紹介したいと思います。

紹介するのは、以前まで使用していた非リアルタイム版と、今年構築したリアルタイム版の2種類です。後者で使用している仕組みは、既に多数の導入事例が報告されていますが、構成や負荷に関する情報があまりなかったので、参考になれば嬉しいです!

サービス紹介

以下はオールアバウトで運営している、画像を使用しているサービスです。

旧構成(SQSを使用した非同期リサイズ)

以前は下記の構成で、PHPのアプリケーションとImageMagickバッチ処理で画像をリサイズして保存する、という仕組みでした。

負荷を懸念してリアルタイム変換は行っていませんでしたが、リサイズ処理にPHPを使用していたということもあり、サーバースペックの高いものを使用する必要がありました。

また変換が完了するまではオリジナルのサイズを返してしまうため、スマートフォンのアプリなどではクラッシュの原因になることも度々ありました。

f:id:allabout-techblog:20160607112847j:plain
※EC2(m3.large) x 2 + EC2(m3.xlarge) x 1

新構成(リアルタイムリサイズ)

新しい構成ではnginx_small_lightとImageMagickでリアルタイム変換を実現しています。nginx.conf内でURLの整形処理なども行えるため、旧構成時に使用していたURL構成を維持したまま移行することができました。

f:id:allabout-techblog:20160607113408j:plain
※EC2(c4.large) x 2

パフォーマンス

  • ベンチマークでは1台あたり50req/sec以上
  • 現状1台あたりピークで20req/secで稼働中(CPU負荷10~30%)
  • メモリは全然使わないので4GBで十分

仕様

使っているもの

特徴

サービスごとのオリジンサーバーの設定

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を切り替えるとよい)

GitHubやBitbucketの画像差分

適切な拡張子を使用する

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の向上だけでなく、コストも大幅に削減できる場合があるので、画質やサイズなどを適切にチューニングすることをオススメします!!

リンク