DynamoDBはAWS Signature Version 4で速くなったのか

AWS Signature Version 4とは

AWSは各種APIにアクセスするための方法をいくつか提供しています。コマンドラインツール、各種プログラミングSDK、WebService API、Query APIなどが代表的です。この中でもQuery APIが最もベーシックで様々な環境から扱いやすくなっています。今までSTS(Security Token Service)で一時利用キーを取得していたDynamoDBがQueryベースの一発認証に対応したので初回の接続が高速化したということで動作確認をしたいと思います。

AWS Toolkit for Eclipse

まず始めに確認を簡単にするために、AWS Toolkit for Eclipseをインストールします。DynamoDBがAWS Signature Version 4に対応したのは、AWS SDK for Java 1.3.13からです。STSを使っている1.3.12も選択できるようにしておいてください。

プロジェクトの新規作成でAWS SDK for Javaのバージョンを指定する

プロジェクトの新規作成でAWS Java Projectを指定してDynamoDBのサンプルを作成しました。テーブル作成に時間が掛かるのであらかじめ作っておき、テストをする際には処理を省いています。

import java.util.HashMap;
import java.util.Map;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.services.dynamodb.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodb.model.AttributeValue;
import com.amazonaws.services.dynamodb.model.ComparisonOperator;
import com.amazonaws.services.dynamodb.model.Condition;
import com.amazonaws.services.dynamodb.model.DescribeTableRequest;
import com.amazonaws.services.dynamodb.model.PutItemRequest;
import com.amazonaws.services.dynamodb.model.PutItemResult;
import com.amazonaws.services.dynamodb.model.ScanRequest;
import com.amazonaws.services.dynamodb.model.ScanResult;
import com.amazonaws.services.dynamodb.model.TableDescription;

/**
 * This sample demonstrates how to perform a few simple operations with the
 * Amazon DynamoDB service.
 */
public class AmazonDynamoDBSample {

    /*
     * Important: Be sure to fill in your AWS access credentials in the
     *            AwsCredentials.properties file before you try to run this
     *            sample.
     * http://aws.amazon.com/security-credentials
     */

    static AmazonDynamoDBClient dynamoDB;
    static long startTime = 0;
    static long endTime = 0;

    /**
     * The only information needed to create a client are security credentials
     * consisting of the AWS Access Key ID and Secret Access Key. All other
     * configuration, such as the service endpoints, are performed
     * automatically. Client parameters, such as proxies, can be specified in an
     * optional ClientConfiguration object when constructing a client.
     *
     * @see com.amazonaws.auth.BasicAWSCredentials
     * @see com.amazonaws.auth.PropertiesCredentials
     * @see com.amazonaws.ClientConfiguration
     */
    private static void init() throws Exception {
    	startTime = System.currentTimeMillis();
    
        AWSCredentials credentials = new PropertiesCredentials(
                AmazonDynamoDBSample.class.getResourceAsStream("AwsCredentials.properties"));

        dynamoDB = new AmazonDynamoDBClient(credentials);
    }


    public static void main(String[] args) throws Exception {
        init();

        try {
            String tableName = "my-favorite-movies-table2";

            // Describe our new table
            DescribeTableRequest describeTableRequest = new DescribeTableRequest().withTableName(tableName);
            TableDescription tableDescription = dynamoDB.describeTable(describeTableRequest).getTable();
            System.out.println("Table Description: " + tableDescription);

            // Add an item
            Map<String, AttributeValue> item = newItem("Bill & Ted's Excellent Adventure", 1989, "****", "James", "Sara");
            PutItemRequest putItemRequest = new PutItemRequest(tableName, item);
            PutItemResult putItemResult = dynamoDB.putItem(putItemRequest);
            System.out.println("Result: " + putItemResult);

            // Add another item
            item = newItem("Airplane", 1980, "*****", "James", "Billy Bob");
            putItemRequest = new PutItemRequest(tableName, item);
            putItemResult = dynamoDB.putItem(putItemRequest);
            System.out.println("Result: " + putItemResult);

            // Scan items for movies with a year attribute greater than 1985
            HashMap<String, Condition> scanFilter = new HashMap<String, Condition>();
            Condition condition = new Condition()
                .withComparisonOperator(ComparisonOperator.GT.toString())
                .withAttributeValueList(new AttributeValue().withN("1985"));
            scanFilter.put("year", condition);
            ScanRequest scanRequest = new ScanRequest(tableName).withScanFilter(scanFilter);
            ScanResult scanResult = dynamoDB.scan(scanRequest);
            System.out.println("Result: " + scanResult);
            endTime = System.currentTimeMillis();
            System.out.println("time : " + (endTime - startTime));

        } catch (AmazonServiceException ase) {
            System.out.println("Caught an AmazonServiceException, which means your request made it "
                    + "to AWS, but was rejected with an error response for some reason.");
            System.out.println("Error Message:    " + ase.getMessage());
            System.out.println("HTTP Status Code: " + ase.getStatusCode());
            System.out.println("AWS Error Code:   " + ase.getErrorCode());
            System.out.println("Error Type:       " + ase.getErrorType());
            System.out.println("Request ID:       " + ase.getRequestId());
        } catch (AmazonClientException ace) {
            System.out.println("Caught an AmazonClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with AWS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message: " + ace.getMessage());
        }
    }

    private static Map<String, AttributeValue> newItem(String name, int year, String rating, String... fans) {
        Map<String, AttributeValue> item = new HashMap<String, AttributeValue>();
        item.put("name", new AttributeValue(name));
        item.put("year", new AttributeValue().withN(Integer.toString(year)));
        item.put("rating", new AttributeValue(rating));
        item.put("fans", new AttributeValue().withSS(fans));

        return item;
    }

}

テスト結果

何度か実行した平均値を書いておきます。

STS(Security Token Service)利用 (AWS SDK for Java 1.3.12) Query API Signature Version 4 (AWS SDK for Java 1.3.13)
DynamoDB接続サンプル 4.22秒 2.78秒

まとめ

SDKのバージョンを変えてまったく同じプログラムを実行したところ、約35%も早くなりました!STSは2回リクエストする分手間と時間が掛かっていましたが、Query APIを使う事で一発で認証をすることができました。SDKを利用している分には表面上のプログラムを一切変更する必要がないため、利用者にとってはメリットだけですね。node.jsを使ったDynamoDBライブラリであるdynodeにも早速要望が上がっていましたので、近いうちに対応されるのではないでしょうか。今日から君もDynamoDBアクセスを高速化しよう!

参考資料

Release: AWS SDK for Java 1.3.13

AWS Toolkit for Eclipse