この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは!DA(データアナリティクス)事業本部 サービスソリューション部の大高です。
最近、AWS SDK for JavaScript v3を利用してDynamoDBの操作をしているのですが、指定したパーティションキーのアイテムを削除する機会があったので、やり方をまとめておきたいと思います。
前提条件
今回試した環境は以下のとおりです。
- @aws-sdk/client-dynamodb
- ^3.121.0"
また、DynamoDB上のテーブルは、以下のようなテーブルを前提とします。
キー | 名前 | 備考 |
---|---|---|
パーティションキー | id (String) | |
ソートキー | type (String) | |
属性 | cap (Number) | |
ローカルセカンダリインデックス(LSI) | IdTypeIndex | パーティションキー:id (String), ソートキー:type (String) |
やりたいこと
今回やりたいことは、前述のような「パーティションキー」と「ソートキー」の両方で「プライマリーキー」を構成しているテーブルにおいて、「パーティションキー」を指定することで、複数アイテムを削除したいと思います。
例えば、以下のようなアイテムがあった場合に、id
が51f66320-7233-48e9-a718-733735da4249
である、4つのアイテムを削除したいと考えています。
id (パーティションキー) | type (ソートキー) | cap |
---|---|---|
51f66320-7233-48e9-a718-733735da4249 | White Mage | 80 |
51f66320-7233-48e9-a718-733735da4249 | Scholar | 80 |
51f66320-7233-48e9-a718-733735da4249 | Astrologian | 80 |
51f66320-7233-48e9-a718-733735da4249 | Sage | 80 |
a9a7b81a-8d7d-44c1-bd98-a6a3a61a4abc | White Mage | 90 |
a9a7b81a-8d7d-44c1-bd98-a6a3a61a4abc | Scholar | 90 |
a9a7b81a-8d7d-44c1-bd98-a6a3a61a4abc | Astrologian | 90 |
a9a7b81a-8d7d-44c1-bd98-a6a3a61a4abc | Sage | 90 |
そもそも、どのAPIを利用して削除すればよいのか
上記のようなケースでは、どのAPIを利用して削除すればよいのでしょうか?
結論としては、以下となりました。
QueryCommand
を利用して、指定した「パーティションキー」を持つアイテムを取得する- 取得したアイテムの「パーティションキー」と「ソートキー」を利用して
BatchWriteItemCommand
を発行し、該当アイテムを削除する
少し細かく解説していきます。
DeleteItemCommand は使えるか?
まず初めに、公式ドキュメントで、アイテムの削除に関するページを探すと以下のページが見つかりました。
ここではDeleteItemCommand
を利用したアイテムの削除が紹介されていますが、一方で以下のような注記が記載されています。
次のコード例では、-KEY_NAME-パーションとソートキーの両方ではなく、パーティションキーのみで構成されたプライマリキーを持つアイテムを削除します 表にパーティションキーとソートキーで構成されるプライマリキーがある場合は、ソートキーの名前と属性も指定する必要があります。
つまり、DeleteItemCommand
を利用する場合、今回のような「パーティションキー」と「ソートキー」でプライマリーキーが構成されている場合には、明示的に両方のキーと値を指定する必要が出てきます。
今回のケースだと、4回DeleteItemCommand
を発行する必要が出てきます。アイテムの数が少なければあり得るかもしれませんが、アイテム数が多い場合にはリクエスト回数が増えるので避けたいところです。
一括処理のための BatchWriteItemCommand がある
今回のような一括で処理を行うケースのために、BatchWriteItemCommand
というコマンドがあります。
このコマンドを利用することで一気にアイテムを削除することがあります。ただ、利用する際には、やはり「パーティションキー」と「ソートキー」が必要となってくるので、事前に該当のキー値を取得する必要があります。
(再掲)結局どうすればいい?
つまり、以下のようにすることで対応が可能となります。
QueryCommand
を利用して、指定した「パーティションキー」を持つアイテムを取得する- 取得したアイテムの「パーティションキー」と「ソートキー」を利用して
BatchWriteItemCommand
を発行し、該当アイテムを削除する
やってみる
QueryCommand
QueryCommand
については、基本的には以前に下記エントリで記載したものと同様です。
下記のようなコードで、特定のid
をキーとしてテーブルにクエリを発行し、アイテムを取得します。
import {
AttributeValue,
BatchWriteItemCommand,
BatchWriteItemCommandInput,
BatchWriteItemCommandOutput,
DynamoDBClient,
QueryCommand,
QueryCommandInput,
QueryCommandOutput,
WriteRequest,
} from '@aws-sdk/client-dynamodb'
import { marshall } from '@aws-sdk/util-dynamodb'
export type Item = {
[key: string]: AttributeValue
}
export function toDynamoDbItem<T>(t: T): Item {
const item = marshall(t)
return item
}
export async function sleep(msec: number): Promise<unknown> {
return new Promise((resolve) => {
setTimeout(resolve, msec)
})
}
const dynamodb = new DynamoDBClient({
apiVersion: '2012-08-10',
})
function toExpressionAttributeValues(attributes): Item {
const exAttributes = {}
for (const [key, value] of Object.entries(attributes)) {
exAttributes[':' + key] = value
}
const expressionAttributeValues: Item = toDynamoDbItem(exAttributes)
return expressionAttributeValues
}
async function query(input: QueryCommandInput): Promise<QueryCommandOutput> {
const command: QueryCommand = new QueryCommand(input)
const output: QueryCommandOutput = await dynamodb.send(command)
return output
}
export async function queryItems(tableName: string, id: string): Promise<QueryCommandOutput> {
let result: QueryCommandOutput = null
const queryAttributes = {
id: id,
}
const queryCommandInput: QueryCommandInput = {
TableName: tableName,
KeyConditionExpression: 'id = :id',
ExpressionAttributeValues: toExpressionAttributeValues(queryAttributes),
}
const recursiveQuery = async (queryCommandInput: QueryCommandInput) => {
const response: QueryCommandOutput = await query(queryCommandInput)
if (!result) {
result = response
} else {
result.Items.push(...response.Items)
}
// 追加Scanデータがある場合には、追加Scanを実施する(Scan結果が1MBを超えるケース)
// ref.) https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Scan.html
if (response.LastEvaluatedKey) {
// APIリクエストレートを制御する
await sleep(500)
queryCommandInput.ExclusiveStartKey = response.LastEvaluatedKey
await recursiveQuery(queryCommandInput)
}
}
await recursiveQuery(queryCommandInput)
// 複数回Scanを考慮して手動設定する
result.Count = result.Items.length
return result
}
これにより、テーブルから条件に一致するアイテムが取得できるので、以下のようにtableItems
としてArrayオブジェクトに変換します。
export interface RoleTableKey {
id: string
type: string
}
export function fromDynamoDbItems<T>(items: Item[]): T[] {
const ret: T[] = items.map((item: Item) => fromDynamoDbItem<T>(item))
return ret
}
const queryResult: QueryCommandOutput = await queryItems(tableName, id)
const tableItems: RoleTableKey[] = fromDynamoDbItems<RoleTableKey>(queryResult.Items)
BatchWriteItemCommand
次にBatchWriteItemCommandについてです。
async function batchWriteItem(input: BatchWriteItemCommandInput): Promise<BatchWriteItemCommandOutput> {
const command: BatchWriteItemCommand = new BatchWriteItemCommand(input)
const output: BatchWriteItemCommandOutput = await dynamodb.send(command)
return output
}
function getDeleteRequests(tableItems: RoleTableKey[]) {
const deleteRequests: WriteRequest[] = []
tableItems.forEach((item: RoleTableKey) => {
deleteRequests.push({
DeleteRequest: {
Key: toDynamoDbItem({
id: item.id,
type: item.type,
}),
},
})
})
return deleteRequests
}
export async function deleteTableItems(
tableName: string,
tableItems: RoleTableKey[]
): Promise<BatchWriteItemCommandOutput> {
const deleteRequests: WriteRequest[] = getDeleteRequests(tableItems)
const input: BatchWriteItemCommandInput = {
RequestItems: {
[tableName]: deleteRequests,
},
}
const result: BatchWriteItemCommandOutput = await batchWriteItem(input)
return result
}
こちらは、まずはBatchWriteItemCommandInput
にわたすWriteRequest
配列を生成します。
そして、WriteRequest
配列ができたら、これをBatchWriteItemCommandInput
に設定してBatchWriteItemCommand
に引き渡しています。
こうすることで、生成したリクエスト対象のアイテムをバッチ処理で一括削除することができます。
まとめ
以上、AWS SDK for JavaScript v3 を使って、 DynamoDB から指定したパーティションキーのアイテムを削除してみました。
一旦Queryコマンドを発行してキー情報を取得する必要がありますが、RDBなどにおけるWhere句を利用した削除処理と異なるので、ここが少し難しいポイントかなと思います。
どなたかのお役に立てば幸いです。それでは!