はじめに
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 | |
---|---|
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のサーバーレスを使ったシステムを作成したのですが、サーバーレス特有の問題に直面し解決できたことを皆さんと共有したいと思い記事にしてみました。