AWS 1ヶ月導入記 part 7 SimpleDBの性能調査


目次

みなさん、こんにちは!フリューでモバイルサイト開発を行っている鷲見といいます。

あるソーシャルプラットフォーム上のアプリケーションを構築するプロジェクトでの、クラウド環境の導入状況をそのまま記事にしています。

前回はSimpleJPAを用いてSimpleDBを操作するプログラムについて解説しました。

クラウド1ヶ月導入記 part 1 比較編

クラウド1ヶ月導入記 part 2 見積編

Amazon Web Services 1ヶ月導入記 part 1 データベース編

Amazon Web Services 1ヶ月導入記 part 2 アーキテクチャ設計編

Amazon Web Services 1ヶ月導入記 part 3 SimpleDBの特徴と制限

Amazon Web Services 1ヶ月導入記 part 4 SimpleDBの制限を回避する

AWS 1ヶ月導入記 part 5 AWS SDK for Javaを用いたプログラミング

AWS 1ヶ月導入記 part 6 SimpleJPAを用いたプログラミング

次回からはバックアップなどと言っていたのですが、JAWS-UG Kyoto 第2回勉強会で少しお話させていただいた、SimpleDBの性能について少し掘り下げて紹介させていただきます。

なお、JAWS-UG Kyoto 勉強会第2回の私の発表資料は以下のページをご覧ください。

JAWS-UG Kyoto勉強会 第2回で発表させていただきました!

性能調査方法

東京リージョン上のEC2インスタンス(m1.small)から、各リージョンのSimpleDBへのアクセス時間を計測します。

本来であればAPIを直接呼び出すのがよいのですが、手っ取り早くAWS SDK for JavaのAPI呼び出しに該当するメソッド呼び出しにどのくらいの時間がかかるかを計測します。

AWS SDK for Javaを経由してのAPI呼び出しですので、実際にはAPIの呼び出し時間だけではなく、各種Requestオブジェクトからリクエストパラメタへの変換や、レスポンスのXMLデータから各種Resultオブジェクトへの変換の処理時間も含まれます。

選択・挿入・更新・削除のCRUDをそれぞれ100回ずつ呼び出し、その平均値を計測します。

性能調査プログラム

AWS Toolkit for Eclipseを使うとSimpleDBのサンプルプログラムを作成することができます。今回はこのSimpleDBのサンプルプログラムをベースに調査用に少し変更を加えてみました。

/*
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file is distributed
 * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied. See the License for the specific language governing
 * permissions and limitations under the License.
 */
import java.util.ArrayList;
import java.util.List;

import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.services.simpledb.AmazonSimpleDB;
import com.amazonaws.services.simpledb.AmazonSimpleDBClient;
import com.amazonaws.services.simpledb.model.BatchDeleteAttributesRequest;
import com.amazonaws.services.simpledb.model.BatchPutAttributesRequest;
import com.amazonaws.services.simpledb.model.CreateDomainRequest;
import com.amazonaws.services.simpledb.model.DeletableItem;
import com.amazonaws.services.simpledb.model.DeleteAttributesRequest;
import com.amazonaws.services.simpledb.model.DeleteDomainRequest;
import com.amazonaws.services.simpledb.model.PutAttributesRequest;
import com.amazonaws.services.simpledb.model.ReplaceableAttribute;
import com.amazonaws.services.simpledb.model.ReplaceableItem;
import com.amazonaws.services.simpledb.model.SelectRequest;
import com.amazonaws.services.simpledb.model.SelectResult;

public class SimpleDBSample {

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

        AmazonSimpleDB sdb = new AmazonSimpleDBClient(new PropertiesCredentials(
                SimpleDBSample.class.getResourceAsStream("AwsCredentials.properties")));
        //リージョンのエンドポイント指定
        sdb.setEndpoint(args[0]);
        
        int roopCount = 100;
        long insertTimeSum = 0;
        long selectTimeSum = 0;
        long updateTimeSum = 0;
        long deleteTimeSum = 0;
        long startTime;
        long finishedTime;
        
        try {
            // ドメインの作成
            String myDomain = "MyStore";
            sdb.createDomain(new CreateDomainRequest(myDomain));
            
            for(int i=0;i<roopCount;i++){
                // 初期データの投入
                sdb.batchPutAttributes(new BatchPutAttributesRequest(myDomain, createSampleData()));
                
                // データの1件挿入(PutAttributes API)
                ReplaceableItem rItem = new ReplaceableItem("Item_06").withAttributes(
                        new ReplaceableAttribute("Category", "Car Parts", true),
                        new ReplaceableAttribute("Subcategory", "Exhaust", true),
                        new ReplaceableAttribute("Name", "O2 Sensor", true),
                        new ReplaceableAttribute("Make", "Audi", true),
                        new ReplaceableAttribute("Model", "TT Coupe", true),
                        new ReplaceableAttribute("Year", "2009", true),
                        new ReplaceableAttribute("Year", "2010", true),
                        new ReplaceableAttribute("Year", "2011", true));
                PutAttributesRequest putAttributesRequest = new PutAttributesRequest(myDomain,rItem.getName() ,rItem.getAttributes());
                startTime = System.currentTimeMillis();
                sdb.putAttributes(putAttributesRequest);
                finishedTime = System.currentTimeMillis();
                insertTimeSum += finishedTime - startTime;
                
                // データの取得(Select API)
                String selectExpression = "select * from `" + myDomain + "` where Category = 'Clothes'";
                SelectRequest selectRequest = new SelectRequest(selectExpression);
                startTime = System.currentTimeMillis();
                sdb.select(selectRequest);
                finishedTime = System.currentTimeMillis();
                selectTimeSum += finishedTime - startTime;
    
                // データの更新(PutAttributes API)
                List<ReplaceableAttribute> replaceableAttributes = new ArrayList<ReplaceableAttribute>();
                replaceableAttributes.add(new ReplaceableAttribute("Size", "Medium", true));
                startTime = System.currentTimeMillis();
                sdb.putAttributes(new PutAttributesRequest(myDomain, "Item_03", replaceableAttributes));
                finishedTime = System.currentTimeMillis();
                updateTimeSum += finishedTime - startTime;
                
                // データの削除(DeleteAttributes API)
                startTime = System.currentTimeMillis();
                sdb.deleteAttributes(new DeleteAttributesRequest(myDomain, "Item_03"));
                finishedTime = System.currentTimeMillis();
                deleteTimeSum += finishedTime - startTime;
                
                // データの削除
                sdb.batchDeleteAttributes(new BatchDeleteAttributesRequest(myDomain,deleteSampleData()));
            }
            //ドメインの削除
            sdb.deleteDomain(new DeleteDomainRequest(myDomain));
            
            System.out.println("insert:AVG:"+(float)insertTimeSum/roopCount);
            System.out.println("select:AVG:"+(float)selectTimeSum/roopCount);
            System.out.println("update:AVG:"+(float)updateTimeSum/roopCount);
            System.out.println("delete:AVG:"+(float)deleteTimeSum/roopCount);
            
        } catch (AmazonServiceException ase) {
            System.out.println("Caught an AmazonServiceException, which means your request made it "
                    + "to Amazon SimpleDB, 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 SimpleDB, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message: " + ace.getMessage());
        }
    }
    
    /**
     * Creates an array of SimpleDB DeletableItems.
     *
     * @return An array of delete item data.
     */
    private static List<DeletableItem> deleteSampleData() {
        List<DeletableItem> deleteData = new ArrayList<DeletableItem>();
        deleteData.add(new DeletableItem().withName("Item_01"));
        deleteData.add(new DeletableItem().withName("Item_02"));
        deleteData.add(new DeletableItem().withName("Item_04"));
        deleteData.add(new DeletableItem().withName("Item_05"));
        deleteData.add(new DeletableItem().withName("Item_06"));
        return deleteData;
    }

    /**
     * Creates an array of SimpleDB ReplaceableItems populated with sample data.
     *
     * @return An array of sample item data.
     */
    private static List<ReplaceableItem> createSampleData() {
        List<ReplaceableItem> sampleData = new ArrayList<ReplaceableItem>();

        sampleData.add(new ReplaceableItem("Item_01").withAttributes(
                new ReplaceableAttribute("Category", "Clothes", true),
                new ReplaceableAttribute("Subcategory", "Sweater", true),
                new ReplaceableAttribute("Name", "Cathair Sweater", true),
                new ReplaceableAttribute("Color", "Siamese", true),
                new ReplaceableAttribute("Size", "Small", true),
                new ReplaceableAttribute("Size", "Medium", true),
                new ReplaceableAttribute("Size", "Large", true)));

        sampleData.add(new ReplaceableItem("Item_02").withAttributes(
                new ReplaceableAttribute("Category", "Clothes", true),
                new ReplaceableAttribute("Subcategory","Pants", true),
                new ReplaceableAttribute("Name", "Designer Jeans", true),
                new ReplaceableAttribute("Color", "Paisley Acid Wash", true),
                new ReplaceableAttribute("Size", "30x32", true),
                new ReplaceableAttribute("Size", "32x32", true),
                new ReplaceableAttribute("Size", "32x34", true)));

        sampleData.add(new ReplaceableItem("Item_03").withAttributes(
                new ReplaceableAttribute("Category", "Clothes", true),
                new ReplaceableAttribute("Subcategory", "Pants", true),
                new ReplaceableAttribute("Name", "Sweatpants", true),
                new ReplaceableAttribute("Color", "Blue", true),
                new ReplaceableAttribute("Color", "Yellow", true),
                new ReplaceableAttribute("Color", "Pink", true),
                new ReplaceableAttribute("Size", "Large", true),
                new ReplaceableAttribute("Year", "2006", true),
                new ReplaceableAttribute("Year", "2007", true)));

        sampleData.add(new ReplaceableItem("Item_04").withAttributes(
                new ReplaceableAttribute("Category", "Car Parts", true),
                new ReplaceableAttribute("Subcategory", "Engine", true),
                new ReplaceableAttribute("Name", "Turbos", true),
                new ReplaceableAttribute("Make", "Audi", true),
                new ReplaceableAttribute("Model", "S4", true),
                new ReplaceableAttribute("Year", "2000", true),
                new ReplaceableAttribute("Year", "2001", true),
                new ReplaceableAttribute("Year", "2002", true)));

        sampleData.add(new ReplaceableItem("Item_05").withAttributes(
                new ReplaceableAttribute("Category", "Car Parts", true),
                new ReplaceableAttribute("Subcategory", "Emissions", true),
                new ReplaceableAttribute("Name", "O2 Sensor", true),
                new ReplaceableAttribute("Make", "Audi", true),
                new ReplaceableAttribute("Model", "S4", true),
                new ReplaceableAttribute("Year", "2000", true),
                new ReplaceableAttribute("Year", "2001", true),
                new ReplaceableAttribute("Year", "2002", true)));

        return sampleData;
    }
}

ソースコードはこちら(GitHub)

プログラムの引数にはSimpleDBのエンドポイントを指定します。

各リージョンのSimpleDBのエンドポイントは以下のとおりです。

Asia Pacific (Tokyo) sdb.ap-northeast-1.amazonaws.com
Asia Pacific (Singapore) sdb.ap-southeast-1.amazonaws.com
EU West (Ireland) sdb.eu-west-1.amazonaws.com
US West (N. California) sdb.us-west-1.amazonaws.com
US East (Virginia) sdb.amazonaws.com
US West (Oregon) sdb.us-west-2.amazonaws.com

調査結果詳細

Select(Select API)

Asia Pacific (Tokyo) 38.8ms
Asia Pacific (Singapore) 126.8ms
EU West (Ireland) 332.6ms
US West (N. California) 154.1ms
US East (Virginia) 234.9ms
US West (Oregon) 163.7ms

Insert(PutAttributes API)

Asia Pacific (Tokyo) 369.2ms
Asia Pacific (Singapore) 431.3ms
EU West (Ireland) 659.8ms
US West (N. California) 467.4ms
US East (Virginia) 585.3ms
US West (Oregon) 428.5ms

Update(PutAttributes API)

Asia Pacific (Tokyo) 287.8ms
Asia Pacific (Singapore) 378.0ms
EU West (Ireland) 648.5ms
US West (N. California) 351.5ms
US East (Virginia) 504.8ms
US West (Oregon) 347.4ms

Delete(DeleteAttributes API)

Asia Pacific (Tokyo) 244.8ms
Asia Pacific (Singapore) 339.7ms
EU West (Ireland) 573.2ms
US West (N. California) 339.7ms
US East (Virginia) 534.0ms
US West (Oregon) 291.8ms

調査結果の考察

各リージョン毎の差異について

当然ですが、呼び出し側のEC2インスタンスと同一リージョンのSimpleDBの呼び出しは速いのがよくわかります。

東京リージョンからの処理時間の順としては

  • 1.Asia Pacific(Tokyo)
  • 2.Asia Pacific(Singapore),US West(N. California),US West(Oregon)
  • 3.US East(Virginia)
  • 4.EU West(Ireland)

のとなりました。

2位はAsia Pacific(Singapore)がダントツかなと想像していましたが、2つのUS Westリージョンが健闘し、Asia Pacific(Singapore)とほぼ同じくらいの値を叩き出しています。

CRUDの差異について

Selectはそれなりに早い速度で結果が返ってきていますが、挿入・更新・削除は同一リージョンからの呼び出しでも1回200ミリ秒以上かかっているのがわかります。挿入・更新・削除が多く発生するアプリケーションにはSimpleDBはあまり向かないということがわかります。

まとめ

今回はSimpleDBの性能調査をJavaプログラムを作成し、実施してみました。

  • 呼び出し側と同一リージョンのSimpleDBであればそれなりに早い
  • 挿入・更新・削除といったデータ操作は少し遅い

ということがわかったかと思います。

さて、次回こそは前回から予告していた、AWSでの各種バックアップについてお話ししたいと思います。