AWS Lambda のレスポンスストリーミングを使って API Gateway + Lambda 構成で 10 MB のレスポンスを生成してみた

2023.04.26

いわさです。

先日のアップデートで Lambda の関数 URL を使う場合は関数がレスポンスを返す方法としてレスポンスストリーミングが使えるようになりました。

従来は Lambda のレスポンスには 6 MB の制限が存在しており、API Gateway (REST) の 10 MB よりも統合される Lambda のほうが上限が低いこともあって注意しなければならない点でした。
しかし、レスポンスストリーミングによってレスポンスサイズの上限が緩和されるので、API Gateway を使う時にレスポンスサイズ上限まで活用したい場合、利用出来るかもしれません。

今回は S3 バケットに大きなファイルサイズのオブジェクトをアップロードし、API Gateway や Lambda 経由で GET してみましたので検証結果を紹介します。
Amazon S3 バケットには次のように 4 MB、9 MB、11 MB のダミーファイルをアップロードしておきます。

% ls -lh                                                                                       
total 49152
-rw-r--r--  1 iwasa.takahito  staff   4.0M Apr 24 07:44 hoge1
-rw-r--r--  1 iwasa.takahito  staff   9.0M Apr 24 07:44 hoge2
-rw-r--r--  1 iwasa.takahito  staff    11M Apr 24 07:44 hoge3

% shasum -a 256 hoge1                                                                          
bb9f8df61474d25e71fa00722318cd387396ca1736605e1248821cc0de3d3af8  hoge1
% shasum -a 256 hoge2
d2ee4703cd9698945ca7b9fe1689ea3095597eac1a0afd8dba00cac7894fdc43  hoge2
% shasum -a 256 hoge3
d4d2639ad94b0211839461226eb57a5e185829e5c4a9783c2da9f0a3da032bf5  hoge3

後続の確認用にハッシュを計算しておきましょう。

Amazon S3 の静的ウェブサイトホスティング機能を使った場合

まずは先程の Amazon S3 バケットの静的ウェブサイトホスティング機能を有効化して直接エンドポイントへアクセスしてみます。
次のようなイメージです。

cURL を使ってリクエストを送信します。

% curl http://iwasa-hoge-public-bucket.s3-website-ap-northeast-1.amazonaws.com/hoge0424/hoge1 -o hoge1s3
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 4096k  100 4096k    0     0  7877k      0 --:--:-- --:--:-- --:--:-- 7984k
% curl http://iwasa-hoge-public-bucket.s3-website-ap-northeast-1.amazonaws.com/hoge0424/hoge2 -o hoge2s3
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 9216k  100 9216k    0     0  12.8M      0 --:--:-- --:--:-- --:--:-- 12.9M
% curl http://iwasa-hoge-public-bucket.s3-website-ap-northeast-1.amazonaws.com/hoge0424/hoge3 -o hoge3s3
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 11.0M  100 11.0M    0     0  14.5M      0 --:--:-- --:--:-- --:--:-- 14.6M

% shasum -a 256 hoge1s3                                                                                 
bb9f8df61474d25e71fa00722318cd387396ca1736605e1248821cc0de3d3af8  hoge1s3
% shasum -a 256 hoge2s3
d2ee4703cd9698945ca7b9fe1689ea3095597eac1a0afd8dba00cac7894fdc43  hoge2s3
% shasum -a 256 hoge3s3
d4d2639ad94b0211839461226eb57a5e185829e5c4a9783c2da9f0a3da032bf5  hoge3s3

全てのファイルを取得することが出来ました。
ハッシュも一致しているので問題なくダウンロード出来ていますね。

API Gateway + S3 (HTTP Proxy)

続いて S3 静的ウェブサイトホスティングに対して HTTP プロキシ統合で API Gatway を前段に配置してみましょう。
次のようなイメージです。

API Gateway の構成ではプロキシリソースで S3 のエンドポイント URL を設定しています。

先程と同じように cURL でアクセスしてみます。
期待する動作としては API Gateway のペイロードサイズ上限 10 MB 以上の挙動が確認出来ると良い感じですが、はたして。

% curl https://nbnhq77404.execute-api.ap-northeast-1.amazonaws.com/hoge/s3/hoge1 -o hoge1
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 4096k  100 4096k    0     0  4872k      0 --:--:-- --:--:-- --:--:-- 4911k
% curl https://nbnhq77404.execute-api.ap-northeast-1.amazonaws.com/hoge/s3/hoge2 -o hoge2
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 9216k  100 9216k    0     0  8615k      0  0:00:01  0:00:01 --:--:-- 8645k
% curl https://nbnhq77404.execute-api.ap-northeast-1.amazonaws.com/hoge/s3/hoge3 -o hoge3
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    36  100    36    0     0     82      0 --:--:-- --:--:-- --:--:--    83

% shasum -a 256 hoge1                                                                    
bb9f8df61474d25e71fa00722318cd387396ca1736605e1248821cc0de3d3af8  hoge1
% shasum -a 256 hoge2
d2ee4703cd9698945ca7b9fe1689ea3095597eac1a0afd8dba00cac7894fdc43  hoge2
% shasum -a 256 hoge3
660f19c09a36ca1620054261590b73df388b95892469ff17c1caf4e50376aa02  hoge3

% curl -i https://nbnhq77404.execute-api.ap-northeast-1.amazonaws.com/hoge/s3/hoge3      
HTTP/2 500 
date: Sun, 23 Apr 2023 23:01:37 GMT
content-type: application/json
content-length: 36
x-amzn-requestid: c1c4007c-e44b-4a76-9a07-dd3a606179a0
x-amzn-errortype: InternalServerErrorException
x-amz-apigw-id: D2nQ0FcZtjMF6pA=

{"message": "Internal server error"}

11 MB のオブジェクト hoge3 は ステータスが 500 になりました。
Internal server errorが発生していますね。

Lambda 関数 URL + S3

続いては API Gateway ではなく、Lambda をプロキシとして S3 静的ウェブサイトホスティングへアクセスしてみます。
次のような構成です。

レスポンス形式ではデフォルト(BUFFERED)と、先日のアップデートのもの(RESPONSE_STREAM)のどちらも試してみたいと思います。

デフォルト

以下を参考に HTTP クライアントで S3 からオブジェクトをダウンロードする Lambda 関数を作成しました。

index.mjs

import { get } from 'http';

export const handler = async (event) => {
  return new Promise((resolve, reject) => {
    const url = 'http://iwasa-hoge-public-bucket.s3-website-ap-northeast-1.amazonaws.com/hoge0424' + event.rawPath;
    console.info(url);
    const request = get(url, (res) => {
      const chunks = [];
      
      res.on('data', (chunk) => {
        chunks.push(chunk);
      });

      res.on('end', () => {
        const data = Buffer.concat(chunks);
        
        resolve({
          statusCode: 200,
          headers: { 'Content-Type': res.headers['content-type'] },
          isBase64Encoded: true,
          body: data.toString('base64')
        });
      });

    });

    request.on('error', (error) => {
      console.error('Error fetching data:', error);
      
      resolve({
        statusCode: 500,
        body: JSON.stringify({ message: 'Error fetching data' })
      });
    });
  });
};

関数 URL を作成し、cURL でリクエストを送信してみます。

% curl -i https://qycq7nmrsmgvnwhwkhxbcpkyhm0angxc.lambda-url.ap-northeast-1.on.aws/hoge1
HTTP/1.1 200 OK
Date: Mon, 24 Apr 2023 12:34:50 GMT
Content-Type: binary/octet-stream
Content-Length: 4194304
Connection: keep-alive
x-amzn-RequestId: 3e3d2cb6-44ef-4920-a923-2d4a681e32dc
X-Amzn-Trace-Id: root=1-64467769-09e36838178ff19951483176;sampled=0;lineage=12846133:0

Warning: Binary output can mess up your terminal. Use "--output -" to tell 
Warning: curl to output it to your terminal anyway, or consider "--output 
Warning: <FILE>" to save to a file.

% curl -i https://qycq7nmrsmgvnwhwkhxbcpkyhm0angxc.lambda-url.ap-northeast-1.on.aws/hoge2       
HTTP/1.1 502 Bad Gateway
Date: Mon, 24 Apr 2023 12:34:38 GMT
Content-Type: application/json
Content-Length: 21
Connection: keep-alive
x-amzn-RequestId: 798d59e0-47ba-4160-b916-692b5e5709b5
X-Amzn-Trace-Id: root=1-6446775b-5ad2b6885da31ddd714b036f;sampled=0;lineage=12846133:0

Internal Server Error

% curl -i https://qycq7nmrsmgvnwhwkhxbcpkyhm0angxc.lambda-url.ap-northeast-1.on.aws/hoge3
HTTP/1.1 502 Bad Gateway
Date: Mon, 24 Apr 2023 12:34:46 GMT
Content-Type: application/json
Content-Length: 21
Connection: keep-alive
x-amzn-RequestId: cce5340e-50ee-4b25-9331-6d43813ac7a0
X-Amzn-Trace-Id: root=1-64467763-21a790d86ee99c88426e426c;sampled=0;lineage=12846133:0

Internal Server Error

4 MB のオブジェクトへのアクセスのみ成功しました。
9 MB と 11 MB は失敗しました。

仕様どおり 6 MB の壁がありますね。

レスポンスストリーミング

続いて先程のコードを少し修正し、レスポンスストリーミング形式で返却するようにします。
ざっくりいうとawslambda.streamifyResponseでラップしてやって、responseStreamオブジェクトを使って都度送信するように変更しています。

index.mjs

import { get } from 'http';

export const handler = awslambda.streamifyResponse(
    async (event, responseStream, context) => {
      return new Promise((resolve, reject) => {
        const url = 'http://iwasa-hoge-public-bucket.s3-website-ap-northeast-1.amazonaws.com/hoge0424' + event.rawPath;
        console.info(url);
        const request = get(url, (res) => {
          const chunks = [];
          
          res.on('data', (chunk) => {
            responseStream.write(chunk);
          });
    
          res.on('end', () => {
            responseStream.end();
          });
    
        });
    
        request.on('error', (error) => {
          console.error('Error fetching data:', error);
          
          resolve({
            statusCode: 500,
            body: JSON.stringify({ message: 'Error fetching data' })
          });
        });
      });
    }
);

cURL でアクセスしてみましょう。

% curl http://iwasa-hoge-public-bucket.s3-website-ap-northeast-1.amazonaws.com/hoge0424/hoge1 -o hoge1a  
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 4096k  100 4096k    0     0  3993k      0  0:00:01  0:00:01 --:--:-- 4019k
% curl http://iwasa-hoge-public-bucket.s3-website-ap-northeast-1.amazonaws.com/hoge0424/hoge2 -o hoge2a
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 9216k  100 9216k    0     0  7247k      0  0:00:01  0:00:01 --:--:-- 7279k
% curl http://iwasa-hoge-public-bucket.s3-website-ap-northeast-1.amazonaws.com/hoge0424/hoge3 -o hoge3a
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 11.0M  100 11.0M    0     0  7420k      0  0:00:01  0:00:01 --:--:-- 7439k

% curl https://rn3cg3rnzukafz7jisje7ysbay0wtdjv.lambda-url.ap-northeast-1.on.aws/hoge1 -o hoge1b       
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 4096k    0 4096k    0     0  1213k      0 --:--:--  0:00:03 --:--:-- 1216k
% curl https://rn3cg3rnzukafz7jisje7ysbay0wtdjv.lambda-url.ap-northeast-1.on.aws/hoge2 -o hoge2b 
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 9216k    0 9216k    0     0  2738k      0 --:--:--  0:00:03 --:--:-- 2744k
% curl https://rn3cg3rnzukafz7jisje7ysbay0wtdjv.lambda-url.ap-northeast-1.on.aws/hoge3 -o hoge3b
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 11.0M    0 11.0M    0     0  3514k      0 --:--:--  0:00:03 --:--:-- 3521k

% shasum -a 256 hoge1a                                                                                 
bb9f8df61474d25e71fa00722318cd387396ca1736605e1248821cc0de3d3af8  hoge1a
% shasum -a 256 hoge1b
bb9f8df61474d25e71fa00722318cd387396ca1736605e1248821cc0de3d3af8  hoge1b
% shasum -a 256 hoge2a
d2ee4703cd9698945ca7b9fe1689ea3095597eac1a0afd8dba00cac7894fdc43  hoge2a
% shasum -a 256 hoge2b
d2ee4703cd9698945ca7b9fe1689ea3095597eac1a0afd8dba00cac7894fdc43  hoge2b
% shasum -a 256 hoge3a
d4d2639ad94b0211839461226eb57a5e185829e5c4a9783c2da9f0a3da032bf5  hoge3a
% shasum -a 256 hoge3b
d4d2639ad94b0211839461226eb57a5e185829e5c4a9783c2da9f0a3da032bf5  hoge3b

良いですね。

4 MB、9 MB、11 MB 全てダウンロードすることが出来ました。

Lambda のタイムアウトがデフォルト 3 秒だと足りない場合があるのでそこだけ気をつけてください。

API Gateway + Lambda レスポンスストリーミング + S3

最後に Lambda レスポンスストリーミング + S3 の前段に更に API Gateway を配置しましょう。
次のような構成になります。

そもそも API Gateway で Lambda レスポンスストリーミングを受けれるのかという点はあるのですが、公式ドキュメントによるといけそうです。
この構成の場合、デフォルトレスポンスの Lambda の場合だと API Gateway (REST) のペイロードサイズ上限 10 MB の前に 6 MB の壁が問題になっていましたが、果たして。

なお、次のように Lambda 統合ではなく HTTP プロキシ統合を使っています。

% curl https://nbnhq77404.execute-api.ap-northeast-1.amazonaws.com/hoge/lambda/hoge1 -o hoge1c
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 4096k  100 4096k    0     0  2383k      0  0:00:01  0:00:01 --:--:-- 2391k
% curl https://nbnhq77404.execute-api.ap-northeast-1.amazonaws.com/hoge/lambda/hoge2 -o hoge2c
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 9216k  100 9216k    0     0  2647k      0  0:00:03  0:00:03 --:--:-- 2652k
% curl https://nbnhq77404.execute-api.ap-northeast-1.amazonaws.com/hoge/lambda/hoge3 -o hoge3c
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100    36  100    36    0     0     14      0  0:00:02  0:00:02 --:--:--    14

% shasum -a 256 hoge1a                                                                          
bb9f8df61474d25e71fa00722318cd387396ca1736605e1248821cc0de3d3af8  hoge1a
% shasum -a 256 hoge1c
bb9f8df61474d25e71fa00722318cd387396ca1736605e1248821cc0de3d3af8  hoge1c
% shasum -a 256 hoge2a
d2ee4703cd9698945ca7b9fe1689ea3095597eac1a0afd8dba00cac7894fdc43  hoge2a
% shasum -a 256 hoge2c
d2ee4703cd9698945ca7b9fe1689ea3095597eac1a0afd8dba00cac7894fdc43  hoge2c
% shasum -a 256 hoge3a
d4d2639ad94b0211839461226eb57a5e185829e5c4a9783c2da9f0a3da032bf5  hoge3a
% shasum -a 256 hoge3c
660f19c09a36ca1620054261590b73df388b95892469ff17c1caf4e50376aa02  hoge3c

% curl https://nbnhq77404.execute-api.ap-northeast-1.amazonaws.com/hoge/lambda/hoge3 -i       
HTTP/2 500 
date: Mon, 24 Apr 2023 13:23:50 GMT
content-type: application/json
content-length: 36
x-amzn-requestid: f03ceda8-663b-4f9e-9fe1-504696fc99e8
x-amzn-errortype: InternalServerErrorException
x-amz-apigw-id: D4ljvHrAtjMFfOw=

{"message": "Internal server error"}

良いですね。
10 MB 以上は取得出来ていないですが、API Gateway + Lambda でも 6 MB 以上のレスポンスを生成することが出来ました。

余談;サイズ 20 MB 以上でもいけるっぽい?

この Lambda レスポンスストリーミング、ソフトリミットでデフォルトは最大 20 MB と公式ドキュメントには記述されていました。

Response stream payloads have a soft limit of 20 MB as compared to the 6 MB limit for buffered responses.

そこで 30 MB のオブジェクトで試してみました。

% dd if=/dev/zero of=hoge5 bs=1M count=30                                                              
30+0 records in
30+0 records out
31457280 bytes transferred in 0.022999 secs (1367767294 bytes/sec)

% aws s3 cp hoge5 s3://iwasa-hoge-public-bucket/hoge0424/                                              
upload: ./hoge5 to s3://iwasa-hoge-public-bucket/hoge0424/hoge5    

% curl http://iwasa-hoge-public-bucket.s3-website-ap-northeast-1.amazonaws.com/hoge0424/hoge5 -o hoge5a
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 30.0M  100 30.0M    0     0  6635k      0  0:00:04  0:00:04 --:--:-- 6953k

% curl https://rn3cg3rnzukafz7jisje7ysbay0wtdjv.lambda-url.ap-northeast-1.on.aws/hoge5 -o hoge5b       
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 30.0M    0 30.0M    0     0  2443k      0 --:--:--  0:00:12 --:--:-- 2052k

% ls -lh
total 123568
-rw-r--r--  1 iwasa.takahito  staff    30M Apr 24 22:40 hoge5a
-rw-r--r--  1 iwasa.takahito  staff    30M Apr 24 22:41 hoge5b

% shasum -a 256 hoge5a                                                                                 
75c91b29d5522c8a97c779e50bc33f11e07ed37b2baa31c8c727016e92915c1d  hoge5a

% shasum -a 256 hoge5b
75c91b29d5522c8a97c779e50bc33f11e07ed37b2baa31c8c727016e92915c1d  hoge5b

あれ?30 MB でもダウンロード出来ていますね。
サービスクォータなどから上限の確認が出来なかったのですが、公式ドキュメントに記述の 20 MB の壁についてはもう少し調べる必要がありそうです。 ストリームの中で一度の送信で返すサイズ上限ということだろうか...。

さいごに

本日は AWS Lambda のレスポンスストリーミングを使って API Gateway + Lambda 構成で 10 MB のオブジェクトを生成してみました。

今まで API Gateway (REST) + Lambda で 6 MB の壁があったと思いますが、レスポンスストリーミングを使うことで API Gateway の上限までレスポンスを生成することが出来ました。
6 MB を 10 MB まで拡張するためにしては少し修正が大掛かりな気がしており限られたシーンでの利用にはなるかもしれないですが、出来るということだけでも覚えておくと良さそうです。