#S3のイベントトリガーでのLambda発火についての実験
内容
s3へファイル(オブジェクト)を保存した時に、Lambdaを発火させて、ゴニョゴニョする。というのは、よくあるアーキテクチャの1つです。
この場合は、Lambdaは「At Least Once」(最低1回)で起動するというのは周知のことですが、
「(LambdaはAt Lease Onceだけど)そもそもLambdaを発火させるため、S3のイベント自体が発行されないことがある」と聞いたので、それを実験してみました。
事前情報
こちらにも「イベント抜け」と記載してあります
https://qiita.com/kkimura/items/92833436349d8fc5ad29
やってみる
- EC2上に空の100万ファイル作成
- s3バケット作成
- DynamoDBのテーブル作成
- Lambda作成とトリガー設定
- aws s3 syncで100万ファイルをs3へコピー
- 結果確認
EC2上に空の100万ファイル作成
20秒位で100万ファイル作成できました
$ mkdir s3-test
$ cd s3-test
$ time seq 1 1000000 | xargs touch
s3バケット作成
別になんでもいいですが、「s3-test-qiita」というバケットを作成し、
「files」というフォルダ内に作成したファイルをコピーするようにします。
DynamoDBのテーブル作成
テーブル名もなんでもいいですが、「s3-test-qiita」でパーティションキーを「key(string)」としています。
s3 sync実行直前にRCU/WCUを調整したいため、どちらもautoscalingなしで「1」としています。
Lambda作成とトリガー設定
python3.6で以下の通り書きました。メモリ割り当ては最小ですが、
DymamoDBのスロットリングでリトライが発生する場合があるため、Timeoutは1分にしています。
s3のイベントタイプは「Object Created(All)」としています。
# -*- coding: utf-8 -*-
import json
import boto3
dynamodb = boto3.resource('dynamodb')
DDB_TABLE = "s3-test-qiita"
table = dynamodb.Table(DDB_TABLE)
def lambda_handler(event, context):
bucket = event['Records'][0]['s3']['bucket']['name']
key = event['Records'][0]['s3']['object']['key']
table.update_item(
Key= {
'key': key
},
UpdateExpression="ADD #name :increment",
ExpressionAttributeNames={
'#name':'count'
},
ExpressionAttributeValues={
":increment": 1
},
ReturnValues="UPDATED_NEW"
)
動作確認テスト(必要であれば)
作成したs3バケットにファイルを保存して、DynamoDBにレコードが作成されていることを確認します。
さらに、同じファイルを再度保存(上書き)して、DynamoDBのレコードの「count]がインクリメントされていることを確認します。
EC2からs3 syncの実行
その前にDynamoDBのWCU/RCUを上げます。検証終わり次第すぐに消すので、一時的にそれぞれ500に変更しました。
$ cd ../
$ aws s3 sync s3-test/ s3://s3-test-qiita/files/
upload: s3-test/10279 to s3://s3-test-qiita/files/10279
upload: s3-test/102792 to s3://s3-test-qiita/files/102792
upload: s3-test/102793 to s3://s3-test-qiita/files/102793
upload: s3-test/102791 to s3://s3-test-qiita/files/102791
upload: s3-test/102794 to s3://s3-test-qiita/files/102794
大体1時間かかりました。DynamoDBのWrite capacityを見ると、250を常に消費しているようでした。
つまり、1秒あたり250ファイルをputできたようです。ただし、s3へのPUTは小さいファイルがあるなら、圧縮する。並列で実行するなどベストプラクティスがあるので、これでS3が早いとか遅いとかの話をすべきじゃないですね。
あと、Lambdaはスロットリングもエラーも発生してないことはcloudwatchから確認済みです。
結果確認
さて、結果の確認方法ですが、DynamoDBには集計機能がないので、個人的な勉強も含めてEMRでやってみたいと思います。
他には、Scanで全レコード取り出して集計(1MB制限に注意)やdatapipelineでs3に吐いて集計(フォーマット注意)などありますね。
EMRを起動後、SSH接続してhiveを実行
外部テーブルの作成
[hadoop@ip-172-31-43-65 ~]$ hive
hive>
CREATE EXTERNAL TABLE hivetable (col1 string, col2 bigint)
STORED BY 'org.apache.hadoop.hive.dynamodb.DynamoDBStorageHandler'
TBLPROPERTIES ("dynamodb.table.name" = "s3-test-qiita",
"dynamodb.column.mapping" = "col1:key,col2:count");
総レコード数の算出
hive> select count(col1) from hivetable;
Query ID = hadoop_20180502104919_f1b374a8-cdd5-4668-a110-2851e0e9fbae
Total jobs = 1
Launching Job 1 out of 1
Tez session was closed. Reopening...
Session re-established.
Status: Running (Executing on YARN cluster with App id application_1525254801764_0002)
----------------------------------------------------------------------------------------------
VERTICES MODE STATUS TOTAL COMPLETED RUNNING PENDING FAILED KILLED
----------------------------------------------------------------------------------------------
Map 1 .......... container SUCCEEDED 2 2 0 0 0 0
Reducer 2 ...... container SUCCEEDED 1 1 0 0 0 0
----------------------------------------------------------------------------------------------
VERTICES: 02/02 [==========================>>] 100% ELAPSED TIME: 17.15 s
----------------------------------------------------------------------------------------------
OK
1000000
Time taken: 26.7 seconds, Fetched: 1 row(s)
お、1,000,000レコードとなりました!!!少なくとも今回の検証では、イベント通知抜けというのはなかったようです。
Lambda複数回起動の特定
hive> select col2, count(col2) from hivetable group by col2;
Query ID = hadoop_20180502105243_ab71ff5d-2493-4c58-8e5b-2bea7d657c0d
Total jobs = 1
Launching Job 1 out of 1
Status: Running (Executing on YARN cluster with App id application_1525254801764_0002)
----------------------------------------------------------------------------------------------
VERTICES MODE STATUS TOTAL COMPLETED RUNNING PENDING FAILED KILLED
----------------------------------------------------------------------------------------------
Map 1 .......... container SUCCEEDED 2 2 0 0 0 0
Reducer 2 ...... container SUCCEEDED 1 1 0 0 0 0
----------------------------------------------------------------------------------------------
VERTICES: 02/02 [==========================>>] 100% ELAPSED TIME: 15.10 s
----------------------------------------------------------------------------------------------
OK
1 999988
2 12
Time taken: 16.221 seconds, Fetched: 2 row(s)
```
お、100万回のうち、999988はLambdaが1回だけ起動し、12回はLambdaが2回起動してしまったようです。
1回のPUTで、s3のイベントが複数回通知されたのか、イベント通知は1回だけどもLambdaが2回起動したのかは不明です
## 結果
少なくとも今回の検証では、100万オブジェクトをPUTしても、Lambdaは抜けなく起動した。
ただし、少なくとも抜けが起こることは確認されているので、絶対に抜けが許容できないシステムなら別途対策が必要です。(その話はまた後日) その対策がめんどくさいなら、「抜けが報告された時点で手動で対応する」と決めるのもありです。