Edited at

S3のイベントトリガーでのLambda発火についての実験

More than 1 year has passed since last update.


S3のイベントトリガーでのLambda発火についての実験


内容

s3へファイル(オブジェクト)を保存した時に、Lambdaを発火させて、ゴニョゴニョする。というのは、よくあるアーキテクチャの1つです。

この場合は、Lambdaは「At Least Once」(最低1回)で起動するというのは周知のことですが、

「(LambdaはAt Lease Onceだけど)そもそもLambdaを発火させるため、S3のイベント自体が発行されないことがある」と聞いたので、それを実験してみました。


事前情報

こちらにも「イベント抜け」と記載してあります

https://qiita.com/kkimura/items/92833436349d8fc5ad29


やってみる


  1. EC2上に空の100万ファイル作成

  2. s3バケット作成

  3. DynamoDBのテーブル作成

  4. Lambda作成とトリガー設定

  5. aws s3 syncで100万ファイルをs3へコピー

  6. 結果確認


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は抜けなく起動した。

ただし、少なくとも抜けが起こることは確認されているので、絶対に抜けが許容できないシステムなら別途対策が必要です。(その話はまた後日) その対策がめんどくさいなら、「抜けが報告された時点で手動で対応する」と決めるのもありです。