- 追加された行はこの色です。
- 削除された行はこの色です。
[[AWSメモ]] >
* DynamoDBでドキュメント型の更新 [#m5518f9f]
#setlinebreak(on);
#contents
-- 参考
--- http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html#Expressions.ConditionExpressions.SimpleComparisons
** DynamoDB でドキュメント型の更新 [#se9cbe5d]
#html(<div style="padding-left:20px;">)
DynamoDB でドキュメント型の更新では、ドキュメントの一部の項目のみを指定して更新する事はできない。
※mongoDB では $set を使う事で更新可能。
#html(<div class="va-top" style="border:1px solid #333;padding:10px;">)
#html(<div class="ib va-top pl20">)
更新前
|id|data|h
|test01|{ "col1", "123", "col2" : "456" }|
#html(</div>)
#html(<div class="ib va-top pl20">)
更新処理
#mycode2(){{
var data = { "col1" : "ABC" };
var params = {
TableName : 'Sample', Key: { "id" : "test01" },
ExpressionAttributeNames: { "#col1" : "col1" },
ExpressionAttributeValues: { ":col1" : data, },
UpdateExpression: "SET #col1 = :col1"
};
documentClient.update(params, function(err, data) { ... }
}}
#html(</div>)
#html(<div class="ib va-top pl20">)
更新後
|id|data|h
|test01|{ "col1", "ABC" }|
#html(<span class="pl20">※col2 が 失われている</span>)
#html(</div>)
#html(</div>)
なので、ドキュメント型の一部のみ更新したい場合でも、ドキュメント全文を指定する必要がある為、
更新前にいちど対象データのドキュメント全文を読み込んでから、更新したい箇所に変更後の値をセットして更新する必要がある。
#html(<div class="va-top" style="border:1px solid #333;padding:10px;">)
#html(<div class="ib va-top pl20">)
更新前
|id|data|h
|test01|{ "col1", "123", "col2" : "456" }|
#html(</div>)
#html(<div class="ib va-top pl20">)
更新処理
#mycode2(){{
var readParams = { ... };
documentClient.query(readParams, function(err, data) {
data.col1 = "ABC";
var params = {
TableName : 'Sample', Key: { "id" : "test01" },
ExpressionAttributeNames: { "#col1" : "col1" },
ExpressionAttributeValues: { ":col1" : data, },
UpdateExpression: "SET #col1 = :col1"
};
documentClient.update(params, function(err, data) { ... }
});
}}
#html(</div>)
#html(<div class="ib va-top pl20">)
更新後
|id|data|h
|test01|{ "col1", &color(red){"ABC"};, "col2" : "456" }|
#html(</div>)
#html(</div>)
この場合に、複数の処理で同時に処理が走る場合に、排他を考慮せずに、読み込み&更新を行ってしまうと、
後勝ちになってしまう為、最初の更新で行われた変更が失われてしまう事がある。
※読み込みから更新までの間に、他ユーザに更新された場合。
#html(<div class="va-top" style="border:1px solid #333;padding:10px;">)
#html(<div class="ib va-top">)
#html(<span class="fa fa-user-o"> ユーザA</span>)
&br;
#html(<div class="ib" style="padding-top:15px;">)
|test01|{ "col1", &color("red"){"ABC"};, "col2" : "456" }|
#html(</div>)
#html(<div class="ib pl10">)
#html(<span class="fa fa-long-arrow-right" style="font-size:20px;"></span>)
#html(</div>)
#html(</div>)
#html(<div class="ib va-top pl20 pr20">)
#html(<span class="fa fa-database"> DB</span>)
#html(<div>)
|id|data|h
|test01|{ "col1", "123", "col2" : "456" }|
#html(</div>)
#html(<div style="padding-top:30px;">)
|test01|{ "col1", &color(red){"ABC"};, "col2" : "456" }|
#html(</div>)
#html(<div style="padding-top:30px;">)
|test01|{ "col1", "123", "col2" : &color(red){"XYZ"}; }|
#html(</div>)
ユーザA による更新( col1 = "ABC")が &br;失われてしまっている。
#html(</div>)
#html(<div class="ib va-top">)
#html(<span class="fa fa-user-o" > ユーザB</span>)
#html(<div style="padding-top:100px;">)
#html(<div class="ib pl10">)
#html(<span class="fa fa-long-arrow-left" style="font-size:20px;"></span>)
#html(</div>)
#html(<div class="ib pl10">)
|test01|{ "col1", "123";, "col2" : &color("red"){"XYZ"}; }|
#html(</div>)
#html(</div>)
#html(</div>)
#html(</div>)
そこで、条件付き更新を使用して更新を行う。
#html(</div>)
** DynamoDB の条件付き更新 [#f8a36a53]
#html(<div style="padding-left:20px;">)
#TODO(条件付き更新の説明)
#TODO(絵を貼る)
参考URL
http://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/Expressions.ConditionExpressions.html#Expressions.ConditionExpressions.SimpleComparisons
#html(</div>)
** 準備 [#m04cc99b]
#html(<div style="padding-left:20px;">)
テーブルの作成
#myterm2(){{
aws dynamodb create-table \
--profile developper \
--table-name Example1 \
--attribute-definitions \
AttributeName=id,AttributeType=S \
--key-schema AttributeName=id,KeyType=HASH \
--provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1
}}
#html(</div>)
** サンプルプログラム [#q1d01bcc]
#html(<div style="padding-left:20px;">)
#TODO(リトライ)
#mycode2(){{
var AWS = require('aws-sdk');
var documentClient = new AWS.DynamoDB.DocumentClient();
const tableName = "Example1";
const createResponse = (callback, statusCode, body) => {
var res = {
"statusCode": statusCode,
"body": JSON.stringify(body)
}
callback(null, res);
}
const getItem = (id, callback) => {
var params = {
TableName : tableName,
KeyConditionExpression: '#id = :id',
ExpressionAttributeNames:{ '#id': 'id'},
ExpressionAttributeValues:{':id': id }
};
documentClient.query(params, function(err, data) {
if (err) {
callback(err);
} else {
if (data.Items) {
data = data.Items[0] || data;
}
callback(null, data);
}
});
}
const setItemParams = (req, params, updated) => {
var colnames = [ "col1", "col2", "col3" ];
var delimiter = "";
var updateExpression = "";
for (var i in colnames) {
var colname = colnames[i];
if (req[colname]) {
if (params.Item) {
params.Item[colname] = req[colname];
} else {
var expressionAttributeNames = params.ExpressionAttributeNames || {};
expressionAttributeNames["#"+colname] = colname;
params.ExpressionAttributeNames = expressionAttributeNames;
var expressionAttributeValues = params.ExpressionAttributeValues || {};
expressionAttributeValues[":"+colname] = req[colname];
params.ExpressionAttributeValues = expressionAttributeValues;
updateExpression = updateExpression + delimiter + "#"+colname + " = " + ":" + colname;
delimiter = ", ";
}
}
}
if (updateExpression !== "") {
updateExpression = updateExpression + delimiter + "#updated" + " = " + ":updated";
params.UpdateExpression = "SET " + updateExpression;
params.ConditionExpression = " #updated = :old_updated ";
params.ExpressionAttributeNames["#updated"] = "updated";
// TODO: 確実ではない(ミリ秒単位で同じ時間に起動された場合)
// context.invokeid や context.awsRequestId などを利用する?
params.ExpressionAttributeValues[":updated"] = new Date().getTime().toString();
params.ExpressionAttributeValues[":old_updated"] = updated;
}
return params;
}
exports.handler = (event, context, callback) => {
let id = false;
if (event.pathParameters){
id = event.pathParameters.id || false;
}
var req = event.body;
if (typeof(req) === "string") {
req = JSON.parse(req);
}
switch(event.httpMethod){
case "GET":
if(id) {
getItem(id, function(err, data){
if (err) {
callback(err);
createResponse(callback, 500, { "msg": "Get Error!", "err": err, "params" : params });
} else {
createResponse(callback, 200, data);
}
});
return;
} else {
var params = {
TableName : tableName
};
documentClient.scan(params, function(err, data) {
if (err) {
console.log(err);
createResponse(callback, 500, { "msg": "List Error!", "err": err});
} else {
createResponse(callback, 200, data);
console.log(data);
}
});
}
break;
case "POST":
var params = { TableName : tableName , Item : { id : req.id } };
params = setItemParams(req, params);
documentClient.put(params, function(err, data) {
if (err) {
console.log(err);
createResponse(callback, 500, { "msg": "Create Error!", "err": err, "req": req});
} else {
createResponse(callback, 200, { "msg": "Create OK!"});
}
});
break;
case "PUT":
getItem(id, function(err, data){
if (err) {
createResponse(callback, 500, { "msg": "Update Error!", "err": err});
} else {
var sleepMs = req.sleep || 0;
setTimeout(function(){
var params = {
TableName : tableName,
Key: { "id" : id }
};
var updated = data.updated;
params = setItemParams(req, params, updated);
documentClient.update(params, function(err, data) {
if (err) {
console.log(err);
console.log(data);
createResponse(callback, 500, { "msg": "Update Error!", "err": err, "params" : params });
} else {
createResponse(callback, 200, { "msg": "Update OK!"});
}
});
}, sleepMs);
}
});
break;
case "DELETE":
var params = {
TableName : tableName,
Key: { "id" : parseInt(id, 10) }
};
documentClient.delete(params, function(err, data) {
if (err) {
console.log(err);
console.log(data);
createResponse(callback, 500, { "msg": "Delete Error!", "err": err});
} else {
createResponse(callback, 200, { "msg": "Delete OK!"});
}
});
break;
default:
console.log("Error: unsupported HTTP method (" + event.httpMethod + ")");
createResponse(callback, 501, { "msg": "Error: unsupported HTTP method (" + event.httpMethod + ")" } );
}
}
}}
#html(</div>)
** 動作確認 [#zfdad009]
*** データ登録 [#hce46623]
#myterm2(){{
curl -XPOST --data '{ "id" : "DATA01", "updated" : 1, "col1": { "sub1" : "AAA", "sub2" : "BBB" } }' https://エンドポイント
}}
*** 登録データを確認 [#bbded3c0]
#myterm2(){{
curl https://エンドポイント/DATA01
}}
*** 2つの処理を同時実行(1つは読み込み後3秒間sleep) [#zbea8d68]
#myterm2(){{
curl -XPUT --data '{ "id" : "DATA01", "col1": { "sub1" : "XXX" } , "sleep" : 3000 }' https://エンドポイント/DATA01 &
curl -XPUT --data '{ "id" : "DATA01", "col1": { "sub2" : "YYY" } }' https://エンドポイント/DATA01
}}
*** 登録データを確認 [#s07b0e00]
#myterm2(){{
curl https://エンドポイント/DATA01
}}