AWS Lambda@Edge と Google Cloud Functions の画像リサイズサーバどちらが速い？

画像リサイズ処理ってけっこう面倒くさいので最近は画像リサイズサーバを構築して URL のクエリストリングで動的にリサイズするようにしています。定番は nginx で構築するケースですが、サーバの管理をしなくてはならないので個人的にはサーバレス構成が好みです。

というわけで、AWS の CloudFront Lambda@Edge と Google の Cloud Functions でリサイズ処理をしたときのパフォーマンスの違いを計測してみました。

## 構成図

以下のような構成で計測しました。

![](https://qiita-image-store.s3.amazonaws.com/0/2152/e15a9470-cf71-6039-87a5-7fed796441f5.png)

AWS は CloudFront + Lambda@Edge を使いリサイズ処理、オリジンサーバは S3 にしています。Google は Cloud Functions を使いリサイズ処理、オリジンサーバは Cloud Storage にしています。どちらもプライベートネットワークなどの接続ができなそうですが、おそらく物理的には近いだろうと推測して、それぞれのストレージサービスでの組み合わせにしています。

## 検証コード

計測するためだけのコードなので実践的ではないですが、参考までに。

### Lambda@Edge

```javascript
const request = require('request-promise')
const Sharp = require('sharp')

const download = (url) => {
  return request({
    url: url,
    encoding: null
  })
}

const resize = (body, format, width, height) => {
  return Sharp(body)
    .resize(width, height)
    .toFormat(format)
    .toBuffer()
    .then(buffer => {
      return buffer
    }).catch(error => {
      console.log(error)
    })
}

module.exports.perform = (event, context, callback) => {
  let response = event.Records[0].cf.response
  const request = event.Records[0].cf.request
  download(`https://s3.amazonaws.com/example-bucket-name${request.uri}`).then(body => {
    const format = 'jpeg'
    resize(body, format, 200, 100).then(body => {
      resizeFunc.save(body, format, 'sample').then(() => {
        response.status = 200
        response.body = body.toString('base64')
        response.bodyEncoding = 'base64'
        response.headers['content-type'] = [{ key: 'Content-Type', value: 'image/' + format }]
        callback(null, response)
      })
    })
  })
}
```

S3 からのファイルダウンロードを S3 SDK を使ったバージョンでも比較しましたが変化はありませんでした。

参考コード

```javascript
const getFile = key => {
  return S3.getObject({
    Bucket: BUCKET,
    Key: key.slice(1)
  }).promise()
}
getFile(request.uri).then(data => {
  const body = data.Body
  // snip
})
```

### Cloud Functions

```javascript
import * as functions from 'firebase-functions'
const firebaseConfig = JSON.parse(process.env.FIREBASE_CONFIG)
import * as admin from 'firebase-admin'
admin.initializeApp()
storage = admin.storage()

const sharp = require('sharp')
const fileType = require('file-type')
const os = require('os')
const path = require('path')

export const image_resize = functions.https.onRequest((req, res) => {
  const filePath = req.query.path
  const bucket = storage.bucket(firebaseConfig.storageBucket)
  const tempFilePath = path.join(os.tmpdir(), `${Math.round( Math.random() * 1000 )}`)
  bucket.file(filePath).download({
    destination: tempFilePath,
  }).then(() => {
    sharp(tempFilePath)
      .rotate()
      .resize(200, 100)
      .toBuffer()
      .then(data => {
        const type = fileType(data)
        res.set('Content-Type', type.mime)
        res.status(200).send(data)
      })
  })
})
```

## 検証結果

`ab` コマンドを用いてそれぞれの平均レスポンスを出しました。

|項目|リクエストあたりの応答速度|
|---|---|
|Lambda@Edge|4.3 sec|
|Cloud Functions|2.6 sec|

2018/11/18 時点では Cloud Functions の方が速そうです。

どこに時間がかかっているのかと言うと、それぞれのオリジンサーバからの画像ファイルダウンロードです。Sharp を使ったリサイズ処理自体はほぼ差はありませんでした。検証で使っているコードを改善すればもっとパフォーマンス良くできるのかも知れませんが…というか、知りたい。

実際には CDN でキャッシュするので、初回のアクセス以降は高速にレスポンスを返せますが、とはいえ初回のアクセスをどこで発生させるかはアプリケーションによって変わってくるので、どんな状況であっても高速であったほうがいいと思います。