タイトル長っ。
WEB+DB PRESS vol.102の時とかの方が長かったか……。
[2018/04/07修正]
DynamoDBLocalではTimeToLive動いた。
localstackでは結局動かず。無念。
[まずは背景の説明から]
ちょっと色々あって、AWS DynamoDBのTime to Live機能(TTL)にお世話になることになった。なることにした。というか、なっている。
こちらがAWSの紹介記事。新機能、と書かれているが、ちょうど1年くらい前、2017年3月上旬くらいにオープンになった機能。
で、まあちょろっとコード書いて、AWSにデプロイして、テストしてもらって、そこまではよかった。
そこまでは。
[DynamoDB Local]
問題はローカル環境での動作確認だった。
話が前後して申し訳ないが、DynamoDBを利用した開発を行うにあたって、伝統的にうちのチームではDynamoDB Local
を利用してきた。なにせAWS公式だから、というのが大きい。
ところが、である。
いつまで待ってもリンクされているファイルのバージョンが2017-02-16
の日付のままなのである。latest
と銘打っているくせに。おかしい。TTL機能が付く直前のバージョンで止まっている。
念のため、ファイルをダウンロードして中身を確認すると、同梱されているsdkのバージョンを確認してみる。
dynamodb_local_latest/DynamoDBLocal_lib/aws-java-sdk-dynamodb-1.11.86.jar
古い……2018年3月現在の最新のバージョンは1.11.301
だというのに。古すぎる。
https://s3-ap-northeast-1.amazonaws.com/dynamodb-local-tokyo
で、リポジトリをよくよく確認すると、ベータ版が置いてある!
というわけで、早速ダウンロードして中身を確認してみる。
dynamodb_local_2017-04-22_beta/DynamoDBLocal_lib/aws-java-sdk-dynamodb-1.11.119.jar
jarの中身を確認すると、やはりあった。TTL関連のクラスがある。
com/amazonaws/services/dynamodbv2/model/UpdateTimeToLiveRequest.class
[DynamoDB LocalとDynamoDB JavaScript Shell]
古いバージョンのDynamoDB Local
を起動してみる。
java -Djava.library.path=./DynamoDBLocal_lib -jar DynamoDBLocal.jar -sharedDb -port 8008
JavaのSDKのコードを書くのはEclipse起動したりpom.xmlセットアップしたりと諸々だるいのでDynamoDB Local
に同梱されているDynamoDB JavaScript Shell
を利用して確認をしてみることに。
上記コマンドでDynamoDB Local
を起動したあと、次のURLを叩くとブラウザでDynamoDB Local
を操作できる。
http://localhost:8008/shell/
(ポート番号は起動コマンドで指定したものを利用する)
で、チートシートを見てみると……オヤ?
何かが足りない。
AmazonDynamoDBClient (AWS SDK for Java - 1.11.301)
上の公式ドキュメントにあるように、Java版にはAmazonDynamoDBClient#updateTimeToLive
とかのメソッドがあるのに、JavaScript版SDKにはないのか?と思いつつ、JSのドキュメントを見る。
Class: AWS.DynamoDB — AWS SDK for JavaScript
日付が、古い。API Version: 2012-08-10
て。関連するfunctionもないわなそりゃ。
仕方がないので、Java版を使うことに。
せっかくの機会なので、AWS SDK for Java 2.0のDeveloper Previewを使おうとしたが、普段使っているバージョンと違いすぎて、パッとDynamoDBClientを用意することすら難しかったのでここは諦める。いつかリベンジするぞ。
public class App { private static final String TABLE_NAME = "test-table-1"; private static final String ATTR_ID = "id"; private static final String ATTR_VAL = "value"; private static final String ATTR_TTL = "ttl"; public static void main(String[] args) { final AmazonDynamoDB dynamo = AmazonDynamoDBClientBuilder.standard() .withCredentials(new SystemPropertiesCredentialsProvider()).withEndpointConfiguration( new EndpointConfiguration("http://localhost:8008", Regions.DEFAULT_REGION.name())) .build(); final CreateTableRequest createTable = new CreateTableRequest().withTableName(TABLE_NAME) .withKeySchema(new KeySchemaElement(ATTR_ID, KeyType.HASH)) .withAttributeDefinitions(new AttributeDefinition(ATTR_ID, ScalarAttributeType.S)) .withProvisionedThroughput(new ProvisionedThroughput(1L, 1L)); try { dynamo.createTable(createTable); sleep(); } catch (ResourceInUseException ignore) { } final TimeToLiveSpecification ttlSpec = new TimeToLiveSpecification().withAttributeName(ATTR_TTL) .withEnabled(true); final UpdateTimeToLiveRequest updateTtl = new UpdateTimeToLiveRequest().withTableName(TABLE_NAME) .withTimeToLiveSpecification(ttlSpec); try { dynamo.updateTimeToLive(updateTtl); sleep(); } catch (AmazonDynamoDBException ignore) { } Map<String, AttributeValue> item = new HashMap<>(); item.put(ATTR_ID, new AttributeValue().withS("id-1")); item.put(ATTR_VAL, new AttributeValue().withS("val-1")); // Wrong usage
// long ttl = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(1);
long ttl = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()) + 1;
System.out.println(ttl); item.put(ATTR_TTL, new AttributeValue().withN(String.valueOf(ttl))); dynamo.putItem(TABLE_NAME, item); sleep(); item = new HashMap<>(); item.put(ATTR_ID, new AttributeValue().withS("id-1")); GetItemResult res = dynamo.getItem(TABLE_NAME, item); if (res != null) { res.getItem().values().stream().forEach(v -> System.out.println(v.getS() != null ? v.getS() : v.getN())); System.out.println(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())); } else { System.out.println("no data"); } sleep(); sleep(); sleep(); res = dynamo.getItem(TABLE_NAME, item); if (res != null) { res.getItem().values().stream().forEach(v -> System.out.println(v.getS() != null ? v.getS() : v.getN())); System.out.println(TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis())); } else { System.out.println("no data"); } } private static void sleep() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }
import文は省略。ツッコミどころしかないのは許してほしい。
とりあえず、updateTimeToLiveを呼んでエラーが返ってこないので、一応このベータバージョンのDynamoDB Local
はTTLに対応してはいるようだ。
実行結果はこんな感じ。
1523089749 id-1 val-1
1523089749
1523089752 id-1 val-1
1523089749
1523089755
アレ、消えてない。
公式ドキュメントには、48時間以内には消えます、とある。そう言えばそうだった気もする。DynamoDB Local
も同じなんだろうか。
念のためDynamoDB Local
を再起動して、getItemを実行して見たが、データが読める。再起動では消えないらしい。
[2018/04/07追記]
なぜか公式ドキュメントを無視してTTLにミリ秒を突っ込んでいたのが動作しない原因だったようだ。
修正版のコードだと、3秒ではデータが消えなかったが、10秒以内にはデータが消えて、NullPointerException
を吐いていた。
データが消えるとres.getItem()
がnull
を返すのが原因らしい。
[2018/04/07追記ここまで]
[いよいよlocalstackが登場する]
インストールコマンドが書いてある。
pip install localstack
しかし、実行するとエラー。
-bash: pip: command not found
あれか、pip
ってpythonのモジュール管理ツールか……brew pip install
とかでいいんだっけ(良くない)、と思いつつ、ググって解決する。
語順が違った。brew install pip
か。
うまく行ったので、pip install localstack
でインストールでき……ない。
OSError: [Errno 1] Operation not permitted: '/var/folders/02/w7f9tqb910g1tn5n3vp6qc1c0000gn/T/pip-jr8t0k-uninstall/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/six-1.4.1-py2.7.egg-info'
公式ドキュメントを見ると、MacOS X Sierra
の場合にはpermissionsの問題があるので次のコマンドを打てとある。
pip install --user localstack
エラーメッセージが同一かどうか定かではないが、とりあえず試して見る。
うまく行った。MacOS X High Sierra
でも事情は同じらしい。
起動のテスト。どっかのパスが通ってないのが悪いのか、公式ドキュメントに載ってるlocalstack start
のコマンドは効かなかったので、~/Library/Python/2.7/bin
に移動して、python localstack start
で起動した。
~/Library/Python/2.7/bin akito$ python localstack start Starting local dev environment. CTRL-C to quit. 2018-03-28T03:30:40:INFO:localstack.services.install: Downloading and installing local Elasticsearch server. This may take some time. 2018-03-28T03:30:40:INFO:localstack.services.install: Downloading and installing local DynamoDB server. This may take some time. 2018-03-28T03:30:40:INFO:localstack.services.install: Downloading and installing local ElasticMQ server. This may take some time. 2018-03-28T03:30:40:INFO:localstack.services.install: Downloading and installing local Kinesis server. This may take some time. 2018-03-28T03:31:17:INFO:localstack.services.install: Downloading and installing LocalStack Java libraries. This may take some time. Starting mock API Gateway (http port 4567)... Starting mock DynamoDB (http port 4569)... Starting mock SES (http port 4579)... Starting mock Kinesis (http port 4568)... Starting mock Redshift (http port 4577)... Starting mock S3 (http port 4572)... Starting mock CloudWatch (http port 4582)... Starting mock CloudFormation (http port 4581)... Starting mock SSM (http port 4583)... Starting mock SQS (http port 4576)... Starting local Elasticsearch (http port 4571)... Starting mock SNS (http port 4575)... Starting mock DynamoDB Streams service (http port 4570)... Starting mock Firehose service (http port 4573)... Starting mock Route53 (http port 4580)... Starting mock ES service (http port 4578)... Starting mock Lambda service (http port 4574)... 2018-03-28T03:31:32:WARNING:infra.pyc: Service "elasticsearch" not yet available, retrying... 2018-03-28T03:31:35:WARNING:infra.pyc: Service "elasticsearch" not yet available, retrying... Ready.
今回使いたいのはDynamoDBだけなんだけど、色々動いてくれるらしい。
S3、CloudWatch、Lambda、SQS、SNSとかは普段使ってたりするのでまたの機会に色々試してみたい。
[localstackでDynamoDB TTLを試す]
で、本題。
localstackのDynamoDBが起動している状態で、Javaのコードの接続先URLだけ変更して、コードを実行して見る。
実行結果が変わらない。データが消えない。
1522176091787 id-1 val-1 1522176091787 1522176091809 id-1 val-1 1522176091787 1522176094827
あれ、と思ってlocalstackのリポジトリをTime to Liveで検索してみると、テストコードが見つかった。
localstack/test_dynamodb.py at master · localstack/localstack · GitHub
TTLのスペック更新リクエストとかは受け付けてくれるようになってるっぽい。
確かに自分のコードでも、テーブルをCreateして、UpdateTimeToLiveRequestを投げてもエラーは返ってこなかった。返ってこなかったが、データが消えてくれる訳ではないらしい。
果たしてissuesを当たると、予想通り実装されていないようだった。
[2018/04/07追記]
念のためミリ秒問題の修正後のコードで実行してみたが、やはりlocalstackでは動作しなかった。
[2018/04/07追記ここまで]
[まとめ]
- DynamoDB TTLを使うならやはりAWS上が安定していて良い
- DynamoDBLocalはTTLを使うとデータが消える [2018/04/07追記]
- LocalStackはDynamoDB TTLを使ってもデータは消えない
- とはいえLocalStackは機能が豊富で色々便利そう
- AWS SDK for Java 2.0への移行は変更点多くて難しそう
DynamoDB LocalとかLocalStackでの対応状況がよろしくないところを見ると、あんまり使うべき機能ではないのかしら。あるいは、使用目的が適切ではないのか。
改めて、実現したいことを考え直して見たほうがいいのかもしれない。