Lambda@Edgeによる画像リサイズを本番運用した感想

Lambda@Edgeによる画像リサイズを数ヶ月ほど本番運用したので、所感をまとめてみました。

Lambda@Edgeの画像のリアルタイムリサイズについては、クックパッドさんの記事（[AWS Lambda@Edge で画像をリアルタイムにリサイズ＆WebP形式へ変換する - クックパッド開発者ブログ](https://techlife.cookpad.com/entry/2018-05-25-lambda-edge)）で詳しく解説されているため、概要についてはそちらを参照してください。

自分の結論としては、「**小〜中規模のサイトでは十分に活用できるが、大規模なサイトで運用する場合は他Saasを使った方が良い**」という結論です。もう少し詳しく解説していきます。

## Lambda@Edgeのレスポンスサイズは1MBが上限
Lambda@Edgeから返すレスポンスサイズは、最大で1MBという制限があります。

これは公式のドキュメント「[制限 - Amazon CloudFront #Lambda@Edge の制限](https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/cloudfront-limits.html#limits-lambda-at-edge)」に、「ヘッダーと本文を含む、Lambda 関数によって生成されたレスポンスのサイズ」は「最大1MB」と記載されています。

そのため、最適化（＋リサイズ）後の画像を返す際に、**レスポンスサイズが1MB以上であれば処理前の画像を直接返す**という処理にしなければなりません。

```lambda.js
const sizeof = require('sizeof') // https://github.com/miktam/sizeof
const { response } = event.Records[0].cf

// ....

const buffer = await sharpObject.toBuffer()

if (buffer.byteLength >= (1024 * 1000) - sizeof(response)) {
  callback(null, response) // responseオブジェクトのbodyプロパティが無い状態でcallbackした場合、CloudFrontはオリジンの画像を直接返します
  return
}
```

これは画像の横幅を2000pxにしたらレスポンスが1MB以上になった、という時にリサイズ前の画像を返さなければなりません。これを許容できない、もしくは将来的に許容できなくなる場合は、Lambda@Edgeの導入は控えたほうが良さそうです。

## レスポンスタイムによるSEO的な悪影響
正確に計測した訳ではありませんが、Lambda@Edgeを挟むことによってレスポンスタイムが最低でも100msほど伸びます。このLambda@Edgeの呼び出しにかかるレイテンシは、CloudFront側の仕様と考えていいかもしれません。

基本的にLambda@Edgeで処理したレスポンスはCloudFront側でキャッシュできるため、このレイテンシはそれほど問題にはなりにくいです。しかし画像サイズのバリエーションが多く、サイト運用側で予期してキャッシュを作るといった対応ができない場合は気を付ける必要があります。

こちらの記事「[ページのダウンロード時間が1000ミリ秒を超えると、Googlebotがクロールに制限をかける可能性あり](https://www.suzukikenichi.com/blog/googlebot-may-limit-crawling-if-time-spent-downloading-a-page-is-over-1000ms/)」によると、ページのダウンロード時間が1000msを越えるとSEOで悪影響があるとのことです。またこのダウンロード時間は、ページで必要なjsやcss、画像などの静的コンテンツも含みます。

そこで本番運用しているサイトをサーチコンソールのページのダウンロード時間を確認したところ、1000msを超えている時が何回かありました。もしやと思いCloudFrontのログをAthenaで見てみると、googlebotに1000ms以上かかった画像のレスポンスが何件かありました。おそらく未キャッシュの画像を返したため、このような遅いレスポンスタイムで返しているようです。

あらかじめ使われる画像のサイズが予想できるのであれば、以下のようにデプロイ後にキャッシュを作るスクリプトを回す必要があります。

```create-cache.js
const axios = require('axios')
const walkSync = require('walk-sync')

async function createCache() {
  console.time('image cache creating')
  const promises = []
  const sizes = [340, 680, 1024, 1360, 2048]

  walkSync('./static/images/', { directories: false }).forEach((file) => {
    const url = `https://my-app.com/images/${file}`
    console.log(url)

    sizes.forEach((size) => {
      promises.push(axios.get(`${url}?w=${size}`))
    })
  })

  await Promise.all(promises)
  console.timeEnd('image cache creating')
}
```

## 結論: 小中規模にはLambda@Edgeを、大規模にはSaaSを
Lambda@EdgeはAWSを使えればすぐに使用できるため、小〜中規模では小回りの効く扱いやすいサービスだと言えます。

しかしLambda@Edgeは上記のような問題点があるため、大規模サイトなどの運用ではImageFluxやImgixなどのSaaSを利用するべきじゃないかと思います。ImageFluxはメルカリで使われており、またImgixは一休.comに利用されています。

- [メルカリもBASEも使ってる！ 画像変換サービスImageFluxの魅力と新機能をご紹介！ ImageFlux meetup #2 レポート (前編) | さくらのナレッジ](https://knowledge.sakura.ad.jp/19287/)
- [imgix導入で画像最適化とサイトスピードを改善した話 - 一休.com Developers Blog](https://user-first.ikyu.co.jp/entry/2018/07/02/070000)

私はこれらのサービスを利用したことがないので分かりませんが、こういった有名サービスで運用されていることを考慮すると、大規模サイトでは有力なソリューションなのでしょう。
またImageFluxやImgixが開発テスト用の環境を簡単に用意できるかという点も気になります。（Lambda@Edgeは完全に従量課金制なので、開発環境のために用意しても料金的には問題なさそうです。）

### ソースコードも公開しています
Node8.10で書き直してTerraformファイルも作成したソースをGitHubに上げていますので、よかったら参考にしてください。（[GitHub - hareku/lambda-edge-image-optimizer-terraform](https://github.com/hareku/lambda-edge-image-optimizer-terraform)）

何かご質問などあればお気軽にコメントください。