【小ネタ】 DynamoDBのTTLで一部だけ無期限にしたい時は0でOK

# DynamoDBのTTL機能 #

DynamoDBにはTTLという便利な機能があり、有効期限を過ぎると、そのデータだけ自動的に削除してくれます。

## 注意1：48時間以内に削除 ##

注意点としては、有効期限後「48時間以内の削除」のため、すでに期限が切れたデータを読み出すこともあるということです。

> 通常、DynamoDB は期限が切れた項目を期限切れから 48 時間以内に削除します。期限切れの後で項目が実際に削除されるまでの期間は、ワークロードの特性とテーブルのサイズによって異なります。期限が切れたが削除されていない項目は、読み取り、クエリ、およびスキャンで引き続き表示されます。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/howitworks-ttl.html

単に、データの保管期限としてTTLを設定する場合は問題ありませんが、データ自体の有効性の期限という意味も兼ねている場合は、要注意です。読み出し時に、期限切れのデータは除外するよう、フィルタなどを設定する必要があります。

## 注意2：期限が5年以上昔なら削除しない ##

ところで、期限のあるデータと無期限のデータが混在する場合は、どうしましょうか。無期限のデータの期限を、遥か未来に設定するというのが、1つの解決方法です。

今回、DynamoDBでホワイトリストを作成するにあたって、オペレーターが手動入力する一部のものだけは、無期限にしたいという要望がありました。TTLはUNIX時間（エポック秒）でなければなりません。日付からUNIX時間に変換してくれるような専用入力画面は用意せず、AWSの管理コンソールを使用して直接入力するという話です。 `3999999999` （2096年10月2日）などと入力しもらえばよいのですが、オペレーターが9を1つ入力し忘れるなどの事件が起きそうだと思っていました。

> 有効期限が 5 年以上前の項目は削除されません。

https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/time-to-live-ttl-before-you-start.html

実は、上記の条件より、期限に `0` を入力すれば、TTLによる削除はされません。 `3999999999` より間違いにくく、また、今回のシステムが、何かの間違いで2096年まで使用され続けても誤作動することもありません。

一部のデータだけ無期限にしたい場合は、値を0にするのがよさそうです。

# 実際に試してみよう #

それでは、実際の動作を確認してみます。

## テーブルを作成 ##

`id` というパーティションキーをもったテーブル `test_whitelist` を作成します。

```
$ aws dynamodb create-table \
    --table-name test_whitelist \
    --attribute-definitions \
        AttributeName=id,AttributeType=S \
    --key-schema \
        AttributeName=id,KeyType=HASH \
    --provisioned-throughput \
        ReadCapacityUnits=5,WriteCapacityUnits=5
```

次のコマンドでステータスが `ACTIVE` になったら作成完了です。

```
$ aws dynamodb describe-table --table-name test_whitelist | 
    jq -r '.Table.TableStatus'
```

## データを作成 ##

3つの項目（データ）を作成し、有効期限としてそれぞれ、無期限、2時間後、明日を指定しましょう（ちなみに、TTLの機能が有効になるには、最大1時間かかります）。

現在、2時間後、明日のUNIX時間は、下記のコマンドで調べることができます。

```
$ date +%s
1568603731
$ date -d '2 hours' +%s
1568610936
$ date -d '1 day' +%s
1568690149
```

`id` にユーザー名（文字列）、 `expiration` に有効期限（数値）を指定して、データを書き込みます。

```
$ aws dynamodb put-item \
    --table-name test_whitelist \
    --item '{"id": {"S": "Alice"}, "expiration": {"N": "0"}}'
$ aws dynamodb put-item \
    --table-name test_whitelist \
    --item '{"id": {"S": "Bob"}, "expiration": {"N": "1568610936"}}'
$ aws dynamodb put-item \
    --table-name test_whitelist \
    --item '{"id": {"S": "Carol"}, "expiration": {"N": "1568690149"}}'
```

## データを確認 ##

全データを表示して確認します。

```
$ aws dynamodb scan --table-name test_whitelist
{
    "Count": 3,
    "Items": [
        {
            "id": {
                "S": "Alice"
            },
            "expiration": {
                "N": "0"
            }
        },
（後略）
```

## TTLを有効に ##

準備ができたので、TTL機能を有効にします。TTLに `expiration` を指定します。

```
aws dynamodb update-time-to-live --table-name test_whitelist \
    --time-to-live-specification "Enabled=true, AttributeName=expiration"
```


## ちなみに、管理コンソールでは ##

TTL機能を有効にすると、AWSの管理コンソールでは、TTLを設定した属性に `(TTL)` という表記がつきます。また、項目にマウスカーソルを近づけると、期限のUNIX時間を、人間向けの表記に変換してくれます。
![dynamodb-ttl.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280182/418efe30-b575-32cf-fa38-843073de8cd4.png)

## データの削除を確認 ##

あとは、時が経つのを待ちます。それぞれの有効期限を過ぎてしばらくすると、 `Bob` と `Carol` は自動削除されます。一方、 `0` を設定した `Alice` は、永遠に残ります。

48時間以内の削除という話ですが、私が試した範囲では、期限の数十分後には削除されていました。

なお、すでに期限切れになったもの（期限が過ぎても、まだ削除されていないもの）を除外して表示したいときは、次のようにフィルタを設定して、読み出します。

```
$ now=$(date +%s)
$ aws dynamodb scan --table-name test_whitelist \
     --filter-expression "expiration > :num OR expiration = :zero" \
     --expression-attribute-values "{\":num\":{\"N\":\"$now\"}, \":zero\":{\"N\":\"0\"}}"
{
    "Count": 2,
    "Items": [
        {
            "id": {
                "S": "Alice"
            },
            "expiration": {
                "N": "0"
            }
        },
        {
            "id": {
                "S": "Carol"
            },
            "expiration": {
                "N": "1568690149"
            }
        }
    ],
    "ScannedCount": 3,
    "ConsumedCapacity": null
}
```

# 実際に試して気づいたこと #

無期限の場合にTTLの値を `0` にすると、前述したとおり、期限が過ぎても、まだ削除されていないものを除外するフィルターが、やや複雑になりました（条件を2つ設定）。

一方、無期限の場合にTTLの値を `0` にせず、 `3999999999` にした場合は、次のようなフィルターで済みます（条件は1つのみ）。

```
$ now=$(date +%s)
$ aws dynamodb scan --table-name test_whitelist \
     --filter-expression "expiration > :num" \
     --expression-attribute-values "{\":num\":{\"N\":\"$now\"}}"
```

オペレーターが直接操作する場合は `0` の方がわかりやすいと思いますが、専用の入力画面があって、オペレーターは「無期限」を選ぶだけの場合は、内部的には `3999999999` にした方がスッキリするかもしれません。

ケースバイケースで、うまく使い分けていただけたらと思います。