AWSのDynamoDBで、「有効期限切れのレコードを削除する」というよくある機能を実装するのにとてつもなく苦労したので記す。
結論
Aws\DynamoDb\DynamoDbClient->createTable([
'TableName' => 'テーブル名',
'AttributeDefinitions' => [
['AttributeName' => 'id', 'AttributeType' => 'N',], // 主キー
['AttributeName' => 'dummy', 'AttributeType' => 'N',], // グローバルセカンダリインデックスのパーティションキー
['AttributeName' => 'expired', 'AttributeType' => 'N',], // グローバルセカンダリインデックスのソートキー
],
'KeySchema' => [
['AttributeName' => 'id','KeyType' => 'HASH',],
],
'GlobalSecondaryIndexes' => [[
'IndexName' => 'expired_index',
'KeySchema' => [
['AttributeName' => 'dummy', 'KeyType' => 'HASH', ],
['AttributeName' => 'expired', 'KeyType' => 'RANGE', ],
]],
],
]);
インサート時にはexpiredにタイムスタンプを、dummyには『1』を固定で入れる。
解説
まずDynamoDBには、『主キーで削除する』『全削除する』以外の削除機能がない。
よって、"DELETE table WHERE expired < NOW()"はいきなり諦めざるを得ない。
次善の策として、"SELECT table WHERE expired < NOW()"でqueryした結果をforeachで回してそれぞれ削除する、といった回りくどい方法を取る必要がある。
scanしてPHP側で判定、という方法は論外なのでパス。
// SELECT table WHERE expired < NOW()
$list = Aws\DynamoDb\DynamoDbClient->query([
'TableName' => 'テーブル名',
'IndexName'=>'expired_index',
'KeyConditionExpression' => '#key1 = :val1 AND #key2 < :val2',
'ExpressionAttributeNames' => ['#key1'=>'dummy', '#key2'=>'expired'],
'ExpressionAttributeValues' => [
':val1' => [ 'N' => 1 ],
':val2' => [ 'N' => time() ],
],
]);
// DELETE table WHERE id = :id
foreach($list as $v){
Aws\DynamoDb\DynamoDbClient->DeleteItem([
'TableName' => 'テーブル名',
'Key' => [
'id' => [ 'N'=>$v['id']['N'] ],
],
]);
}
"expired < NOW()"で検索するためにグローバルセカンダリインデックスを作る必要があるわけだが、グローバルセカンダリインデックスにはHASHタイプのキー(パーティションキー)が必須。
HASHタイプのキーは=でしか検索できず、不等号での検索ができない。
HASHのキーに付随してRANGEタイプのキー(ソートキー)を作成することが可能。
RANGEキーは不等号で検索することが可能だが、RANGEだけのキーを作成することはできない。
さらに検索時もHASH+RANGEでの絞り込みが必須で、RANGEだけで検索することはできない。
そのため、今回はdummyという完全に無意味なHASHキーを作成し、全て同じ値を入れることで、実質的にRANGEだけでの検索を可能にしている。
感想
そもそもDynamoDBをそういう用途に使いるべきではない。