第48夜 AWSのDynamoDBを試す

ひさびさのJavaネタ。

Amazon Web ServicesからオールSSDによる性能(キャパシティ)を自由に設定可能なデータベース、DynamoDBが発表されました。

キャパシティを自由に設定可能ってことは、一時的にバッチを走らせたいというときにキャパを上げて短時間で終わらせるとか、空いている時間に少しずつ流していくとかいうテクニックが使えそうです。また、こちらが通常の使い方でしょうが、負荷の高い夜にはキャパを上げて、あまり人のいない早朝には下げることによって運用コストを大きく減らせる可能性があります。

これはつかうしかない!100MBまでの無料枠もありますので試すにはうってつけでしょう。


というわけで、試してみたのですが、すでにSimpleDBとか触ったことがある人にとっては何も難しいとか引っかかるところとかなかったですねぇ。SDKの使い方もいつもどおりだし、テーブルとかわかりやすい名前になってるとか、高レベルAPIも用意されているとかもあって。

というわけで特別ネタにもなってないですが、とりあえず公開する。まずはすべての基礎となる低レベルAPIで。

テーブルの作成

テーブルはAWSのWeb管理コンソールから作れます。スループット等の設定ができますのでGUIが麺からやったほうがいいと思います。

とりあえずテーブル名は「testdb」、キー名は「id」で数値型としておきました。

パッケージ

  • com.amazonaws.services.dynamodb
  • com.amazonaws.services.dynamodb.model
  • com.amazonaws.services.dynamodb.datamodeling

これですべて。
低レベルAPIがmodelパッケージを利用して、高レベルAPIがdataModelingを利用します。

すべての基点

AWSAPIはDynamoDBに限らず、まずすべての基点となるクライアントのインスタンスを生成するところから始まります。生成方法もみんなほぼ同じです。ここがわかればSimpleDBだろうがS3だろうがすぐ使えます。


DynamoDBの場合はAmazonDynamoDBClientです。
こいつをnewするだけですが、コンストラクタに接続情報を渡す必要があります。

接続情報は今回一番お手軽なBasicAWSCredentialsというクラスを利用します。これだけはどのAPIでも利用し、com.amazonaws.authにあります。

String access_key = "hogehoge";
String secret_keykey = "hugahuga";

        
BasicAWSCredentials ac = new BasicAWSCredentials(access_key, secret_keykey);
AmazonDynamoDBClient client = new AmazonDynamoDBClient(ac);

データを挿入

先ほど取得したclientをつかっていろんなことが発行できるようになります。メソッド一覧を眺めていると何が出来るかはすぐにわかるでしょう。

AWSSDKでは何らかの命令を発行するときに「〜Request」、その結果を保持する「〜Result」というクラスが対になって登場します。それらを引数と戻り値にしてclientから発行します。


まずはデータが何もないのでデータの挿入です。
Mapにデータを入れて、それをわたしてリクエスト情報の生成、命令発行という手順です。今回は結果は使いません。

  //1レコードのデータはMapで持つだけ。楽チン
  Map<String, AttributeValue> item = new HashMap<String, AttributeValue>();
  item.put("id", new AttributeValue().withN("123"));
  item.put("名前", new AttributeValue().withS("なまえ"));

  //リクエスト情報を作成
  PutItemRequest request = new PutItemRequest("testdb", item);

  //リクエスト情報を利用してclientから発行、結果を取得
  PutItemResult result = client.putItem(request);

ポイントとしては主キーとなる"id"を必ず入れることでしょうか。テーブルの作成時に主キーの名前と型を指定します。

AttributeValueは型があります。文字列型と数値型、そしてそれぞれの配列型です。数値型であってもwithNメソッドには文字列で渡す、というのを覚えておきましょう。

AWSAPIはこのように「with〜」という命令がたくさんあります。これらはセッターとほぼ同じ命令を持ち、自分自身のインスタンスを返します。メソッドチェーンがやりやすいようになっているわけです。

が、メソッド名が長いこともあってあんまりチェーンする気に慣れません。セッターメソッドより扱いが便利なメソッドがあるのでこっちを使う、というくらいでしょうか。


これをふまえてとりあえずデータを10件作ってみます。

for (int i = 0; i < 10; i++) {
  Map<String, AttributeValue> item = new HashMap<String, AttributeValue>();
  item.put("id", new AttributeValue().withN(Integer.toString(i)));
  item.put("名前", new AttributeValue().withS("しんさん" + i));
  PutItemRequest request = new PutItemRequest("testdb", item);
  client.putItem(request);
}

まぁ難しくはないですね。

データの取得

データを追加するときにはclient#putItemメソッドでした。では取得するときには・・・そう#getItem()です。

getItemメソッドはキーを指定して取得するためのものです。

//検索のキーを指定
Key key = new Key(new AttributeValue().withN("5"));

//リクエストの生成
GetItemRequest request = new GetItemRequest("testdb", key);

//実行
GetItemResult item = client.getItem(request);

これだけです。これで先ほど10件生成した中の6番目、キー値:5のデータが取得できました。

GetItemResult には#getItem()というメソッドがあり、Mapが帰ってきます。楽チンですね。
ちなみにtoString()の結果がわかりやすいので、デバッグ時にはそのままこのオブジェクトを渡すとよいでしょう。

System.out.println(item);

結果
{Item: {id={N: 5, }, 名前={S: しんさん5, }}, ConsumedCapacityUnits: 0.5, }

データの更新

更新の仕方も同じです。#updateItemを利用します。
ただし、メソッド名が長ったらしくて多少めんどいですが、単純ではあります。

更新対象のキー(Key)と更新する値の集合(Map)の2つがあると理解するのがポイントです。

//更新対象
Key key = new Key(new AttributeValue().withN("3"));

//更新する値のセット
Map<String, AttributeValueUpdate> values = new HashMap<String, AttributeValueUpdate>();
values.put("名前",  new AttributeValueUpdate().withValue(new AttributeValue().withS("更新後")));

//リクエストの生成
UpdateItemRequest request = new UpdateItemRequest("testdb", key, values);

//更新
client.updateItem(request);

AttributeValueUpdateというのが新しく出てきました。AttributeValueとの違いは、値そのものを保持するだけではなく、更新するのか、それともそのデータを削除するのか、加算するのかがあるためです。それはアクションと呼ばれます。無指定ではPUT、つまり上書き保存です。

たとえばこれは加算のアクションの例です。「no」という項目の値が1増えていきます。

values.put("no",  
  new AttributeValueUpdate().
      withAction(AttributeAction.ADD).
      withValue(new AttributeValue().withN("1"))
);

これは現在の値に対してインクリメントしていきます。現在の値がなにか知る必要がないというのは面白いです。SQL
になれているひとなら積極的に使いたいといったところでしょうか。

削除

データの削除は簡単です。キーを指定するだけです。

//削除対象
Key key = new Key(new AttributeValue().withN("3"));

//リクエストの生成
DeleteItemRequest request = new DeleteItemRequest("testdb", key);

//実行
client.deleteItem(request);

検索条件を入れる

キー値をダイレクトに指定して1件取得するのが#getItemなのにたしいて、検索条件を入れて複数件検索するのが#scanです。

//リクエストの生成
ScanRequest request = new ScanRequest("testdb");

//取得する項目指定
request.withAttributesToGet("id", "名前");

//検索条件
Map<String, Condition> filter = new HashMap<String, Condition>();
filter.put("名前", 
        new Condition().
        withComparisonOperator(ComparisonOperator.BETWEEN).//検索方法
        withAttributeValueList(new AttributeValue().withS("更新3"), new AttributeValue().withS("更新7")));
request.withScanFilter(filter);

//実行
ScanResult result = client.scan(request);

ConditionをMapにつめたのが検索条件となります。キー値が検索項目で、値がConditionです。こいつに検索方法と利用する値を詰め込みます。上の例では「更新3」〜「更新7」までのデータ5件を取得します。

結果セットのScanResultには#getItemes()メソッドがあります。これはMapをListにつめたものです。
以下のように取り出すとよいでしょう。

for (Map<String, AttributeValue> row : result.getItems()) {
  System.out.printf("id:%s, 名前:%s%n" ,
    row.get("id").getN()  , 
    row.get("名前").getS());
}

軽く基本操作を紹介しました。これがいわば低レベルAPIになります。もっと簡単なものが用意されています。次回はそちらを紹介します。