ScanでDynamoDBテーブルから1MB以上のデータを取得する方法について

LastEvaluatedKey値を使用してDynamoDBテーブルから全データを取得する方法について
2021.01.27

こんにちは、DA部の下地です。

DynamoDBテーブルからScanでデータを取得する際は1回の呼び出しで1MBまでしか取得できません。1MB以上のデータを全件取得するにはScan実行時のレスポンスに含まれているLastEvaluatedKeyを使用してループ処理を実装する必要があります。LastEvaluatedKeyについては下記サイトをご参照ください。

今回は、1MB以上のデータをScanで取得しようとした際に全件取得できなかったので改善した実装内容についてまとめます。

1. 実装環境

実行環境は以下のようになっています。

  • boto3: 1.12.33
  • python: 3.7.0
  • CLI: aws-cli/2.0.12

2. 前準備

まずはじめに、下記公式サイトのCloudFormationテンプレートを使用してDynamoDBテーブルを作成します。投入するCSVデータは10万行(11MB)ですので検証に最適です。

2.1 テンプレートの修正

上記の公式サイトのgit(aws-samples/csv-to-dynamodb)からコードをダウンロードします。そして、テンプレートファイルのParametersにそれぞれDefault値を追加します。

※Defaultを設定するのはこのテンプレートをcliで実装時に簡易的にするためなので本来は不要です

CSVToDynamo.template

{
    "AWSTemplateFormatVersion": "2010-09-09",
    "Metadata": {

    },
    "Parameters" : {
        "BucketName": {
            "Description": "Name of the S3 bucket you will deploy the CSV file to",
            "Type": "String",
            "Default": 作成するバケット名,
            "ConstraintDescription": "must be a valid bucket name."
        },
        "FileName": {
            "Description": "Name of the S3 file (including suffix)",
            "Type": "String",
            "Default": "testfile.csv",
            "ConstraintDescription": "Valid S3 file name."
        },
        "DynamoDBTableName": {
            "Description": "Name of the dynamoDB table you will use",
            "Type": "String",
            "Default": 作成するDynamoDBテーブル名,
            "ConstraintDescription": "must be a valid dynamoDB name."
        }
    },
    "Resources":
...

2.2 スタック作成とデータ投入

修正したテンプレートをAWS CLIを使用して実行します。

$ aws --profile プロファイル名 cloudformation create-stack --stack-name スタッック名 \
--template-body file://csv-to-dynamodb-master/CloudFormation/CSVToDynamo.template \
--parameters --capabilities CAPABILITY_NAMED_IAM
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:************:stack/スタッック名/******-***-****-**********"
}

環境構築が完了しましたら下記コマンドで作成したS3バケットにファイルをコピーします。コピーするとLambdaが起動しDynamoDBにデータが投入されます。

$ aws --profile プロファイル名 s3 cp csv-to-dynamodb-master/testfile.csv s3://作成するバケット名/
upload: csv-to-dynamodb-master/testfile.csv to s3://作成するバケット名/testfile.csv

3. Scanで1MB以上のデータを取得する

AWS SDK for Python (Boto3)を使用して作成したDynamoDBテーブルにScanを実行し件数の確認を行います。

まずは必要なライブラリのインポートを行います。

from boto3.session import Session
session = Session(profile_name= プロファイル名)
dynamodb = session.client("dynamodb")
table_name = 作成したDynamoDBのテーブル名

3.1 Scan実行

10万行あるDynamoDBテーブルに対してScanを実行します。

# Scan実行
dynamodb_table_counts = dynamodb.scan(TableName=table_name, Select='COUNT')
print(dynamodb_table_counts)

# 戻り値確認
print(dynamodb_table_counts)
{'Count': 4354, 'ScannedCount': 4354, 'LastEvaluatedKey': {'uuid': {'S': '512027108'}}, 'ResponseMetadata': {'RequestId': 'LOF6VVIF2MJS1FUNO5VKUENSG3VV4KQNSO5AEMVJF66Q9ASUAAJG', 'HTTPStatusCode': 200, 'HTTPHeaders': {'server': 'Server', 'date': 'Tue, 26 Jan 2021 14:23:27 GMT', 'content-type': 'application/x-amz-json-1.0', 'content-length': '80', 'connection': 'keep-alive', 'x-amzn-requestid': 'LOF6VVIF2MJS1FUNO5VKUENSG3VV4KQNSO5AEMVJF66Q9ASUAAJG', 'x-amz-crc32': '1948180869'}, 'RetryAttempts': 0}}

# 取得件数確認
print(dynamodb_table_counts['Count'])
4354

4354件のレコードしか取れていないことがわかります。

3.2 LastEvaluatedKeyについて

1回のScanでは全件取得できていませんでしたので、2回目のScan開始位置を示すLastEvaluatedKeyについて確認します。

# LastEvaluatedKey
print(dynamodb_table_counts['LastEvaluatedKey'])
{'uuid': {'S': '512027108'}}

3.3 コード修正

取得できたLastEvaluatedKey値を使用して全件取得できるまでループ処理できるようにコードを追記します。また、Scan実行時にprint文でLastEvaluatedKeyが確認できるようにします。

def get_all_data():
    response = dynamodb.scan(TableName=table_name)
    data = response['Items']
    print(response['LastEvaluatedKey']['uuid'])

    # レスポンスに LastEvaluatedKey が含まれなくなるまでループ処理を実行する
    while 'LastEvaluatedKey' in response:
        response = dynamodb.scan(TableName=table_name, ExclusiveStartKey=response['LastEvaluatedKey'])
        if 'LastEvaluatedKey' in response:
            print("LastEvaluatedKey: {}".format(response['LastEvaluatedKey']))
        data.extend(response['Items'])

    return data

では作成したコードを実行します。

get_scan_all_data = get_all_data()
LastEvaluatedKey: {'uuid': {'S': '512027108'}}
LastEvaluatedKey: {'uuid': {'S': '809463059'}}
LastEvaluatedKey: {'uuid': {'S': '740823829'}}
LastEvaluatedKey: {'uuid': {'S': '449246680'}}
LastEvaluatedKey: {'uuid': {'S': '538821303'}}
LastEvaluatedKey: {'uuid': {'S': '443480145'}}
LastEvaluatedKey: {'uuid': {'S': '719648110'}}
LastEvaluatedKey: {'uuid': {'S': '684783685'}}
LastEvaluatedKey: {'uuid': {'S': '614555299'}}
LastEvaluatedKey: {'uuid': {'S': '601599156'}}
LastEvaluatedKey: {'uuid': {'S': '552259385'}}
LastEvaluatedKey: {'uuid': {'S': '254441058'}}
LastEvaluatedKey: {'uuid': {'S': '981677615'}}
LastEvaluatedKey: {'uuid': {'S': '818131101'}}
LastEvaluatedKey: {'uuid': {'S': '172728097'}}
LastEvaluatedKey: {'uuid': {'S': '333571803'}}
LastEvaluatedKey: {'uuid': {'S': '399487626'}}
LastEvaluatedKey: {'uuid': {'S': '553535258'}}
LastEvaluatedKey: {'uuid': {'S': '655687510'}}
LastEvaluatedKey: {'uuid': {'S': '122325742'}}
LastEvaluatedKey: {'uuid': {'S': '174549424'}}
LastEvaluatedKey: {'uuid': {'S': '782208812'}}

print(len(get_scan_all_data))
100000

10万行の全レコードを取得することができました!

4. 後片付け

最後に検証環境を削除します。S3バケット内にフォルダが存在すると削除できませんのでオブジェクトを削除してスタックを削除します。

#バケット内のファイルを削除
$ aws --profile プロファイル名 s3 rm s3://作成したS3バケット名/testfile.csv
delete: s3://作成したS3バケット名/testfile.csv

# cfn スタック削除
$ aws --profile プロファイル名 cloudformation delete-stack --stack-name スタッック名

5. まとめ

1MB以上あるDynamoDBテーブルから全件データを取得する方法について実装してきました。少し脱線しましたが環境を作る際にCloudFormationテンプレートを使用できたのは良かったなと思います。同様の問題で悩んでいる方の助けになれば幸いです。

参考リンク