TTL を有効化した DynamoDB でちょっと焦った話

記事タイトルとURLをコピーする

こんにちは。技術4課の保田(ほだ)です。

先日28回目の誕生日を迎えたのですが、全然実感がないので実質まだ27歳です。

要約

TTL を有効にした DynamoDB に対して有効期限切れのレコードを PutItem すると何も起きない

DynamoDB の 有効期限(TTL)

冒頭の年齢の話とは一切関係ありませんが、TTL のお話をします。

ご存じの方も多いと思われますが DynamoDB には Time to live(TTL)という機能があり、これによって一定期間が経過したレコードを自動的に削除してくれます。

「この属性の日時(UNIX時間)を過ぎたら削除してね」を設定します。

CloudFormation で最初からどの属性を TTL とするか定義したテンプレートを書いてテーブルを作成しても、既存のテーブルに対して後から設定しても大丈夫です。

ただし後述のドキュメントにも記載があるように、既存のテーブルに対して TTL を後から有効化すると完全に反映されるまで最大1時間かかるようです。

詳細については公式ドキュメント:DynamoDB 有効期限 (TTL) を使用して項目を失効させる を見ていただければと思います。

簡単なサンプル

例えば、適当ですがこんなテーブルを用意して  ttl という属性を TTL 属性として定義したとします。

id (Partiton Key) name ttl
0 tanaka 1593661777
1 yamada 1593661835
2 suzuki 1593661861

そして、今回は有効期限が1週間だとしてみます。執筆時点の6月25日から1週間後なので7月2日です。

ちなみにコンソール上でこのようにマウスオーバーするとちゃんと UTC や JST に変換して分かりやすく表示してくれます。やさしみの刺身!

そしてまた適当ですが、ここにデータを入れていくスクリプトを作りました。

import boto3

from datetime import datetime, timedelta

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('ttl-sample-tb')


def main(item):
    start = datetime.now()
    item['ttl'] = get_ttl(start)
    response = table.put_item(Item=item)
    print(response)


def get_ttl(start):
    expiration_date = start + timedelta(days=7)
    return round(expiration_date.timestamp())


if __name__ == '__main__':
    item = {
        'id': '0',
        'name': 'tanaka'
    }
    main(item)

get_ttl 関数にて 7日後の UNIX 時間を取得し、それを int 型で DynamoDB に PutItem しています。

何があったか

ようやくタイトルの件についてです。

前提として、お客様の環境(本番環境)と同じテーブルを dev- のようなプレフィックスを付けて(会社の)個人検証アカウントにも開発環境として持っていました。

そしてテスト用のデータを用意しようとして上のようなスクリプトを作ってデータを登録していました。

が、実は手抜きでデータを用意していたため、一度どこかで計算した TTL の値をテスト用スクリプトにベタ書きしていました。そしてその値はすでに過去のものとなっていました(スクリプト書いたのが1週間以上前だったということですね)。

そんなことに気付かず、ペペッと実行してコンソールを確認しに行くと何もデータが登録されていません。再読み込みを何度かしても出てきません。

そして私は「アレ?もしかしてお客様の本番環境にテストデータ入れちゃった…?」と焦り散らしてしまった、というわけです。

しかも私は自分用の環境に入れるテストデータは毎回ネタというかちょっとしたおふざけを入れてしまうので余計に焦りました。

種明かし

焦りに焦って慌ててコードをちゃんと読みますと、テーブル名の指定はちゃんと自分の環境にしかないもので、API 実行用のプロファイルも自分のアカウントに対するものになっていました。

そして TTL に指定している時間が過去に計算した値でべた書きになっていることに気付いたというわけです。

そうです。

どうやら、TTL として過去の時刻を指定するとエラーにもならず、ただデータが保存されない仕様のようなのです。

内部的には保存してから「有効期限切れとるやんけ」とすぐに消されるのか、そもそもバリデーション的なもので弾かれるのか分かりませんが、どちらにせよデータは保存されません。

もう少し検証

先ほどご紹介したスクリプトで TTL を取得する関数をこのように書き換えます。

def get_ttl(start):
    expiration_date = start + timedelta(seconds=10)
    return round(expiration_date.timestamp())

start にはスクリプト実行時の時刻が入りますので、10秒後に有効期限が来るようにしてなります。

これを実行するとちゃんとデータが保存されますが、10秒後になったからといって即削除されるわけではありません。

これについてはドキュメントにもあるようにラグはあるよということです。(ドキュメント:仕組み: DynamoDB 有効期限 (TTL)

テーブルのサイズとアクティビティレベルによっては、期限切れの項目の実際の削除オペレーションが異なる場合があります。TTL はバックグラウンドプロセスであることを意図しているため、TTL を介して項目の有効期限切れや削除に使用される容量の性質は可変です (無料)。項目の削除には最大 48 時間かかることがあります。

そしてある程度なら過去の時間を TTL に指定してもデータを Put することができます。今回の環境で試してみると 12 分前にすると OK で13分前にするとダメでした。

ちなみに既存のデータの TTL を先ほど書いたより過去の時間で更新すると問答無用でそのデータが消えます。検証してて気づきました。

おそらくこの時間の境目についても先ほど引用したドキュメントのようにテーブルのサイズや使われ具合によって変わってくると思われます。なのでここに書いた具体的な時間の値はあくまでも参考程度に捉えていただければと思います。

まとめ

在宅勤務等でずっと家にいた方は体が暑さに慣れてないと思うので、夏バテには気を付けよう!