先日、DynamoDB のレコードのパージ(一括削除)処理を書きました。将来の自分へのメモとしてソースコードを残したいと思います。また、そもそもこの書き方がベストな気が全くしないので、コードを晒すことでいろいろコメントをもらえないかなーという魂胆です。コメント頂けるとありがたいです。
DynamoDB レコード削除の要件について
まず今回私が叶えたかった要件についてです。
- 対象はHashキーとRangeキーを持つテーブル
- Hashキーは全レコード共通(現時点では)
- Rangeキーにはレコードの作成時刻がYYYYMMDDHHMI形式で格納されている
- レコードは毎分作られるので、何もしないとそれなりの数のレコードが溜まっていく。そこで、一週間以上前までに作られたレコードを全て消したい
が実現したいことです。
参考文献
下記の記事で全てのレコードを消すケース(RDBでいうTRUNCATE)が紹介されていたので、参考にさせていただきました。ありがとうございます。
一括削除プログラム
私が作成したコードは下記になります。
# -*- coding: utf-8 -*- import os import boto3 import json import logging import decimal import datetime from boto3.dynamodb.conditions import Key, Attr logger = logging.getLogger() logger.setLevel(logging.INFO) TARGET_ID = "1234567890" dynamo_table = boto3.resource('dynamodb').Table("*** TARGET_TBL_NAME ***") def lambda_handler(event, context): try: logger.info("*** delete_record() is started. ***") delete_targets = dynamo_table.query( KeyConditionExpression=Key('id').eq(TARGET_ID) & Key('updated_at').between("201701010000", _get_week_ago_as_YYYYMMDD2359()) )['Items'] key_names = [ x["AttributeName"] for x in dynamo_table.key_schema ] delete_keys = [ { k:v for k,v in x.items() if k in key_names } for x in delete_targets ] with dynamo_table.batch_writer() as batch: for key in delete_keys: batch.delete_item(Key = key) logger.info("Delete: " + str(key)) response = { 'statusCode': '200', 'body': '', 'headers': { 'Content-Type': 'application/json', 'Access-Control-Allow-Origin': '*' } } return response except Exception as e: logging.error("type: % s", type(e)) logging.error(e) def _get_week_ago_as_YYYYMMDD2359(): week_ago_jst = datetime.datetime.now() + datetime.timedelta(hours=9) + datetime.timedelta(weeks=-1) return week_ago_jst.strftime('%Y%m%d') + "2359"
レスポンス部分はじめ、やや適当なところもありますがお許しください。
実行環境
実行環境は下記の通りです。
- Lambda 上で実行
- Runtime: Python 3.6
- 設定メモリ: 128 MB
cron(0 15 * * ? *)
として Daily で実行
課題感
幾つか課題感があるので共有します。
between(to, from) の From の埋め方
今回実現したかった要件は「YYYYMMDD2359より前の(小さい)レコードを全て削除」でした。そのため between(from, to)
の to のみ設定し、Fromは無しできないかなと考えていました。ただ軽く試してみたところ、from に空文字やNoneは受けつられなかったので、アプリ仕様を鑑みて下記のような形にしました。
delete_targets = dynamo_table.query( KeyConditionExpression=Key('id').eq(TARGET_ID) & Key('updated_at').between("201701010000", _get_week_ago_as_YYYYMMDD2359()) )['Items']
「201701010000 より後(大きい) かつ YYYYMMDD2359より前の(小さい)レコードを全て削除」となりました。今回はいいんですが、将来的にこれじゃダメな要件が出てくるかもしれないので(まぁどうにかなりそうですが)、どこかで調べてみたいところです。
Hash キーが固定ではなくなったときにどうするか
再掲となりますが、削除対象のレコードを
delete_targets = dynamo_table.query( KeyConditionExpression=Key('id').eq(TARGET_ID) & Key('updated_at').between("201701010000", _get_week_ago_as_YYYYMMDD2359()) )['Items']
やろうと思えばできるのですが、全てのHashキーを取得して、その数だけこの処理をする形はイマイチなんじゃないかと悩み中です。
削除速度について
あまり効率のいい削除方式ではないなーと思ったので、1分間でどれくらいのレコードを削除できるか実験してみたところ、1分間で約350レコードという結果でした。Write Capacity Unit を 1 にしていたのでその影響もあると思い、Write Capacity Unit を 5にして実験してみると約20秒で約600件のレコードを削除できました。
現在、1日に1,440レコード(60分 * 24時間)貯まるので、Write Capacity Unit が 1 だと Lambda の制限時間5分ギリギリかなーという状況です。将来的にレコードが増えたら Write Capacity Unit を上げる必要があるかもしれません。ただ、このパージ処理のために Write Capacity Unit をあげるのもどうかなーと考え中です。
まとめ
以前、DynamoDB に関する記事を書きました。
少しずつ DynamoDB の知見が溜まってきた気がするのですが、「将来の拡張性を(ある程度)担保できる設計」についてはまだまだナレッジが不足している気がします。こればっかりは実際にシステムを運用してみて良い思いや辛い思いをしていくしかないと思いますので、今後も継続して使っていければと思います。