この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
IoT機器からセンサーデータを収集しています。 そのセンサーデータをAPI Gateway(+Lambda)で扱おうとしたところ、Lambdaのレスポンス上限6MB(同期実行時)に引っかかりました。
そこで、対策としてS3バケットのPre-Signed URLを使ってみました。
おすすめの方
- LambdaでPre-Signed URLを使いたい方
適当なデータを作成する
サンプルデータ
下記のようなデータを作成します。
{
"aaa": [
[1619600735000, 11.1],
[1619600737000, 22.1],
[1619600739000, 33.1]
],
"bbb": [
[1619600735000, 11.2],
[1619600737000, 22.2],
[1619600739000, 33.2]
],
"ccc": [
[1619600735000, 11.3],
[1619600737000, 22.3],
[1619600739000, 33.3]
],
"ddd": [
[1619600735000, 11.4],
[1619600737000, 22.4],
[1619600739000, 33.4]
],
"eee": [
[1619600735000, 11.5],
[1619600737000, 22.5],
[1619600739000, 33.5]
]
}
データ作成用のプログラム
make_data.py
import json
import sys
from random import random
def make_data(number):
aaa = []
bbb = []
ccc = []
ddd = []
eee = []
# タイムスタンプは固定にしておく
timestamp = 1619600735000
for i in range(number):
aaa.append([timestamp, round(random() * 100, 1)])
bbb.append([timestamp, round(random() * 100, 1)])
ccc.append([timestamp, round(random() * 100, 1)])
ddd.append([timestamp, round(random() * 100, 1)])
eee.append([timestamp, round(random() * 100, 1)])
data = json.dumps({
'aaa': aaa,
'bbb': bbb,
'ccc': ccc,
'ddd': ddd,
'eee': eee,
})
print(data)
if __name__ == '__main__':
if len(sys.argv) != 2:
print('please data count: $ python make_data.py 3')
else:
make_data(int(sys.argv[1]))
データを作成する
python make_data.py 100000 > data_100000.json
S3バケットに格納する
aws s3 cp data_100000.json s3://cm-fujii.genki-test
S3オブジェクトのファイルサイズ
6MBを超えていました。
- data_100000.json: 10.9 [MB]
まずは、Lambdaが6MBを超えるデータをReturnできないことを確認する
sam init
sam init \
--runtime python3.8 \
--name Lambda-Pre-Signed-Test \
--app-template hello-world \
--package-type Zip
SAMテンプレート
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Lambda-Pre-Signed-Test
Resources:
HelloWorldFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: hello_world/
Handler: app.lambda_handler
Runtime: python3.8
Timeout: 10
Policies:
- arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess
Events:
HelloWorld:
Type: Api
Properties:
Path: /hello
Method: get
HelloWorldFunctionLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub /aws/lambda/${HelloWorldFunction}
Outputs:
HelloWorldApi:
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
Lambdaコード
app.py
import json
import boto3
S3_BUCKET_NAME = 'cm-fujii.genki-test'
S3_OBJECT_KEY = 'data_100000.json'
s3 = boto3.resource('s3')
def lambda_handler(event, context):
obj = s3.Object(S3_BUCKET_NAME, S3_OBJECT_KEY)
data = obj.get()['Body'].read().decode('utf-8')
print(obj)
print(len(data) / 1024 / 1024)
return {
"statusCode": 200,
"body": data,
}
デプロイ
sam build
sam deploy \
--template-file template.yaml \
--stack-name Lambda-Pre-Signed-Test-Stack \
--s3-bucket cm-fujii.genki-deploy \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset
APIエンドポイントを取得
$ aws cloudformation describe-stacks \
--stack-name Lambda-Pre-Signed-Test-Stack \
--query 'Stacks[].Outputs'
[
[
{
"OutputKey": "HelloWorldApi",
"OutputValue": "https://awvil3t6nc.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/",
}
]
]
APIアクセスすると、Internal server error になる
$ curl https://awvil3t6nc.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "Internal server error"}
CloudWatch Logの様子
下記のメッセージが残っていました(抜粋)。
LAMBDA_RUNTIME Failed to post handler success response. Http response code: 413.
RuntimeError: Failed to post invocation response
Response payload size (11450971 bytes) exceeded maximum allowed payload size (6291556 bytes).
Function.ResponseSizeTooLarge
Lambdaの最大レスポンスサイズ(6MB)より多いデータは返せませんでした。
Pre-Signed URLを使う
Lambdaコード
Lambdaコードを書き換えて、Pre-Signed URLを返すようにします。
app.py
import json
import boto3
S3_BUCKET_NAME = 'cm-fujii.genki-test'
S3_OBJECT_KEY = 'data_100000.json'
s3 = boto3.client('s3')
def lambda_handler(event, context):
url = s3.generate_presigned_url(
'get_object',
Params={
'Bucket': S3_BUCKET_NAME,
'Key': S3_OBJECT_KEY
},
ExpiresIn=3600
)
return {
"statusCode": 200,
"body": url,
}
ExpiresIn
は1時間(3600秒)としましたが、6時間を超える値を設定する場合は、適切な認証情報を用いてください。
HTTPステータスは200
を返すようにしていますが、303
でも良さそうです。
デプロイ
sam build
sam deploy \
--template-file template.yaml \
--stack-name Lambda-Pre-Signed-Test-Stack \
--s3-bucket cm-fujii.genki-deploy \
--capabilities CAPABILITY_NAMED_IAM \
--no-fail-on-empty-changeset
APIアクセスすると、Pre-Signed URLを取得できる
$ curl https://awvil3t6nc.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
https://s3.ap-northeast-1.amazonaws.com/cm-fujii.genki-test/data_100000.json?AWSAccessKeyId\=xxxxx
※\=
部分は=
だけです。(\=
にしないと、本ブログ上で表示されませんでした)
S3オブジェクトを取得する
$ curl -o s3_object.json "https://s3.ap-northeast-1.amazonaws.com/cm-fujii.genki-test/data_100000.json?AWSAccessKeyId\=xxxxx"
バッチリ取得できました!!
$ ls -lh s3_object.json
*** 11M *** s3_object.json