S3 Object Lambdaを利用したOn-Demandイメージ変換サービスの紹介

こんにちは。ムシンサ・コミュニティ開発チームのバックエンドエンジニア、チョ・ユシンです。 今回はAWS S3 Object Lambdaを利用してオンデマンドイメージ変換サービスを構築した事例を共有したいと思います。

### イメージ変換サービスとは？
一般的にサーバーでイメージを提供するためには、オリジナルイメージよりも小さいサイズのイメージを提供する方がサーバーのリソースおよびトラフィックコストを節約できます。最近ではスマートフォンの発展により、スマートフォンのカメライメージのサイズが基本的に10MB前後で保存されるため、100枚のイメージを提供するだけで1Gbpsのネットワーク帯域を占有してしまいます。また、イメージサイズが大きい場合、クライアント側でもイメージをレンダリングするのに時間がかかり、ユーザーエクスペリエンスにも悪影響を与える可能性があります。

従来のイメージ変換および提供方法は、AWSチュートリアルにも記載されているように、S3の保存イベントを通じてリサイズされたイメージを保存し提供する方式です。変換されたイメージがオリジナルイメージと同様にストレージに保存されるため、CDNを通じて提供することが可能であり、現在でも非常に効果的に使用されているアーキテクチャです。

![Screen Shot 2024-06-02 at 7.04.05 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/30c0c371-9193-613f-8681-275712811efd.png)

しかし、このようなイメージ提供方式は、イメージをUIに合わせて最適化するためには、さまざまなサイズやフォーマットのイメージを変換して事前に保存する必要があり、非同期で動作するシステムであるため、サーバーアプリケーションは変換されたイメージをクライアントに提供するために、元のイメージが変換されたかどうかを継続的に追跡しなければならず、これによりアプリケーションの複雑さが増加するという欠点があります。

変化するUIやクライアントに合わせて多様に変換されたイメージをストレージに継続して保存しなければならないため、コストも増加します。もし追加のイメージ変換方式が必要となれば、既存のすべてのイメージを変換しなければならない過程も必要になります。サービスのイメージはいつユーザーに再提供されるかわからないため、イメージの保存コストはサービスが続く限り累積して増加します。

### オンデマンドイメージ変換サービス
上記のような従来のイメージ変換サービスの限界を克服するために出てきたアーキテクチャがオンデマンドイメージ変換サービスです。オンデマンドイメージ変換サービスは、一般的にクライアントからイメージが要求された時に変換プロセスが行われ、変換されたイメージをCDNにキャッシュして継続的に提供する方式です。

![Screen Shot 2024-06-02 at 7.05.24 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/82b8c589-7d20-34c3-cc1e-5b14a6195289.png)

アップロードはS3に同様に行われますが、その後クライアントからイメージが要求される時点（オンデマンド）でイメージ変換が行われるため、サーバーアプリケーションはイメージ変換のタイミングを追跡する必要がないという利点があります。また、事前に変換されたイメージを保存しておく必要がないため、累積するストレージ保存コストを削減できるという利点もあります。

---

### オンデマンドイメージ変換の実装
最近、さまざまな企業がオンデマンドイメージ変換サービスを構築し公開しています。イメージという大きなデータを扱うためにサーバーレスアーキテクチャを使用し、フロントレイヤーにはCDNサービス（AWS CloudFront）があるという前提のもと、一般的なオンデマンドイメージ変換アーキテクチャの実装例は以下の通りです。

- Lambda@Edge
- API Gateway + Lambda
- Lambda Function URL
- S3 Object Lambda

まず、私たちはタイトルの通りS3 Object Lambdaを利用して構築し、上記の構成の長所と短所および意思決定の過程をご紹介します。

#### Lambda@Edge
Lambda@Edgeは、AWSのエッジロケーションインフラを活用してエッジコンピューティングを行えるようにするツールです。CloudFront Functionと似た形で動作しますが、Lambdaというサービスを利用してプロビジョニングできるため、さまざまなツールのサポートを受けられるのが利点です。また、エッジロケーションでコンピューティングが行われるため、ユーザーは自分に近い場所からコンピューティングされた応答を受け取ることができるという利点があります。ただし、S3の保存場所からエッジロケーションにあるLambda関数までイメージが転送される時間があるため、他の構成との実際の性能ベンチマーク比較が必要かもしれません。Lambda@EdgeはAWS CloudFrontと共に使用するために開発されたサービスであるため、構成が簡単ですが、*すべてのエッジロケーションにデプロイされるまでに時間がかかり、他の選択肢よりもコストが高いという欠点があります。*

![AWS-Lambda-at-Edge_How-It-Works-diagram.7ed76b49010fff37b53eb87467cd922b391a3cc7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/1bdad46a-12f6-304d-529f-dbb69e5a31ac.png)

#### API Gateway + Lambda
API Gatewayは、代表的なサーバーレスツールの一つで、サービスのエンドポイントを公開するために使用されます。API Gatewayを利用してLambdaのエンドポイントを作成し、CloudFrontとLambdaを接続するために使用でき、エンドポイントのモニタリングおよび保護、さまざまなAPIの管理も一緒に行えるのが利点です。代表的なサーバーレスツールの一つであるため、さまざまなフレームワークやIaCツールを使用して一度にプロビジョニングおよび管理できるのも利点ですが、*オンデマンドイメージ変換サービスのためだけに構成するには管理ポイントが増える可能性があり、API Gatewayに対する別途の費用がかかるという欠点があります*。

![Screen Shot 2024-06-02 at 7.10.01 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/0f8889c6-4201-08ac-024f-a463b11f6bda.png)

#### Lambda Function URL
Lambda Function URLは、Lambdaに直接的なエンドポイントを付与できる機能で、すべての構成の中で最もシンプルな構造であり、コストも安いです。Lambda Function URLを利用してパブリックエンドポイントを生成し、CloudFrontから直接接続が可能です。ただし、生成されたエンドポイントがパブリックであり、WAFなどを活用したモニタリングが難しく、リソース保護が容易ではないため、*セキュリティに脆弱であるという欠点があります。*

![lambdaorigincf_architecture.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/b834fe5e-8afa-5f8f-603c-2264e97f9d6d.png)

#### S3 Object Lambda
S3 Object Lambdaは、S3オブジェクトに対するプロキシのように動作し、取得したオブジェクトに対する応答前に処理を追加できる機能です。S3 Access PointおよびS3 Object Lambda Access Pointを利用して、S3ファイルおよびLambda関数にアクセスできるAccess Pointを取得し、それらを接続します。Lambda Function URLとは異なり、エンドポイントを公開せずにモニタリングおよび保護が可能であるという利点があります。

![s3-object-lambda-aliases-cloudfront-architecture-1024x325.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/6c3c5fa1-9f3a-187a-120a-c4ab92a5273b.png)

### コスト比較
Lambda Function URLはセキュリティ上の問題から除外し、他の構成のコストを比較すると、1) S3 Object Lambda 2) API Gateway + Lambda 3) Lambda@Edge の順でコストが安くなります。同じリクエスト量を用いて比較した場合、次のようになります。

- S3 Object LambdaはLambda@Edgeに比べて約65%安価です。
- S3 Object LambdaはAPI Gateway + Lambdaに比べて約10%安価です。

このようなコスト比較を通じて、イメージ変換サービスがコスト最適化を重視するアーキテクチャであることに焦点を当て、S3 Object Lambdaを利用してイメージ変換サービスを構築しました。

---

### S3 Object Lambdaを利用したオンデマンドイメージ変換サービスの実装
S3 Object Lambdaを構成するためには、まず対象バケットに対するS3 Access Pointの作成が必要です。この時、S3 Access Pointのパブリックアクセスおよびネットワークオリジンに関する設定が可能です。

![Screen Shot 2024-06-02 at 7.14.33 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/d4ce5bb4-a2df-7260-91dd-101f430ccbf4.png)

S3 Access PointにCloudFrontからアクセスできるように、Access Point Policyに以下のように設定します。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3:*",
            "Resource": [
                "arn:aws:s3:ap-northeast-2:000000000000:accesspoint/{s3 access point}",
                "arn:aws:s3:ap-northeast-2:000000000000:accesspoint/{s3 access point}/object/*"
            ],
            "Condition": {
                "ForAnyValue:StringEquals": {
                    "aws:CalledVia": "s3-object-lambda.amazonaws.com"
                }
            }
        }
    ]
}
```

その後、イメージ変換に使用するLambdaをプロビジョニングする必要があります。Object Lambda Access Pointを通じて入ってくるリクエストは以下の通りです。以下のJsonフォーマットを使用して、Lambdaコンソールおよびローカルインボークを通じてテストできます。

```json
{
    "xAmzRequestId": "1a5ed718-5f53-471d-b6fe-5cf62d88d02a",
    "getObjectContext": {
        "inputS3Url": "https://your-bucket-name.s3-accesspoint.us-east-1.amazonaws.com/s3.txt?X-Amz-Security-Token=...",
        "outputRoute": "io-iad-cell001",
        "outputToken": "..."
    },
    "configuration": {
        "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:123412341234:accesspoint/myolap",
        "supportingAccessPointArn": "arn:aws:s3:us-east-1:123412341234:accesspoint/myap",
        "payload": "test"
    },
    "userRequest": {
        "url": "/s3.txt",
        "headers": {
            "Host": "your-bucket-name.s3-object-lambda.us-east-1.amazonaws.com",
            "Accept-Encoding": "identity",
            "X-Amz-Content-SHA256": "e3b0c44297fc1c149afbf4c8995fb92427ae41e4649b934ca495991b7852b855"
        }
    },
    "userIdentity": {
        "type": "IAMUser",
        "principalId": "...",
        "arn": "arn:aws:iam::123412341234:user/myuser",
        "accountId": "123412341234",
        "accessKeyId": "..."
    },
    "protocolVersion": "1.00"
}
```

また、LambdaのリソースベースポリシーでCloudFrontがLambdaをインボークできるように、権限を以下のように許可する必要があります。

```json
// Lambda Resource Based Policy
{
  "Version": "2012-10-17",
  "Id": "default",
  "Statement": [
    {
      "Sid": "cloudfront",
      "Effect": "Allow",
      "Principal": {
        "Service": "cloudfront.amazonaws.com"
      },
      "Action": "lambda:InvokeFunction",
      "Resource": "arn:aws:lambda:ap-northeast-2:000000000000:function:sample-function"
    }
  ]
}
```
Lambdaのプロビジョニングが成功したら、S3 Object Lambda Access Pointを作成する必要があります。S3 Access Pointと同様に、パブリックアクセスに関する権限設定が可能です。S3 Object Lambdaの構成設定で、以前に作成したS3 Access PointとLambdaを選択してプロビジョニングできます。

![Screen Shot 2024-06-02 at 7.17.21 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/41185f50-4f6c-5ad8-1b5f-6f19b966d531.png)
![Screen Shot 2024-06-02 at 7.17.35 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/3059d114-9eea-68ca-5755-26e643984607.png)

また、Lambdaリソースベースポリシーと同様に、AWS CloudFrontがS3 Object Lambda Access Pointにアクセスできるように、Object Lambda Access Pointポリシーを以下のように設定する必要があります。

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "cloudfront.amazonaws.com"
            },
            "Action": "s3-object-lambda:Get*",
            "Resource": "${{ Object Lambda Access Point ARN }}",
            "Condition": {
                "StringEquals": {
                    "aws:SourceArn": "${{ CloudFront ARN }}"
                }
            }
        }
    ]
}
```

最後に、CloudFrontでオリジンを作成します。S3 Object Lambda Access Pointは公開されたエンドポイントではないため、OAC設定を新たに作成し、Signed Request（Signature V4）を選択します。

![Screen Shot 2024-06-02 at 7.18.42 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/84621239-1778-df50-b792-9e7b81565e73.png)

その後、上で作成したOACを利用してCloudFrontのオリジンと接続し、キャッシュポリシーおよびオリジンリクエストポリシーなどを設定すれば、正常に動作することを確認できます。


S3 Object Lambdaを構成する方法を中心に取り扱いましたが、詳細は以下の通りです。

- ラムダ関数の構成はLambda Power Tuningを利用して最適化しました。
- イメージ変換はnode.jsで作成され、node-sharpを使用して実装しました。
- アプリケーションのロギングはmiddyとLambda Powertoolを利用して作成しました。
- モニタリングおよびログの可視化はDatadogを利用して構築しました。
- CI/CDはserverless frameworkとGithub Actionを利用して構築しました。

### 結論
従来のイメージ変換アーキテクチャの累積するストレージ保存コストを削減し、ユーザーのUIにさらに最適化されたイメージを提供するために、S3 Object Lambdaを利用してコスト最適化されたオンデマンドイメージ変換システムを成功裏に構築しました。

オリジナルイメージに対しては95%の圧縮率を達成し、従来のアーキテクチャで提供していたイメージに比べても50%以上の圧縮率を実現しました。これにより、CloudFrontのデータ転送コストおよびS3保存コストを削減でき、クライアント側でもTBT（Total Blocking Time）とSI（Speed Index）をそれぞれ39%、13%削減することができました。

![Screen Shot 2024-06-02 at 7.20.01 PM.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/10dc0e9b-e855-b117-0bf9-d46b51da579a.png)
![IMG_0818.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3797109/7c9c0e31-f4de-543c-2c45-fbd7febcb680.jpeg)



#### Reference
- Source : https://medium.com/musinsa-tech/s3-object-lambda%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-on-demand-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%B3%80%ED%99%98-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%86%8C%EA%B0%9C-5e3650cc27a9
- https://aws.amazon.com/ko/blogs/korea/new-use-amazon-s3-object-lambda-with-amazon-cloudfront-to-tailor-content-for-end-users/
- https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/transforming-objects.html
- https://docs.aws.amazon.com/solutions/latest/constructs/aws-cloudfront-apigateway-lambda.html
- https://aws.amazon.com/ko/blogs/korea/new-use-amazon-s3-object-lambda-with-amazon-cloudfront-to-tailor-content-for-end-users/
- https://aws.amazon.com/ko/blogs/korea/new-use-amazon-s3-object-lambda-with-amazon-cloudfront-to-tailor-content-for-end-users/
- aws-lambda-power-tuning, middly, lambda power tool
- node-sharp, serverless framework