lambda@edge上でwasmを使用した画像リサイズにおける速度
はじめに
最近、lambdaでwasmを利用できることを知ったので、画像のリサイズ処理をwasm(Rust)で行った場合の速度を計測してみました。
環境構築
環境構築には下記を使用しました。
- Terraform
- Docker
- Git
AWSリソースの用意
AWSリソースの作成にはTerraformで行います。
今回使用したtfファイルはこちらになります。
AWS 構成
作成する手順は下記の通り。
git clone https://github.com/takenoko-gohan/terraform-image-resizing-on-cloudfront.git
cd terraform-image-resizing-on-cloudfront
terraform init
# 問題がないか確認
# 変数 prefix は任意の値を指定してください
terraform plan -var="prefix=hoge"
# 問題がなければ AWS リソースの作成
terraform apply -var="prefix=hoge"
Lambda@edgeの用意
nodejs
デプロイパッケージの作成
比較対象のnodejsはAWSのブログを参考に下記コードを書きました。
BUCKET
は自身の環境に合わせて記述します。
index.js
"use strict";
const http = require("http");
const https = require("https");
const querystring = require("querystring");
const Sharp = require("sharp");
const AWS = require("aws-sdk");
const S3 = new AWS.S3({
signatureVersion: "v4",
});
// 自環境の S3 バケットを指定してください
const BUCKET = "";
exports.handler = (event, context, callback) => {
let response = event.Records[0].cf.response;
let request = event.Records[0].cf.request;
let params = querystring.parse(request.querystring);
console.log(JSON.stringify(params));
// パラメーター d の指定がない場合、そのまま返す
if (!params.d) {
callback(null, response);
return;
}
// リサイズ後の幅と高さを取得する
let dimension = params.d.split("x");
let width = parseInt(dimension[0], 10);
let height = parseInt(dimension[1], 10);
// URI から必要な情報を取得
let path = request.uri;
let key = path.substring(1);
let prefix, originalKey, match, imageName, requiredFormat;
try {
match = key.match(/(.*)\/(.*)/);
prefix = match[1];
imageName = match[2];
requiredFormat = imageName.split(".")[1];
originalKey = prefix + "/" + imageName;
}
catch (err) {
console.log("no prefix present..");
match = key.match(/(.*)/);
imageName = match[1];
requiredFormat = imageName.split(".")[1];
originalKey = imageName;
}
// 画像を取得し、リサイズ
console.log("start getObject");
S3.getObject({ Bucket: BUCKET, Key: originalKey }).promise()
.then(data => Sharp(data.Body)
.resize(width, height)
.toFormat(requiredFormat)
.toBuffer()
)
.then(result => {
// generate a binary response with resized image
response.status = 200;
response.body = result.toString("base64");
response.bodyEncoding = "base64";
response.headers["content-type"] = [{ key: "Content-Type", value: "image/" + requiredFormat }];
console.log("response :", JSON.stringify(response));
callback(null, response);
})
.catch( err => {
console.log("Exception while reading source image :%j",err);
});
};
次に下記のDockerfileを使用してデプロイパッケージを作成します。
docker pull node:12-buster-slim
docker run --rm --volume ${PWD}/<index.jsが格納されているディレクトリ>:/build node:12-buster-slim /bin/bash -c "cd /build; npm init -f -y; npm install sharp --save; npm install querystring --save; npm install --only=prod"
cd <index.jsが格納されているディレクトリ>
zip -r app.zip ./*
Lambda@edgeにデプロイ
作成したデプロイパッケージを{ prefix }-image-resize-nodejs
にアップロードし、「トリガーを追加」からLambda@edgeにデプロイします。
wasm
リポジトリ
index.js
はnodejsと同じで画像リサイズ処理をwasmに置き換えているだけです。
@@ -3,7 +3,7 @@
const http = require("http");
const https = require("https");
const querystring = require("querystring");
-const Sharp = require("sharp");
+const wasm = require("./wasm/image_resize");
const AWS = require("aws-sdk");
const S3 = new AWS.S3({
@@ -62,15 +62,11 @@
// 画像を取得し、リサイズ
console.log("start getObject");
S3.getObject({ Bucket: BUCKET, Key: originalKey }).promise()
- .then(data => Sharp(data.Body)
- .resize(width, height)
- .toFormat(requiredFormat)
- .toBuffer()
- )
+ .then(data => wasm.resize(data.Body, width, height, requiredFormat))
.then(result => {
// generate a binary response with resized image
response.status = 200;
- response.body = result.toString("base64");
+ response.body = result;
response.bodyEncoding = "base64";
response.headers["content-type"] = [{ key: "Content-Type", value: "image/" + requiredFormat }];
console.log("response :", JSON.stringify(response));
wasmではネットワーク通信、ファイル操作が行えないようなので、nodejs側が取得したS3オブジェクトのバッファーを受け取って処理するようにしました。
extern crate base64;
extern crate console_error_panic_hook;
extern crate wasm_bindgen;
extern crate web_sys;
use image::{self, imageops::FilterType};
use wasm_bindgen::prelude::*;
macro_rules! log {
( $( $t:tt )* ) => {
web_sys::console::log_1(&format!( $( $t )* ).into());
}
}
#[wasm_bindgen]
pub fn resize(buf: Vec<u8>, width: u32, height: u32, format: &str) -> String {
console_error_panic_hook::set_once();
log!("start wasm function");
// nodejs から渡されたバッファーを読み込む
log!("start buffer load");
let img = image::load_from_memory(&buf).expect("buffer load error");
log!("end buffer load");
// リサイズ
log!("start image resize");
let resize_image = img.resize_to_fill(width, height, FilterType::Triangle);
log!("end image resize");
// リサイズ結果を書き込む
log!("start image write to buffer");
let mut result: Vec<u8> = Vec::new();
match format {
"jpeg" | "jpg" => {
log!("match jpeg");
match resize_image.write_to(&mut result, image::ImageOutputFormat::Jpeg(80)) {
Ok(_) => log!("buffer write sucess"),
Err(err) => log!("buffer write error: {}", err),
}
},
"png" => {
log!("match png");
match resize_image.write_to(&mut result, image::ImageOutputFormat::Png) {
Ok(_) => log!("buffer write sucess"),
Err(err) => log!("buffer write error: {}", err),
}
},
_ => {
log!("did not match");
},
}
log!("end image write to buffer");
log!("end wasm function");
// BASE64 で返す
return base64::encode(&result);
}
デプロイパッケージの作成
git clone https://github.com/takenoko-gohan/image-resizing-with-rust-wasm-on-lambda-edge.git
# wasm のビルド準備
cd image-resizing-with-rust-wasm-on-lambda-edge/image_resize
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
# wasm のビルド
wasm-pack build --target nodejs --release --out-dir ../app/wasm
# BUCKET を編集
cd ../
vim app/index.js
# デプロイパッケージの作成
docker pull node:12-buster-slim
docker run --rm --volume ${PWD}/app:/build node:12-buster-slim /bin/bash -c "cd /build; npm init -f -y; npm install querystring --save; npm install --only=prod"
cd app
zip -r app.zip ./*
Lambda@edgeにデプロイ
nodejsと同様に{ prefix }-image-resize-wasm
にアップロードし、Lambda@edgeにデプロイします。
速度
それぞれのCloudfrontでa(927KB)、b(4.2MB)、c(9.9MB)の画像を400x400にリサイズした場合の速度は下記のようになりました。
nodejs
a(927KB) | b(4.2MB) | c(9.9MB) | |
---|---|---|---|
1回目 | 573ms | 1130ms | 1960ms |
2回目 | 456ms | 990ms | 1850ms |
3回目 | 450ms | 904ms | 1780ms |
4回目 | 494ms | 950ms | 1640ms |
5回目 | 455ms | 935ms | 1730ms |
平均 | 486ms | 982ms | 1792ms |
wasm
a(927KB) | b(4.2MB) | c(9.9MB) | |
---|---|---|---|
1回目 | 1810ms | 6630ms | 10340ms |
2回目 | 1890ms | 6160ms | 10100ms |
3回目 | 1840ms | 6080ms | 10050ms |
4回目 | 1770ms | 6060ms | 10020ms |
5回目 | 1760ms | 6010ms | 9950ms |
平均 | 1814ms | 6188ms | 10092ms |
Cloudwatch Logsでログを見てみるとwasmではnodejsから渡されたバッファーの読み込みに時間がかかっていたようでした。(画像のログはcをリサイズしたときのものになります。)
さいごに
最近Rustを学習し始めたので、こうしたほうがwasmの速度が早くなるなどのアドバイスをいただければ幸いです。
Discussion