SimpleDBのデータをアカウント間で移行する(完全版)

なかなか完全版のものがなかったので。

Promiseを使うことでコールバック地獄に陥らずにすみました。

process.env.TZ = "Asia/Tokyo";
var AWS = require('aws-sdk');
var develop1 = new AWS.SharedIniFileCredentials({profile: 'develop1'});
var develop2 = new AWS.SharedIniFileCredentials({profile: 'develop2'});
AWS.config.update({
    region: 'ap-northeast-1'
});

var simpledb = new AWS.SimpleDB({apiVersion: '2009-04-15', region: 'ap-northeast-1' , credentials: develop1});
var simpledb2 = new AWS.SimpleDB({apiVersion: '2009-04-15', region: 'ap-northeast-1' , credentials: develop2});

var domainName = 'your_domain_name';

var promises = [];

getAllItems().then(function(data) {
    createDomain()
        .then(function() {
            data.forEach(function(val, index, ar) {
                promises.push(putAttributes(val.Name, val.Attributes));
            });
            Promise.all(promises)
                .then(function(results) {
                    console.log("***********");
                    console.log(data.length);
                });
        });
});

function createDomain() {
    var params = {
        DomainName: domainName
    };
    return new Promise(function(resolve, reject) {
        simpledb2.createDomain(params, function(err, response) {
            if (err) {
                console.log(err, err.stack);
                reject(err);
                return;
            }
            resolve();
        });
    });
}

function putAttributes(itemName, attributes) {
    var attributesList = [];
    attributes.forEach(function(val, index, ar) {
        attributesList = attributesList.concat({
            Name: val.Name,
            Value: val.Value,
            Replace: true
        });
    });
    var params = {
        Attributes: attributesList,
        DomainName: domainName,
        ItemName: itemName,
    };
    return new Promise(function(resolve, reject) {
        simpledb2.putAttributes(params, function(err, response) {
            if (err) {
                console.log(err, err.stack);
                reject(err);
                return;
            }
            resolve(response);
        });
    });
}

function getAllItems() {
    var dataList = [];
    var params = {
        SelectExpression: 'SELECT * FROM `' + domainName + '`',
        ConsistentRead: true,
        NextToken: null
    };

    function recursiveCall(params, resolve, reject) {
        simpledb.select(params, function(err, data) {
            if (err) {
                console.log(err, err.stack);
                reject(err);
            } else {
                dataList = dataList.concat(data.Items);
                if (data.NextToken) {
                    params.NextToken = data.NextToken;
                    recursiveCall(params, resolve, reject);
                } else {
                    resolve(dataList);
                }
            }
        });
    }

    return new Promise(function(resolve, reject) {
        recursiveCall(params, resolve, reject);
    });
}

アカウントの切替

以下のようにして切り替えてしまうのが一番確実かつ手っ取り早いです。

SimpleDB 以外の S3 なども同様にして切り替えることが可能です。

var simpledb = new AWS.SimpleDB({apiVersion: '2009-04-15', region: 'ap-northeast-1' , credentials: develop1});
var simpledb2 = new AWS.SimpleDB({apiVersion: '2009-04-15', region: 'ap-northeast-1' , credentials: develop2});

全データの取得

AWSのlist取得系の処理の多くはNextTokenによるページングがあるため、以下のようにすることで全件のデータを取得することが可能です。

nextToken ではなく NextToken なのでご注意あれ。

この例では、アカウントの切り替えにコストがかかるかも?と思ったので全件一気に取得してから新しい方にデータを入れていますが、古い方からデータを一定量取得しつつ新しい方にいれつつをループで回したほうがいいかもしれません。

AWSの場合はAPI発行数のリミットとか処理速度の遅さとかあまり気にすることないかと思いますが、他のサービスの場合などではその辺を考えるとデータを取得した分ずつ入れていくほうがベターかも。

function getAllItems() {
    var dataList = [];
    var params = {
        SelectExpression: 'SELECT * FROM `' + domainName + '`',
        ConsistentRead: true,
        NextToken: null
    };

    function recursiveCall(params, resolve, reject) {
        simpledb.select(params, function(err, data) {
            if (err) {
                console.log(err, err.stack);
                reject(err);
            } else {
                dataList = dataList.concat(data.Items);
                if (data.NextToken) {
                    params.NextToken = data.NextToken;
                    recursiveCall(params, resolve, reject);
                } else {
                    resolve(dataList);
                }
            }
        });
    }

    return new Promise(function(resolve, reject) {
        recursiveCall(params, resolve, reject);
    });
}

ドメインの作成

新しいアカウントの方にドメインを作成します。

function createDomain() {
    var params = {
        DomainName: domainName
    };
    return new Promise(function(resolve, reject) {
        simpledb2.createDomain(params, function(err, response) {
            if (err) {
                console.log(err, err.stack);
                reject(err);
                return;
            }
            resolve();
        });
    });
}

データ投入

取得したデータを加工しないままパラメータとして渡せるといいのですが、SimpleDB の場合は Attributes の Replace も渡してあげる必要があるため、データをゴニョゴニョしています。

function putAttributes(itemName, attributes) {
    var attributesList = [];
    attributes.forEach(function(val, index, ar) {
        attributesList = attributesList.concat({
            Name: val.Name,
            Value: val.Value,
            Replace: true
        });
    });
    var params = {
        Attributes: attributesList,
        DomainName: domainName,
        ItemName: itemName,
    };
    return new Promise(function(resolve, reject) {
        simpledb2.putAttributes(params, function(err, response) {
            if (err) {
                console.log(err, err.stack);
                reject(err);
                return;
            }
            resolve(response);
        });
    });
}

あとは取得した全データを入れたいのですが、レスポンスをうまいことハンドリングするために Promise.all を使うといいようです。

data.forEach(function(val, index, ar) {
  promises.push(putAttributes(val.Name, val.Attributes));
});
Promise.all(promises)
  .then(function(results) {
    console.log("***********");
    console.log(data.length);
});

実行

あとはこのスクリプトを index.js とかで保存して、いい感じの package.json 用意して

aws cliとかでアカウントごとのprofileの設定をした上で

npm install
node index.js

としてあげれば実行されます。

結論

Node.js最高! Promise最高! AWS SDK for JavaScript最高!