はじめに
以前の記事で書いたように、DynamoDBはちゃんとキャパシティ管理さえしてあげれば、サーバレスで高性能でかなり良い感じなデータベースだ。
ただし、データベースと言ってもRDBとは違うので、そちらに慣れている人にはハマりどころが多々ある。
DynamoDBではどんなCRUD操作が可能かを確認しておこう。
前提条件
前提条件はあまりないが、以下の記事くらいを読んでおくと入りが良いとおもう。
- 【Qiita】AWS DynamoDBと仲良くなれるかもしれないまとめ
特に、LSI、GSIはちゃんと理解しておかないと混乱することになるのでしっかり覚えておこう。
事前準備
今回は、以下のような仕様のデータベースを管理する前提とする。
- 社員ID(項目名:
id
)をキーとする社員管理テーブルを想定 - 社員は複数の部署(項目名:
department
)に所属可能である - 社員の年齢をデータベースに持っておく
上記から、ハッシュキーはid
とする。
と同時に、2つ目の要件を満たすために、department
をレンジキーに指定する。
さらに、どんな検索要件に対応できるか確認するために、GSIとLSIにdepartmentを設定したテーブルをそれぞれ用意する。
※これ、書いてから気付いたが、別にテーブル分けなくてもそれぞれのインデックスを設定すれば1つのテーブルでも実験できたな……。
上記のテーブルを用意するTerraformは以下のような感じで準備。
################################################################################
# DynamoDB Table #
################################################################################
resource "aws_dynamodb_table" "test" {
name = "test-table"
billing_mode = "PROVISIONED"
read_capacity = 1
write_capacity = 1
hash_key = "id"
range_key = "department"
attribute {
name = "id"
type = "S"
}
attribute {
name = "department"
type = "S"
}
local_secondary_index {
name = "department_lsi"
range_key = "department"
projection_type = "ALL"
}
global_secondary_index {
name = "department_gsi"
hash_key = "department"
write_capacity = 1
read_capacity = 1
projection_type = "ALL"
}
}
さらに、スクリプトで以下のような感じでベースとなるアイテムをロードしておく。
def prepare_001():
try:
with table.batch_writer() as batch:
batch.put_item(Item={ 'id': '00001', 'department': '11111', 'age': 35 })
batch.put_item(Item={ 'id': '00001', 'department': '22222', 'age': 35 })
batch.put_item(Item={ 'id': '00002', 'department': '11111', 'age': 28 })
batch.put_item(Item={ 'id': '00003', 'department': '11111', 'age': 34 })
batch.put_item(Item={ 'id': '00004', 'department': '22222', 'age': 39 })
return "OK"
except Exception as error:
return error
実験開始
重複したキーの項目をPUTする
def test_001_01():
try:
table.put_item(Item={ 'id': '00001', 'department': '11111', 'age': 36 })
return "OK"
except Exception as error:
return error
結果:重複したキーの項目が更新された。
単一項目の非キー項目のアップデート
def test_001_02():
try:
option = {
'Key': {'id': '00001', 'department': '11111'},
'UpdateExpression': 'set #age = :age',
'ExpressionAttributeNames': {
'#age': 'age'
},
'ExpressionAttributeValues': {
':age': 37
}
}
table.update_item(**option)
return "OK"
except Exception as error:
return error
結果:問題なく実行可能。
単一項目のキー項目のアップデート
def test_001_03():
try:
option = {
'Key': {'id': '00001', 'department': '11111'},
'UpdateExpression': 'set #department = :department, #age = :age',
'ExpressionAttributeNames': {
'#department': 'department',
'#age': 'age'
},
'ExpressionAttributeValues': {
':department': "33333",
':age': 37
}
}
table.update_item(**option)
return "OK"
except Exception as error:
return error
結果:エラーになる。キー項目の更新は delete
→ put
するしかないようだ(詳細は後述)。
'An error occurred (ValidationException) when calling the UpdateItem operation: One or more parameter values were invalid: Cannot update attribute department. This attribute is part of the key'
ハッシュキーのみでqueryする
def test_001_04():
try:
response = table.query(
KeyConditionExpression=Key('id').eq('00001')
)
return response['Items']
except Exception as error:
return error
結果:同一ハッシュキーのアイテムが複数返却される。
## ハッシュキー+レンジキーでqueryする
def test_001_05():
try:
response = table.query(
KeyConditionExpression=Key('id').eq('00001') & Key('department').eq('22222')
)
return response['Items']
except Exception as error:
return error
結果:単一アイテムに絞り込まれて返却される
ハッシュキー+レンジキー以外の項目でqueryする
def test_001_06():
try:
response = table.query(
KeyConditionExpression=Key('id').eq('00001'),
FilterExpression=Attr('age').eq(35)
)
return response['Items']
except Exception as error:
return error
結果: queryで指定した条件で絞ることができる
LSIでレンジキーのみ指定してqueryする
def test_001_07():
try:
response = table.query(
IndexName="department_lsi",
KeyConditionExpression=Key('department').eq('11111')
)
return response['Items']
except Exception as error:
return error
結果: 不可能。LSIの場合、ハッシュキーが必要になるためエラーになる。
'An error occurred (ValidationException) when calling the Query operation: Query condition missed key schema element: id'
GSIでレンジキーのみ指定してqueryする
def test_001_08():
try:
response = table.query(
IndexName="department_gsi",
KeyConditionExpression=Key('department').eq('11111')
)
return response['Items']
except Exception as error:
return error
結果: GSIの場合はハッシュキー不要なので、セカンダリインデックスで指定したアイテムが複数返却される
GSIでレンジキーのみ指定してqueryしてさらに件数を2件までに絞る
def test_001_09():
try:
response = table.query(
IndexName="department_gsi",
KeyConditionExpression=Key('department').eq('11111'),
Limit=2
)
return response['Items']
except Exception as error:
return error
結果: 想定通りの件数で絞られる
キー項目を更新できない代わりに、delete → put する
def test_001_10():
try:
table.delete_item(Key={ 'id': '00001', 'department': '22222' })
table.put_item(Item={ 'id': '00001', 'department': '33333', 'age': 36 })
return "OK"
except Exception as error:
return error
結果: 問題なく動作。ただし、DynamoDBは結果整合でしかないため、delete前にput結果が取得されることを考慮しておく。