AWS流行ってますね。
先月末のre:Invent 2016では既存のサービスの拡充や、意欲的な新サービスが発表されたようです。
ところで、最近API GatewayはBinary Support, Lambdaでは環境変数のサポートなど、サーバレスな仕組みをお手軽に実現するためのサポートも増えました。1
今回は、API GatewayのBinary SupportとLambdaを使って既存の構成をサーバレスな仕組みに置き換えられそうか、試してみました。
対象は、サムネイル画像の配信システムです。
注意: 資料を元にいくつかの既存システムを紹介していますが、私の解釈の間違いや現状のシステムとの差異がある可能性に注意してください。
要件
- ユーザに投稿してもらった画像は、オリジナル画像としてS3に保存
- オリジナル画像は配信しない (private)
- サムネイル画像が要求されたらオリジナル画像から動的変換2 し、CDNで配信
- サムネイル画像は保存しない
- CDNにヒットしなかったらもう一度動的変換
動的変換は便利
サービスの要求によりますが、よくある事前にサムネイル画像生成は将来的にもアプリが事前に決められたサイズしか要求しない場合には、シンプルで都合が良い構成です。
しかし、既存の画像を丸々新しいサイズにリサイズしなければならないので、新たにレスポンシブルなデザインに対応するなど新しいデバイスの展開やプロトタイピング的な利用が難しくなります。
今回はよりチャレンジングそうな動的変換を行うことにします。
既存の構成
動的変換でよくある構成を次に示します。TOFU, pixivのサムネイル事情 などを参考にしています。
リクエスト数やCDNへのヒット率に応じて、EC2のインスタンスの増減をすればお手軽にスケールできそうです。
サムネイル画像へのアクセス
-
http://example.com/images/[width]x[height]/[hash]/[image].jpg
-
hash
は悪意のあるユーザがwidth, height
をちょっとづつずらしてリサイズ処理を多数走らせないように用意
-
サーバレスな構成
API GatewayとLambdaを使って次のように構成してみました。シンプルですね。
アクセス方法は既存の構成と同じにできます。
スケールすることに関して
- API GatewayはLambdaのプロキシとして十分にリクエストを処理できる
- AWSがよしなにやってくれる
- ボトルネックになるとしたらLambda
- Lambdaもスケールすることに関して何も気にしなくて良い
- AWSがよしなにやってくれる
- Lambdaなのでスケールすることに関して気にする必要がある
-
リージョン毎に 同時実行数制限がある (デフォルト: 100)
- サムネイル処理に1000msかかるとすると、100 rps程度まで処理できる
- AWSに要求すれば引き上げられる
-
リージョン毎に 同時実行数制限がある (デフォルト: 100)
構築手順の概要だけ説明します。
Lambdaの設定
API Gatewayのバックエンドとして構築し、パス名を処理する必要があります (API Gatewayの設定でevent
にセットできる)。
また、Lambdaから対象バケットのオブジェクトに対してGetObject
を行える設定が必要です。
変換はLambdaの環境でサポートされているImageMagickが手軽ですね (ImageMagickがベストかどうかについては議論の余地があります)。
const aws = require('aws-sdk');
exports.handler = (event, context, callback) => {
const s3Client = aws.S3();
const size = parseSize(event.size);
const hash = event.hash;
const image = event.image;
// hashを使った検査
...
// サムネイル生成
s3Client.getObject({ Bucket: "...", Key: image }).promise()
.then(response => resize(response.Body, size))
.then(thumbnail => callback(null, thumbnail))
.catch(err => callback(err))
}
API Gatewayの設定
-
/{size}/{hash}/{image}
というリソースを作成- 全てパスパラメータ
-
/{size}/{hash}/{image}
にGETメソッドを用意- 先ほどのLambdaと統合
- Integrationでマッピングパターンを次のように設定
- この設定でLambdaのハンドラの
event
にパスが渡せる
- この設定でLambdaのハンドラの
- Binary Supportを利用し、
image/*
のときバイナリを返すように設定
{
"size": "$input.params('size')",
"hash": "$input.params('hash')",
"image": "$input.params('image')"
}
CloudFront
- Custom OriginをAPI Gatewayのエンドポイントに設定
- Origin (API Gateway)がサムネイル画像を返せない時は、
no_img.png
のような画像を短い時間キャッシュするように設定- 高い負荷の時は、一部この画像が表示されることになる
性能
siege を使って簡単に測定して得られた知見。
- 800x800 (500KB) のサンプル画像で、100x100のサムネイル生成の平均変換時間は500ms程度
- いわゆるLambdaのコンテナがHotなとき 参考
- API Gateway + Lambdaは100rpsくらいならErrorなくさばけていた
- Lambdaの同時実行数は100を超えても短い時間なら問題無い
まとめ
- サーバ管理が不要に
- AWSの仕様や変更をウォッチし続ける必要はありそうですが...
- CloudFrontのヒット率が高ければ (90%以上)、1000rps程度のサムネイル画像要求ならさばける可能性が十分ある
- 全ての仕組みが従量課金制なので、発展段階の小規模なサービスにはコストを低く抑えやすい
- Route 53を使えば途中で配信方法を切り替えるなど柔軟な対応もできそう
参考文献
動的変換
- TOFU, 2015年時点
- CloudFront+S3の画像配信にリサイズ機能を追加する
- nginxの画像キャッシュサーバにリサイズ機能を付けた
- pixivのサムネイル事情, サムネイルマスタとgo-thumber
- とあるECサイトに動的画像変換を導入した話
- 簡単!リアルタイム画像変換をNginxだけで行う方法
事前に変換
クラウドサービス
その他
-
サムネイル画像が実際に要求された時に初めて変換することを動的変換と呼ぶことにする ↩