初級読了 3 分

DynamoDBで複数のアイテムを削除する方法

DynamoDBにDELETE FROM table WHERE …はありません。すべての削除は完全なプライマリキーで1つのアイテムを対象にします。APIには一括削除操作もtruncateもなく、のDELETEもステートメントごとにちょうど1つのアイテムしか削除しません。

つまり「複数のアイテムを削除する」は常に2ステップの作業です。キーを見つけ、次にキーごとに1つの削除を発行し、スループットのためにバッチ化します。

DynamoDBで複数のアイテムを削除するには?

削除したいアイテムのプライマリキーを(QueryまたはScanで)集め、バッチで削除します。BatchWriteItemは1回の呼び出しで最大25個のDeleteRequestを受け取り、TransactWriteItemsは最大100アイテムをアトミックに削除し、PartiQLのDELETE文はBatchExecuteStatementで25個ずつバッチ実行できます。

  • 一括・ベストエフォート: BatchWriteItem。 1回の呼び出しで25削除、残りは自分でリトライします。
  • All-or-nothing: TransactWriteItems。一緒に成功するか一緒に失敗する最大100のアクションで、書き込みコストは2倍です。
  • SQL風の構文: PartiQL DELETE + BatchExecuteStatement。キーごとのルールは同じで、綴りが馴染み深いだけです。
  • テーブル内のすべて: truncate戦略。通常はテーブルを削除して再作成するのが最も安上がりです。
  • GUIで: DynoTableは、選択した行またはクエリに一致するすべてのアイテムを削除します。レビューのためにステージすることも、直接書き込むこともできます。

ステップ0:キーを集める

削除にはプライマリキー全体が必要です。テーブルではパーティションキーとソートキーです (AWS: "For each primary key, you must provide all of the key attributes")。任意の属性で削除することはできないので、「status = 'archived'のすべてのアイテムを削除する」は読み取りから始まります。

  • 条件がキー(またはGSIのキー)にあるなら、対象を絞ったQueryで安価にキーを集められます。
  • そうでなければフィルタ付きのScanです。フィルタの有無にかかわらず、テーブル全体を読み取り(そして課金し)ます。query vs scanを参照してください。

キー属性だけを返すProjectionExpressionを使い、LastEvaluatedKeyを最後まで辿ってください(ページネーション)。途中で止めると、最初の1 MBページ分しか削除されません。

方法1:BatchWriteItem(1回の呼び出しで最大25削除)

BatchWriteItemは1回の呼び出しで最大25個のput/deleteリクエスト、または16 MBをまとめます (AWS APIリファレンス: "A single call to BatchWriteItem can transmit up to 16MB of data over the network, consisting of up to 25 item put or delete operations")。

aws dynamodb batch-write-item --return-consumed-capacity TOTAL --request-items '{
  "Tickets": [
    {"DeleteRequest": {"Key": {"PK": {"S": "TICKET#1001"}, "SK": {"S": "META"}}}},
    {"DeleteRequest": {"Key": {"PK": {"S": "TICKET#1002"}, "SK": {"S": "META"}}}},
    {"DeleteRequest": {"Key": {"PK": {"S": "TICKET#1003"}, "SK": {"S": "META"}}}}
  ]
}'

バッチの4つの性質が人をつまずかせます。すべて APIリファレンスに書かれています。

  • アトミックではありません: "The individual PutItem and DeleteItem operations specified in BatchWriteItem are atomic; however BatchWriteItem as a whole is not." 一部の削除が反映され、他は反映されないことがあります。
  • UnprocessedItemsをリトライしなければなりません。 スロットリングされたリクエストは、リクエストと同じ形でUnprocessedItemsレスポンスマップに返ってきます。"Typically, you would call BatchWriteItem in a loop"であり、AWSはリトライの間に"strongly recommend[s] that you use an exponential backoff algorithm"としています。
  • 条件は使えません。 "You cannot specify conditions on individual put and delete requests"。条件付き削除は代わりにDeleteItemかトランザクションを通します。
  • 重複キーは不可。 1回の呼び出しで同じキーに対する2つの操作があると、呼び出し全体がValidationExceptionで失敗します。分割する前にキーのリストを重複排除してください(PK + SK文字列のSet)。

ループの形(25個ずつに分割、送信、残りをバックオフ付きで再キュー)はバッチ操作に書き出してあります。

方法2:TransactWriteItems(アトミックな削除)

一連の削除が一緒に成功または失敗しなければならないとき(例えば親アイテムとそのの子)は、TransactWriteItemsを使います。最大100アクション(削除、put、更新、条件チェック)がアトミックに適用され、アイテムごとのも使えます。

トレードオフ:トランザクション書き込みは、プレーンな書き込みの2倍の書き込みキャパシティがかかります。完全な仕組み(冪等性トークン、TransactionCanceledException、コスト計算)はDynamoDBトランザクションにあります。

方法3:PartiQL DELETEのバッチ実行

PartiQLはキーごとのルールをそのまま残し、綴りを変えるだけです。

開発者ガイドは明示的です (PartiQL DELETE): "You can only delete one item at a time. You cannot issue a single DynamoDB PartiQL statement that deletes multiple items"、そしてWHERE条件は "must resolve to a single primary key value"です。

複数のアイテムを削除するには、単一アイテムのステートメントをバッチ化します。 BatchExecuteStatement は1バッチあたり最大25ステートメントを実行します(すべて読み取りかすべて書き込みで、混在は不可):

[
  {"Statement": "DELETE FROM \"Tickets\" WHERE PK = 'TICKET#1001' AND SK = 'META'"},
  {"Statement": "DELETE FROM \"Tickets\" WHERE PK = 'TICKET#1002' AND SK = 'META'"}
]
aws dynamodb batch-execute-statement --statements file://statements.json

完全なキーを固定しないWHEREは失敗するので、DELETE FROM Tickets WHERE status = 'archived'は存在しません。その他のPartiQLパターン(とその背後にある制限)はPartiQLの例と PartiQL vs SQLにあります。

すべてのアイテムを削除する(truncate)

DynamoDBにtruncateはありません。選択肢を推奨順に挙げます。

  1. テーブルを削除して再作成する。 DeleteTable + CreateTableはアイテムごとの書き込み料金を回避します。設定は勝手には戻ってきません(下記参照)。
  2. でアイテムを失効させる。 「Xより古いものをすべて削除」が目的で、アイテムがすでにエポック秒のNumber型の有効期限を持っているなら、TTLは書き込みスループットを消費せずに、期限切れから"within a few days"でそれらを削除します。その間は期限切れアイテムを読み取り時にフィルタで除外してください。
  3. Scan + バッチ削除(方法1)は、テーブルを稼働させたまま一部だけを消す場合です。Scanが読み取るすべてのバイトと、削除したアイテムごとの書き込みを支払います。

再作成の手順:まず削除保護をオフにしてください。そうしないとDeleteTableは失敗します。キー、インデックス、ストリーム、キャパシティモード、タグをCreateTableで再宣言します。

新しいテーブルがACTIVEになったら、TTL(UpdateTimeToLive。CreateTableのフィールドではありません)、ポイントインタイムリカバリ、オートスケーリングを再び有効にします。

新しいテーブルには新しいストリームARNが付くので、Lambdaトリガーやその他のストリームコンシューマーを接続し直してください。Infrastructure as Codeなら、これらすべてが1回のデプロイで済みます。

TTLが無料なのは、属性がすでにある場合だけです。既存のアイテムに有効期限を書き込むのはアイテムごとに1書き込みで、削除するのと同じ料金です。グローバルテーブルでは、各レプリカのリージョンも複製されたTTL削除の料金を支払います。

Scanのコストはアイテム単位ではなく4 KBの読み取り単位です。200バイトのアイテム100万件は約195 MBで、4 KBあたり0.5ユニットの結果整合性読み取りでおよそ24,400読み取りユニットになります。

DynoTableで複数のアイテムを削除する

DynoTableでは、キーを見つけて分割しリトライするループが、グリッドでの選択になります。ケースは2つです。

  1. 選択した行。 クエリを実行またはフィルタし、行を選択して⌘⌫を押します。各行がステージされた削除になり、グリッドで赤く色づき、保留中の変更にカードとして表示されます。まだDynamoDBには何も触れていません。
  2. クエリに一致するすべてのアイテム。 未読み込みのページがあるビジュアルクエリまたはスキャンで⌘Aを押します(またはヘッダーのチェックボックスをオンにします)。削除では、最大10,000アイテムまでの削除をステージまたは削除を Commit、あるいは一致全体に対する直接削除を選べます。

Commitは最大100アイテムずつのTransactWriteItemsチャンクとして送信され、各削除はアイテムがまだ存在することを条件とします。チャンクが失敗するとCommitはそこで止まり、それ以前のチャンクは適用されたまま残ります。それらは履歴から元に戻せます。

直接削除は読み取りながら書き込み、ステージも履歴もないため、元に戻せません。確認のためにテーブル名を入力します。

安全(デフォルト)は削除のたびにフィルタを再確認し、高速は再確認せずにバッチ削除します。最大書き込み数/秒は本番テーブルでのペースに上限を設けます。

選択した行で⌘⇧⌫を押すとレビューをスキップします。25件ずつの無条件のBatchWriteItem呼び出しとして直接削除し、未処理のアイテムは自動でリトライします。

DynoTableでのステージされた削除:選択された行がグリッドで赤く色づき、コミット前にそれぞれが保留中の変更のレビュー可能なカードになっているところ。
DynoTableでのステージされた削除:選択された行がグリッドで赤く色づき、コミット前にそれぞれが保留中の変更のレビュー可能なカードになっているところ。

テーブル全体を消すなら、まずその定義をコードとしてエクスポートし(Terraform、CDK、CloudFormation。PITR、タグ、オートスケーリングは含まれません)、それからテーブルを削除して再作成します。各モードの詳細は一括削除のドキュメントにあります。

削除に手書きの条件(attribute_exists、バージョンチェック)が必要なら、 DynamoDB Expression Builderで正確なConditionExpression+プレースホルダーマップを組み立てて、代わりにDeleteItemやトランザクションに付けてください。

よくある質問

DynamoDBの1回の呼び出しで複数のアイテムを削除できますか?はい。BatchWriteItemで最大25個(ベストエフォート、UnprocessedItemsをリトライ)、またはTransactWriteItemsでアトミックに最大100個です。どちらもアイテムごとに完全なプライマリキーが必要で、アイテムを選択する「where」条件はどちらも受け付けません。

DynamoDBにDELETE WHEREはありますか?ありません。低レベルAPIもPartiQLも、削除するアイテムを条件で選択できません。PartiQLのDELETEは"resolve[s] to a single primary key value"するWHEREを要求します。まずキーをクエリまたはスキャンで集めてから、1つずつ削除します。

DynamoDBテーブルのすべてのアイテムを削除するには?完全に消すなら、テーブルを削除して再作成します。アイテムごとに1書き込みを支払わずに済みます。経過時間ベースのクリーンアップにはTTLを使ってください。書き込みスループットを消費せずに、期限切れから数日以内に期限切れアイテムを削除します。テーブルを稼働させたままにする必要があるときのフォールバックがScan+バッチ削除です。

バッチ削除の後もアイテムが残っているのはなぜですか?ほとんどの場合、リトライされていないUnprocessedItemsです。スロットリングされたBatchWriteItemは失敗した削除をレスポンスの中で返し、それをループしないコードは、アイテムがまだテーブルに残ったまま「完了」します。マップが空で返ってくるまで指数バックオフでリトライしてください。

アイテムの削除にはいくらかかりますか?各削除は削除されるアイテムのサイズに応じた書き込みです(1 KBあたり1書き込みユニット、さらにそのアイテムを保持する各GSIへの書き込み、トランザクション内では2倍)。キーを見つけるのにも読み取りコストがかかります。料金計算ツールでコストを見積もってください。

まず何を削除するのか見てから消したいですか?DynoTableをダウンロードしてください。行を選択し、⌘⌫で削除をステージし、差分をレビューし、確信できたらコミットします。

著者: Dmitriy · 更新日