[AWS]DynamoDBで並列スキャンを試してみよう[Node.js]

2013.06.07

Dynamoに新機能が追加されました

少し前になりますが、ここにあるように、Dynamoに新機能が追加されました。

  • 並列スキャン機能
  • すばやいプロビジョンドスループットの変更
  • リードキャパシティの測定方法を変更によるコスト削減

どれもうれしい新機能なのですが、この記事では並列スキャンをaws-sdk-jsから使用する方法について紹介します。

Dynamoの並列スキャン

ここの解説の繰り返しになってしまいますが、DynamoDBはアクセス速度高速化のため、複数の物理ストレージのパーティションにデータを保存しています。
Dyanamoのスキャンのよるスループットはひとつのパーティションの最大スループットによって制限されますが、
これにより、スキャンがテーブルに設定されたリードキャパシティをフル活用できないという問題があります。

そこで、より高速にデータを取得するため、並列スキャンを使用することができます。
並列スキャンは、複数のワーカースレッド/プロセスを同時に実行し、各ワーカーはテーブルの別のセグメントを他のワーカーと同時にスキャンすることができます。
Dynamoの並列スキャンは次のように2つの追加パラメータを受けとるようになっています。

  • TotalSegments:テーブルに同時にアクセスするワーカーの数
  • Segment:コールしているワーカーによってアクセスされたテーブルのセグメント

例えば4つのワーカーを持つ場合、並列スキャンを開始すると次のような呼び出しを行います。

Scan(TotalSegments=4, Segment=0, ...)
Scan(TotalSegments=4, Segment=1, ...)
Scan(TotalSegments=4, Segment=2, ...)
Scan(TotalSegments=4, Segment=3, ...)

TotalSegmentsとSegmentパラメータを使用すると、テーブル内の項目の特定のブロックだけスキャンをすることができます。
では、aws-sdk-jsで並列スキャンを試してみましょう。

ちなみに、上記説明はここに詳しく書いてあるので、ご確認ください。

環境構築方法

今回使用した動作環境は以下のとおりです。

  • OS : MacOS X 10.7.5
  • Node.js : v0.10.8
  • npm : 1.2.23

npmを使ってaws-sdkと必要なモジュールをインストールしておきましょう。

% mkdir aws-sample
% cd aws-sample
% npm install aws-sdk async

aws-sdk-jsで並列スキャンを行うサンプル

テスト用データの準備

最初にテストデータを準備しましょう。 今回はAWSコンソールで「dynamo_test」というテスト用テーブルを作成し、テストデータを5000件登録しておきました。
※APIで一気に登録しようとするとwriteキャパシティの制限にひっかかる可能性があるので、数百件づつ実行

まずは普通の検索

まずは通常の検索(というかデータの取得)をおこなってみます。全件データ検索を行い、データの数を表示しているだけです。

//app.js
var AWS = require('aws-sdk');

//AWSアカウント情報の設定
AWS.config.update({
    "accessKeyId": <アクセスキーID>,
    "secretAccessKey": <シークレットアクセスキー>,
    "region": <リージョン> });

//API実行時間の計測
AWS.events.on('send',function startSend(resp) {
    resp.startTime = new Date().getTime();
}).on('complete', function calculateTime(resp) {
        var time = (new Date().getTime() - resp.startTime) / 1000;
        console.log(resp.request.operation + ':' + time + ' seconds');
});

var db = new AWS.DynamoDB();

db.scan({TableName: "dynamo_test",
    Select: "ALL_ATTRIBUTES",
}, function (err, res) {
    console.log("data count:",res.Count);
});

上記プログラムを実行してみます。私の環境では実行時間は0.584秒でした。(全件取得なので件数は5000件です)

% node app.js
scan:0.584 seconds
data count:5000

並列スキャンで検索

では次に、並列でデータ取得を実行してみましょう。
セグメントを4つに分割し、asyncモジュールをつかって同時にデータ検索を実行しています。
個々のワーカーの結果をコールバックに渡し、最終的にそれらを合算して合計件数を算出しています。

・
・
var pstart =new Date().getTime();
async.parallel([
    function (callback) {
        db.scan({TableName: "dynamo_test",
            Select: "ALL_ATTRIBUTES",
            TotalSegments: 4,//テーブルにアクセスするワーカー数
            Segment: 0//コールしているワーカーによってアクセスされたテーブルのセグメント
        }, function (err, res) {
            callback(null, res.Count);
        });
    },
    function (callback) {
        db.scan({TableName: "dynamo_test",
            Select: "ALL_ATTRIBUTES",
            TotalSegments: 4,//テーブルにアクセスするワーカー数
            Segment: 1//コールしているワーカーによってアクセスされたテーブルのセグメント
        }, function (err, res) {
            callback(null, res.Count);
        });
    },
    function (callback) {
        db.scan({TableName: "dynamo_test",
            Select: "ALL_ATTRIBUTES",
            TotalSegments: 4,//テーブルにアクセスするワーカー数
            Segment: 2//コールしているワーカーによってアクセスされたテーブルのセグメント
        }, function (err, res) {
            callback(null, res.Count);
        });
    },
    function (callback) {
        db.scan({TableName: "dynamo_test",
            Select: "ALL_ATTRIBUTES",
            TotalSegments: 4,//テーブルにアクセスするワーカー数
            Segment: 3//コールしているワーカーによってアクセスされたテーブルのセグメント
        }, function (err, res) {
            callback(null, res.Count);
        });
    }
], function (err, results) {
    if (err) { throw err; }
    var pend = new Date().getTime();
    console.log('total time:' + ((pend - pstart) / 1000)) + " seconds";
    console.log('data count = ' + results.reduce(function(x,y) { return x + y }));
});

結果を見てみましょう。先ほどよりわずかですが、速くなっています。
データが5000件程度なのであまり違いがわかりませんが、もっと多数のデータになれば、大きな差が出るようになるはずです。(たぶん。。)

% node app.js
scan:0.34 seconds
scan:0.318 seconds
scan:0.325 seconds
scan:0.328 seconds
total time:0.376 seconds
data count = 5000

まとめ

今回はDynamoの新機能、並列スキャンをaws-sdk-jsから使ってみました。
とかく大量データの取得/処理は時間がかかりがちなので、データが多量の場合には並列スキャンが有効に使えそうです。

参考サイトなど