こんにちは、広野です。
Amazon DynamoDB は大量データからのキーバリューマッチングは超高速で素晴らしいのですが、クエリにかなりの制約があるので扱いが非常に難しいです。
実はセカンダリインデックスを使ったデータ削除もクエリ一発でできないので、スクリプトを組む必要があります。
今回はそんなスクリプトの紹介をしたいと思います。
やりたいこと
以下のサンプル DynamoDB テーブルがあるとします。
通常、category, user, id, checked の 4つの情報を元に検索をしています。※今回の説明で直接的に必要ない他の属性は割愛
categoryuser パーティションキー |
checkedid ソートキー |
id 属性(グローバルセカンダリインデックスのパーティションキー) |
---|---|---|
AAA#user1 | checked#001 | 001 |
AAA#user1 | unchecked#002 | 002 |
AAA#user2 | unchecked#001 | 001 |
AAA#user2 | checked#002 | 002 |
AAA#user3 | checked#005 | 005 |
ここで、以下のように id が 002 のデータだけを削除したいとします。
categoryuser パーティションキー |
checkedid ソートキー |
id 属性(グローバルセカンダリインデックスのパーティションキー) |
---|---|---|
AAA#user1 | unchecked#002 | 002 |
AAA#user2 | checked#002 | 002 |
パーティションキーに格納されている category や user は大量にパターンがあるため、パーティションキー、ソートキーによる検索は難しい状況です。そのため、id をパーティションキーにしたグローバルセカンダリインデックスを作成し、該当のデータを削除したいと思います。
ところが、仕様上、セカンダリインデックスで条件を指定したデータを 1本のクエリで削除することができません。
削除するには、一旦削除対象のデータを取得し、メインのパーティションキー、ソートキーを条件にしてベタに削除処理をループさせることになります。
上の例では、以下 2 つの条件で削除クエリをかけます。
- categoryuser が AAA#user1 で、checkedid が unchecked#002 であるもの
- categoryuser が AAA#user2 で、checkedid が checked#002 であるもの
さらに、追加要件として削除したい対象の id が複数件(大量)にあったとしましょう。上の例では id は 1件でしたが。
それをコードで一気に処理するには、以下のアルゴリズムが例なります。
- 削除対象の id をファイルにリストアップさせ、スクリプトに読み込ませる
- リストアップされた id 分、削除処理をループさせる
- 1つの id から取得したデータに含まれている categoryuser, checkedid のリストに対して、データを削除する(1件1件なのでループ)
言葉で書いてもわかりにくいので、以降はコードを見て頂いた方が早いと思います。
つくったもの
以下の Python スクリプトです。以下、パラメータは適宜修正してください。
TableName は DynamoDB テーブル名
FileName は 読み込みたいファイル名
DynamoDB に対してアクセスできること、Python、boto3 がインストールされていることが実行環境として必要です。必要に応じて DynamoDB のリージョン名は変更します。
import json import boto3 import decimal from boto3.dynamodb.conditions import Key dynamodb = boto3.resource("dynamodb", region_name="ap-northeast-1") table = dynamodb.Table("TableName") # JSONファイルを読み込む with open("FileName") as f: data = json.load(f, parse_float = decimal.Decimal) # JSONデータの各行から指定した id のデータを取得する for row in data: id_value = row.get("id", None) if id_value: print(f"Searching for items with id: {id_value}") output = table.query(IndexName="id-index", KeyConditionExpression=Key("id").eq(id_value)) # 該当する項目があれば削除する for item in output.get("Items", []): print(f"Found item: {item}") # categoryuser がパーティションキー、checkedid がソートキー res = table.delete_item( Key = { "categoryuser": item["categoryuser"], "checkedid": item["checkedid"] } ) print(f"Deleted item: {item}")
※エラー処理は省略しています。
削除対象とする id は JSON データで以下のフォーマットでファイルとして用意します。実際に使ったときには他のデータも入っていたため JSON にしましたが、id だけなら単純な配列の方が適切だと思います。
[ {"id":"002"}, {"id":"005"}, {"id":"006"} ]
これで、 削除したい id をファイルにリストアップすれば、DynamoDB に対してセカンダリインデックスベースで削除をかけられるようになりました。
まとめ
いかがでしたでしょうか?
本件、急ぎで必要になったため同僚や ChatGPT の助けも借りて即席でつくったものなんです。一応動いていますので、参考になるかと思います。
本記事が皆様のお役に立てれば幸いです。