DynamoDBのTransactionを使った更新処理

DynamoDBのトランザクション関係のメソッドは取得系と更新系の2メソッドがあります。
今回は更新系のtransactWriteItemsメソッドについてお話していきます。
取得系のTransactGetItemsは別の機会にお話します。

 

データの用意

今回は新しくテーブルを用意します。

テーブル名「XpYamaguchi1」
インデックス「XpYamaguchi1No」(数値)

Lambda(node.js)を使用していきます。

 

PutでInsert

SQLでいう所のInsert、CRUDでいう所のCreateに当たる処理をします。
次のコードを実行するとどうなるか見てみましょう。

console.log('Loading function');
var AWS = require('aws-sdk');
var dynamoDb = new AWS.DynamoDB();

exports.handler = async (event, context) => {
  var data = await dynamoDb.transactWriteItems({
    TransactItems: [
      {
        Put: {
          TableName: 'XpYamaguchi1',
          Item: {
            XpYamaguchi1No: { N: '1' } ,
            XpYamaguchi1Name: {S:'name1'}
          }
        }
      }
    ]
  }).promise();
  return data;
};

テーブルに1行追加されました。

 

PutでUpdate

実はPutはUpdateをすることもできます。
次のコードを実行するとどうなるか見てみましょう。

console.log('Loading function');
var AWS = require('aws-sdk');
var dynamoDb = new AWS.DynamoDB();

exports.handler = async (event, context) => {
  var data = await dynamoDb.transactWriteItems({
    TransactItems: [
      {
        Put: {
          TableName: 'XpYamaguchi1',
          Item: {
            XpYamaguchi1No: { N: '1' } ,
            XpYamaguchi1Name: {S:'name1-1'}
          },
          ConditionExpression: "XpYamaguchi1Name = :XpYamaguchi1Name",
          ExpressionAttributeValues: {
            ":XpYamaguchi1Name" : {"S": "name1"}
          }
        }
      }
    ]
  }).promise();
  return data;
};

 

name1→name1-1へ変わったことが確認できます。

 

さらにもう1度同じLambdaを実行してみてください。

 

TransactionCanceledExceptionが発生しましたね。
ConditionExpressionで「XpYamaguchi1Name=name1のアイテムを更新する」と指定したのにすでに「name1-1」へ変わってしまっており、排他処理に引っかかったため更新が止まりました。

ちなみにPKに当たる項目(今回のはXpYamaguchi1No)はItem内の必須項目になりますので、複数アイテムを同時に更新することはできません。

 

UpdateでInsert

実はUpdateでSQLでいう所のInsert、CRUDでいう所のCreateに当たる処理をすることができます。
次のコードを実行するとどうなるか見てみましょう。

 

console.log('Loading function');
var AWS = require('aws-sdk');
var dynamoDb = new AWS.DynamoDB();

exports.handler = async (event, context) => {
  var data = await dynamoDb.transactWriteItems({
    TransactItems: [
      {
        Update: {
          TableName: 'XpYamaguchi1',
          Key: { XpYamaguchi1No: { N: '2' }},
          UpdateExpression: 'set XpYamaguchi1Name = :XpYamaguchi1Name_New',
          ExpressionAttributeValues: {
            ':XpYamaguchi1Name_New': { S: 'name2-1' }
          }
        }
      }
    ]
  }).promise();
  return data;
};

 

テーブルに1行追加されました。

 

 

UpdateでUpdate

 

Updateしてみます。
次のコードを実行するとどうなるか見てみましょう。

 

console.log('Loading function');
var AWS = require('aws-sdk');
var dynamoDb = new AWS.DynamoDB();

exports.handler = async (event, context) => {
var data = await dynamoDb.transactWriteItems({
  TransactItems: [
    {
      Update: {
        TableName: 'XpYamaguchi1',
        Key: { XpYamaguchi1No: { N: '2' }},
        ConditionExpression: 'XpYamaguchi1Name = :XpYamaguchi1Name_Old',
        UpdateExpression: 'set XpYamaguchi1Name = :XpYamaguchi1Name_New',
        ExpressionAttributeValues: {
          ':XpYamaguchi1Name_Old': { S: 'name2-1' },
          ':XpYamaguchi1Name_New': { S: 'name2-2' }
        }
      }
    }
  ]
  }).promise();
  return data;
};

 

name2-1→name2-2へ変わったことが確認できます。

 

 

さらにもう1度同じLambdaを実行してみてください。

 

 

TransactionCanceledExceptionが発生しましたね。
ConditionExpressionで「XpYamaguchi1Name=name2-1のアイテムを更新する」と指定したのにすでに「name2-2」へ変わってしまっており、排他処理に引っかかったため更新が止まりました。

 

Delete

Deleteをします。
次のコードを実行するとどうなるか見てみましょう。

 

console.log('Loading function');
var AWS = require('aws-sdk');
var dynamoDb = new AWS.DynamoDB();

exports.handler = async (event, context) => {
  var data = await dynamoDb.transactWriteItems({
    TransactItems: [
      {
        Delete: {
          TableName: 'XpYamaguchi1',
          Key: { XpYamaguchi1No: { N: '1' }},
          ConditionExpression: 'XpYamaguchi1Name = :XpYamaguchi1Name',
          ExpressionAttributeValues: {
            ':XpYamaguchi1Name': { S: 'name1-1' }
          }
        }
      }
    ]
  }).promise();
  return data;
};

 

テーブルからXpYamaguchi1No=1のアイテムが削除されました。

 

 

さらにもう1度同じLambdaを実行してみてください。
TransactionCanceledExceptionが発生しましたね。

 

 

注意点としては、今回は既に指定したKeyのアイテムが削除済みである点です。
ConditionExpressionの条件を満たしていないという訳ではなく、そもそもKeyが無いのですがConditionalCheckFailedでエラーになっています。

 

ConditionCheck

アイテムがあるかどうかのチェックを行います。
用途としてはトランザクション処理の最後に入れておき、
親テーブルが更新されていないかどうかチェックして、更新されていればロールバックさせる。
といった用途が多いと思います。

次のコードを実行するとどうなるか見てみましょう。

console.log('Loading function');
var AWS = require('aws-sdk');
var dynamoDb = new AWS.DynamoDB();

exports.handler = async (event, context) => {
  var data = await dynamoDb.transactWriteItems({
    TransactItems: [
      {
        ConditionCheck: {
        TableName: 'XpYamaguchi1',
        Key: { XpYamaguchi1No: { N: '2' }},
        ConditionExpression: 'XpYamaguchi1Name = :XpYamaguchi1Name',
        ExpressionAttributeValues: {
          ':XpYamaguchi1Name': { S: 'name2-2' }
        }
      }
    }
  ]
  }).promise();
  return data;
};

 

正常終了しました。

 

続いてアイテムをちょっと変えてから、もう1度Lambdaを実行してみましょう。

TransactionCanceledExceptionが発生しましたね。

 

最後に

今回は更新系のトランザクションメソッド「transactWriteItems」の使い方をお伝えしました。
DynamoDBでロールバックを狙うには、この機能を使うしか方法が無いため必然的に使用頻度は高くなると思います。
みなさんの参考になれば幸いです。