LoginSignup
7
0

More than 1 year has passed since last update.

DynamoDBにデータを追加する際、「エラー」が出てこない問題に対する解決策

Last updated at Posted at 2022-12-04

はじめに

Lambdaを使ってDynamoDBにデータを追加する簡易システムを作っていた際、エラーログすら出力されない問題に当たったので、ここではその知見共有をしたいと思います。

1. 今回使用するDynamoDBの構成

order_infoテーブル

今回は、order_infoテーブルにレコードが追加されるたびにプライマリーキーであるid属性を連番設定し、数を1ずつ増やしていきます。

id created_at host name price quantity
number number string string number string

id_sequenceテーブル

order_infoテーブルのid属性の連番を管理するためのテーブルです。
※DynamoDBには、オートインクリメントの機能がないため、Lambdaでコードを読み込むと数字をインクリメントするという処理にします。

target_tabele sequence
string number

2. API Gatewayの設定

下記Lambdaを指定した、API Gatewayを準備します。
(※この際、CORSを有効にすることを忘れないようにしてください。)

3. Lambdaのコード

import boto3
import json
import base64
import time
import decimal

dynamodb = boto3.resource('dynamodb')

# DynamoDBのシーケンス用のテーブル(id_sequence)で連番を更新して返す関数
def next_seq(DatabaseTable, DatabaseTablename):
    response = DatabaseTable.update_item(
        Key = {
            'target_table': DatabaseTablename
        },
        UpdateExpression = "set sequence = sequence + :val",
        ExpressionAttributeValues = {
            ':val' : 1
        },
        ReturnValues='UPDATED_NEW'
    )
    return response['Attributes']['sequence']

def lambda_handler(event, context):
    try:
        body = event['body']
        
        while True:
            # event引数で受け取った値(body)がFalseなら何もしない
            if not body:
                pass
            
            # event引数で受け取った値(body)がTrueなら
            else:
                seqtable = dynamodb.Table('id_sequence')
                
                # デバッグ用
                print('ここまで成功①')
                
                nextseq = next_seq(seqtable, 'order_info')
                
                # デバッグ用
                print('ここまで成功②')
                
                # もしbodyの情報が暗号化されていたらデコードする
                if event['isBase64Encoded']:
                    body = base64.b64decode(body)
                
                decoded = json.loads(body)
                
                # ①商品の商品名データを格納する空の配列データを用意する
                product_name = []
                # ②商品の個数データを格納する空の配列データを用意する
                product_quantity = []
                # ③商品の金額データを格納する空の配列データを用意する
                product_sum = []
                
                
                for item_num in range(len(decoded['Item'])):
                    product_name.append(decoded['Item'][item_num][0])
                    product_quantity.append(decoded['Item'][item_num][1])
                    product_sum.append(decoded['Item'][item_num][2])

                
                product_name_existing = []
                product_quantity_existing = []
                product_sum_existing = []
                
                # ②商品の個数データが入っている配列のkeyとvalueを取得する
                for key,value in enumerate(product_quantity):
                    if (value == 0) or (value == "0"):
                        pass
                    else:
                        product_name_existing.append(product_name[key])
                        product_quantity_existing.append(value)
                        product_sum_existing.append(product_sum[key])
                
                host = event['requestContext']['http']['sourceIp']
                now = time.time()
                usertable = dynamodb.Table('order_info')
                
                
                for i in range(len(product_quantity_existing)):
                    
                    # dynamodbに設定するデータを定義する
                    dynamo_items = {
                        'id': nextseq,
                        'name': product_name_existing[i],
                        'price': product_sum_existing[i],
                        'quantity': product_quantity_existing[i],
                        'created_at': decimal.Decimal(str(now)),
                        'host': host,
                    }
                    nextseq = next_seq(seqtable, 'order_info')
                    usertable.put_item(Item = dynamo_items)
                
                
            return {
                "statusCode": 200,
                "headers":{
                    "Content-Type": "application/json",
                    "Access-Control-Allow-Origin": '*'
                },
                "body": json.dumps('Success send DynamoDB!!')
            }
            break
    
    except:
        return {
            "statusCode": 200,
            "headers":{
                "Content-Type": "application/json",
                "Access-Control-Allow-Origin": '*'
            },
            "body": json.dumps('Hello from Lambda!2')
        }

4. 失敗

コードは合っているはずなのにDynamoDBにデータが追加されません。

5.ログを確認

CloudWatch Logsにログを見にいきます。

2022-11-01T17:21:21.289+09:00
START RequestId:~~~中略~~~

2022-11-01T17:21:21.293+09:00
ここまで成功①

2022-11-01T17:21:21.364+09:00
END RequestId:~~~中略~~~

どうやら、シーケンステーブルの連番を更新して返す関数の処理に失敗していそうだということはわかりますが、肝心のログが出力されていません。
自分はここで丸一日悩みました。

6.原因

結論から言うとDynamoDBの予約語に引っ掛かっていました。
https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/ReservedWords.html
上の記事によると、DynamoDBの予約語はこれだけの数存在しており、指定の名前を属性名に使用すると正常にデータを追加できないどころか、ログすら出力されないので皆さんも気をつけましょう!
DynamoDBで属性名にあらかじめ予約語を使えない仕様にして欲しいです。

7.属性名の修正

id_sequenceテーブル

修正前

target_tabele sequence
string number

修正後

target_tabele current_id
string number

Lambdaのコード変更

def next_seq(DatabaseTable, DatabaseTablename):
    response = DatabaseTable.update_item(
        Key = {
            'target_table': DatabaseTablename
        },
        # 修正箇所①と②(sequenceをcurrent_idに)
        UpdateExpression = "set current_id = current_id + :val",
        ExpressionAttributeValues = {
            ':val' : 1
        },
        ReturnValues='UPDATED_NEW'
    )
    # 修正箇所③(sequenceをcurrent_idに)
    return response['Attributes']['current_id']

8.成功

AWS CLIを使ってデータを追加することができたか確認してみます。
指定のDynamoDBテーブルにデータが追加されたことを確認できました。

id_sequenceテーブル

aws dynamodb scan --table-name id_sequence
"Items": [
        {
            "current_id": {
                "N": "2"
            },
            "target_table": {
                "S": "order_info"
            }
        }
    ],

order_infoテーブル

aws dynamodb scan --table-name order_info
"Items": [
        {
            "quantity": {
                "S": "1"
            },
            "created_at": {
                "N": "1667283801.4577591"
            },
            "host": {
                "S": "hogehoge"
            },
            "id": {
                "N": "1"
            },
            "price": {
                "N": "110"
            },
            "name": {
                "S": "商品①"
            }
        },

まとめ

 今回初めて、AWSのサーバーレスを使ったシステムを作成したのですが、サーバーレス特有の問題に直面し解決できたことを皆さんと共有したいと思い記事にしてみました。

7
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
0