1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

SDKで操作しながらDynamoDBの基本を学ぶ

Last updated at Posted at 2023-11-11

自分用にスニペットを残しつつ、
SDKの各種コマンドとプロパティを網羅しながら理解を深めました。

実務で使ってみたい。

ここでは試さないこと

  • TransactGet/TransactWrite
  • TTL設定
  • DynamoDBStream連携
  • PartiQL

CreateTable

const command = new CreateTableCommand({
  TableName: TABLE_NAME,
  KeySchema: [
    { AttributeName: 'CustomerId', KeyType: 'HASH' }, // partition key
    { AttributeName: 'OrderNumber', KeyType: 'RANGE' }, // sort key
  ],
  AttributeDefinitions: [
    { AttributeName: 'CustomerId', AttributeType: 'S' }, // 属性:String
    { AttributeName: 'OrderNumber', AttributeType: 'N' }, // 属性:Number
  ],
  LocalSecondaryIndexes: [
    {
      IndexName: 'LSI_1',
      KeySchema: [
        { AttributeName: 'CustomerId', KeyType: 'HASH' },
        { AttributeName: 'Date', KeyType: 'RANGE' },
      ],
      Projection: {
        ProjectionType: 'INCLUDE', // || "ALL" || "KEYS_ONLY"
        NonKeyAttributes: ['Price'],
      },
    },
  ],
  GlobalSecondaryIndexes: [
    {
      IndexName: 'GSI_1',
      KeySchema: [
        { AttributeName: 'Product', KeyType: 'HASH' },
        { AttributeName: 'OrderNumber', KeyType: 'RANGE' },
      ],
      Projection: { ProjectionType: 'ALL' }, // || "KEYS_ONLY" || "INCLUDE"
      ProvisionedThroughput: {
        ReadCapacityUnits: 1,
        WriteCapacityUnits: 1,
      },
    },
  ],
  BillingMode: 'PAY_PER_REQUEST', // || "PROVISIONED"
  ProvisionedThroughput: {
    ReadCapacityUnits: 1,
    WriteCapacityUnits: 1,
  },
  StreamSpecification: {
    StreamEnabled: true, // || false
    StreamViewType: 'NEW_IMAGE', //  || "OLD_IMAGE" || "NEW_AND_OLD_IMAGES" || "KEYS_ONLY"
  },
  SSESpecification: {
    Enabled: true, // || false
    SSEType: 'KMS',
    KMSMasterKeyId: 'keyId',
  },
  TableClass: 'STANDARD', //  || "STANDARD_INFREQUENT_ACCESS"
  DeletionProtectionEnabled: true, //  || false
  Tags: [{ Key: 'id', Value: '1' }],
});

基本

  • 作成後はテーブル名変更不可
  • テーブル名以外の変更はUpdateTableCommandを使う

TableName(必須)

KeySchema(必須)

  • 「主キー(PartitionKey)」または「複合主キー(PartitionKey + SortKey)」 を指定
  • HASHキー = PartitionKey
  • RANGEキー = SortKey

AttributeDefinitions(必須)

  • PartitionKey および SortKey の型を指定
  • [ S(String) | N(Number) | B(binary) ] のいずれかを指定可能

LocalSecondaryIndexes

  • テーブル作成時のみ追加可能
  • SortKeyありのテーブルに対してのみ追加可能
  • テーブルにつき最大5個まで作成可能
  • PartitionKeyにはメインテーブルと同様の項目指定が必須
  • ProjectionType
    • KEYS_ONLY:複合主キーの情報のみをINDEXに投影する
    • INCLUDE:「複合主キー + NonKeyAttributesに指定した属性」をINDEXに投影する
    • ALL:すべての属性をINDEXに投影する
  • パーティションごとに 10 GB のサイズ制限がある
  • WCU/RCU はメインテーブルから消費される

LSIの「ローカル」が意味すること(参考:コンセプトから学ぶAmazon DynamoDB【LSI篇】

同じパーティション内に別の規則で整理INDEXを構築するため「ローカル」という理解になる模様。
関連して「PartitionKeyはメインテーブルと同じ」「10GBのサイズ制限」などの制約が存在する。

GlobalSecondaryIndexes

  • テーブル作成時以外でも追加可能
    • 追加時はCreateGlobalTableCommandを使う
  • GSIはメインテーブルとは異なる PartitionKey を指定して追加可能
  • 1テーブルにつき最大20個まで作成可能
  • ProjectionType の仕様はLSIと同様
  • GSIは 結果整合性 のみをサポート(= 強力な結果整合性 をサポートしないため ConsistentRead の指定は不可)
  • ProvisionedThroughput
    • テーブルがプロビジョンドモードの場合、独立したスループット設定が必要
    • テーブルがオンデマンドモードの場合、設定してもWCU/RCUの値は0になる模様(=設定省略できる)

BillingMode

ProvisionedThroughput

  • プロビジョニングモードにおいて RCU(ReadCapacityUnits) と WCU(WriteCapacityUnits) の値を設定する

  • RCUとWCUの仕様

    1RCU
    結果整合性
    1RCU
    強い結果整合性
    1WCU
    read最大サイズ 4KB 4KB -
    read実行/秒 2 1 -
    write最大サイズ - - 1KB
    write実行/秒 - - 1

StreamSpecification

  • DynamoDB Stream の有効化設定
  • DynamoDB Stream は DynamoDB に対する項目の追加・変更・削除を検出する
  • StreamViewType で Stream への書き込み内容を設定できる
    • KEYS_ONLY:変更されたアイテムのキー属性のみ
    • NEW_IMAGE:変更後に表示されるアイテム全体
    • OLD_IMAGE:変更前に表示されていたアイテム全体
    • NEW_AND_OLD_IMAGES:アイテムの新しいアイテムと古いアイテムの両方
  • 「追加のみをStreamに流す」などの制御はできないが、例えば Lambda ではイベントソースマッピングでフィルタリングができる模様

SSESpecification

  • ストレージ保存時のサーバーサイド暗号化設定
  • Enabled
    • false:デフォルト値。AWS所有キー が利用される
    • true:指定した AWS(カスタマー)管理キー が利用される
  • SSEType:KMSが唯一サポートされている値
  • KMSMasterKeyId:Enabled が true の場合に使用するキーをここで指定する

TableClass

  • STANDARD:標準クラス
  • STANDARD_INFREQUENT_ACCESS:低頻度アクセス
    • メリット:ストレージ料金が安くなる
    • デメリット:読み書き料金が高くなる
    • つまりは「大量データが登録されているがアクセス頻度が少ない」というケースに有用

DeletionProtectionEnabled

  • 削除保護の有効化設定(CFnとかで事故ること多そうなので基本はON?)
  • DynamoDB Local だとONにしてても削除できてしまう:cry:

Tags

  • タグの設定

DeleteTable

const command = new DeleteTableCommand({
  TableName: TABLE_NAME,
});

基本

  • テーブル削除

TableName(必須)

ListTables

const command = new ListTablesCommand({
  ExclusiveStartTableName: 'OrderHistory',
  Limit: 10,
});

基本

  • テーブル一覧を取得

ExclusiveStartTableName

  • 指定したテーブル名の次結果から返却するよう指定できる

Limit

  • 返却されるテーブル名の最大数
  • デフォルトは100

(まず使うことはないであろう) ExclusiveStartTableName 指定時の例

  • 「OrderHistory・OrderHistory2・OrderHistory3」が存在
  • ListTablesのLimitは1
  • 全件取得が完了するまで処理を繰り返したい場合のコーディング例
    let exclusiveStart = undefined;
    // LastEvaluatedTableName が空になるまで続ける
    while (true) {
      try {
        const command = new ListTablesCommand({
          ExclusiveStartTableName: exclusiveStart,
          Limit: 1,
        } as ListTablesCommandInput);
        const output: ListTablesCommandOutput = await ddbClient.send(command);
        console.log('===================== listTables success =====================\n', JSON.stringify(output, null, 2));
    
        exclusiveStart = output.LastEvaluatedTableName || undefined;
        if (!exclusiveStart) break;
      } catch (err) {
        console.error('===================== listTables error =====================\n', err);
        break;
      }
    }
    
  • 出力結果
    ~/workspace/dynamodb-test (main) % npm run test
    
    > dynamodb-test@1.0.0 test
    > ./node_modules/.bin/ts-node ./src/dynamodb.ts
    
    ===================== listTables success =====================
     {
      "$metadata": {
        ...
      },
      "TableNames": [
        "OrderHistory"
      ],
      "LastEvaluatedTableName": "OrderHistory"
    }
    ===================== listTables success =====================
     {
      "$metadata": {
        ...
      },
      "TableNames": [
        "OrderHistory2"
      ],
      "LastEvaluatedTableName": "OrderHistory2"
    }
    ===================== listTables success =====================
     {
      "$metadata": {
        ...
      },
      "TableNames": [
        "OrderHistory3"
      ]
    }
    

DescribeTable

const command = new DescribeTableCommand({
  TableName: TABLE_NAME,
});

基本

  • テーブル定義詳細情報を取得

TableName(必須)

CRUD - 共通OPTION

Put Update Delete Get Query Batch
Get
Batch
Write
ConditionExpression - - - -
ExpressionAttributeNames -
ExpressionAttributeValues - - -
ReturnValues - - - -
ReturnValuesOn
ConditionCheckFailure
- - - -
ReturnItem
CollectionMetrics
- - -
ReturnConsumedCapacity
ConsistentRead - - - -
ProjectionExpression - - - -

ConditionExpression

  • [ Put | Update | Delete ] アクション実行時の条件式を設定できる(※ BatchWrite は不可)

  • Putでの上書き防止・Updateでの非存在項目の追加防止が可能となる

  • 条件式には 比較演算子・論理式・関数 が使用可能

    • 比較演算子:= | <> | < | > | <= | >= | BETWEEN | IN
    • 論理式:AND | OR | NOT
    • 関数:以下の機能が提供されている
    関数 概要
    attribute_exists(path) 項目にpathで指定した属性が含まれる場合 true
    attribute_not_exists(path) pathで指定した属性が項目に存在しない場合true
    attribute_type(path, type) pathが特定のデータ型である場合true
    typeの値には式の属性値を使用する必要あり
    contains(path, operand) pathに「String, Set, List」のいずれかの属性値をもつ属性を指定
    operandにはpathに指定した属性に対応する式の属性値を指定
    begins_with(path, substr) pathで指定された属性がsubstrに指定した値から始まる場合true
    size(path) pathで指定した属性のサイズを表す数値を返す

ConditionExpression の正しい理解

初心者は挙動に混乱すると思われます。(...自分だけ?)
以下の記事がわかりやすいです。
参考:Understanding DynamoDB Condition Expressions

条件式の評価は次の手順で行われるようです。

  1. 最大1つの項目にマッチする同じ(複合)主キーを持つ既存項目を見つける
  2. マッチした項目があれば、それに対してConditionExpressionを評価(既存項目がない場合はnullに対して評価)
  3. trueと評価された場合、書き込みを実行

image.png

これは以下のように解釈でき、挙動もその通りになります。

ExistingItem: { PK: 1, SK: 2 }
PutItem:      { PK: 1, SK: 3 }

ConditionExpression: attribute_not_exists(PK)

=> success

直感的にはPKが重複しているのでエラーになりそうですが、成功したので???でした。
理由は手順1で「put予定のItemと同様の複合主キーを持つ既存項目」を取得するためです。
例の場合 { PK: 1, SK: 3 } の組み合わせは既存テーブルに存在しません。
attribute_not_exists() は null に対して評価することになり true になるという仕組みです。

つまり、複合主キーを持つテーブルに対して
attribute_not_exists(PK) AND attribute_not_exists(SK)
という条件式は不要、ということになります。
PK・SK・PK+SKを指定したいずれの条件式も結果は同じになります。

ExpressionAttributeNames

  • 式内で使用する属性名の置換トークン
  • 必須ではないが以下ユースケースの場合使用が推奨されているので基本は使うでいいはず
    • DynamoDB の予約語と名前が競合する属性(例:Percentile)
    • 式内で属性名が繰り返し出現するためのプレースホルダーを作成
    • 属性名の特殊文字が式内で誤って解釈されるのを防ぐ
  • #を使用して属性名を逆参照する(例:{ '#P': 'Price' }

ExpressionAttributeValues

  • 式内で置換できる値
  • :を使用して属性値を逆参照する(例:{ ':P': 100 }

ReturnValues

  • アクションにより指定可能な値が異なる
    説明 Update Put Delete
    NONE デフォルト。値を何も返さない
    ALL_OLD 更新前の全属性値を返却
    ALL_NEW 更新後の全属性値を返却 - -
    UPDATED_OLD 更新前の更新対象属性値のみを返却 - -
    UPDATED_NEW 更新後の更新対象属性値のみを返却 - -

ReturnValuesOnConditionCheckFailure

  • ConditionExpression で false となった場合のレスポンス設定を調整する
    • NONE:デフォルト値。何も返さない
    • ALL_OLD:テーブルにある該当 Item を返却する
      • 該当 Item 特定のために getItem でRCUを消費する必要がなくなる
      • DynamoDB Local だと response の Item が undefined になってしまう:cry:
      • ALL_OLD 指定時の返却内容
        ConditionalCheckFailedException: The conditional request failed
            ...
        {
          '$fault': 'client',
          '$metadata': {
            httpStatusCode: 400,
            ...
          },
          Item: {
            Product: { S: 'apple' },
            Price: { N: '100' },
            Date: { S: '2023-11-01 00:00:01' },
            OrderNumber: { N: '1' },
            CustomerId: { S: 'A001' }
          }, // エラーの起因となったItem情報を返却してくれる
          __type: 'com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException'
        }
        

ReturnConsumedCapacity

  • 消費されたRCU/WCU情報に関する詳細レベルを設定する
    • NONE:デフォルト値。集計に何も含めない
    • TOTAL:操作の集計のみが含まれる
    • INDEXES:アクセスされた各テーブル・セカンダリインデックスとともに、操作の集計が含まれる
    • TOTAL と INDEXES の出力差分
      ===================== TOTAL =====================
        "ConsumedCapacity": {
          "CapacityUnits": 1,
          "TableName": "OrderHistory"
        }
      ===================== INDEXES =====================
        "ConsumedCapacity": {
          "CapacityUnits": 3,
          "GlobalSecondaryIndexes": {
            "GSI_1": {
              "CapacityUnits": 1
            }
          },
          "LocalSecondaryIndexes": {
            "LSI_1": {
              "CapacityUnits": 1
            }
          },
          "Table": {
            "CapacityUnits": 1
          },
          "TableName": "OrderHistory"
        }
      

ReturnItemCollectionMetrics

  • NONE:デフォルト値。値を何も返さない
  • SIZE:影響を受けた ItemCollection に関する以下の情報を返却する
    • ItemCollectionKey:PartitionKey の情報
    • SizeEstimateRangeGB:
      • ItemCollectionSize の下限/上限を含むGB単位の推定値(LSIに投影される属性サイズ含む)
      • LSI の10GB制限に近づいているか測定可能
      • 算出される数値はあくまで見積りで高い信頼性はない模様
    • DynamoDB Local だと設定が反映されない:cry:
    • SIZE 指定時の出力例
        "ItemCollectionMetrics": {
          "ItemCollectionKey": {
            "CustomerId": "A001"
          },
          "SizeEstimateRangeGB": [
            0,
            1
          ]
        }
      

ConsistentRead

  • false:デフォルト値。結果整合性での読み取りとなる
  • true強い結果整合性での読み取りとなる

ProjectionExpression

  • RDB の SELECT句のようなもの
    • 指定しない場合:Itemの全属性が返却される(SELECT * FROM)
    • 指定する場合:
      • 指定したItemの属性が返却される(SELECT a, b FROM)
      • 複数項目指定する場合は1つの文字列内でカンマで区切る
  • 指定あり/なしの出力差分(指定あり:CustomerId, Product を指定)
    ===================== Option =====================
    
    ProjectionExpression: 'CustomerId, Product'
    
    ===================== Option指定なし =====================
    {
      "$metadata": {
        ...
      },
      "Item": {
        "Product": "apple",
        "OrderNumber": 1,
        "CustomerId": "A001",
        "Price": 100,
        "Timestamp": "2023-11-01 00:00:01"
      }
    }
    ===================== Option指定あり =====================
    {
      "$metadata": {
        ...
      },
      "Item": {
        "Product": "apple",
        "CustomerId": "A001"
      }
    }
    

Put

// use DynamoDBDocumentClient
const command = new PutCommand({
  TableName: TABLE_NAME,
  Item: {
    CustomerId: 'A001',
    OrderNumber: 2,
    Product: 'pine',
    Price: 100,
    Date: '2023-11-01 00:00:01',
  },
  ConditionExpression: attribute_not_exists(CustomerId)
});

基本

  • Itemを1件追加・更新
    • 追加:主キー(複合主キー)が重複しない場合
    • 更新:主キー(複合主キー)が重複する場合
      • 更新を回避するにはConditionExpressionで条件式を指定する

TableName(必須)

Item(必須)

  • Item内は(複合)主キーが必須項目

Update

// use DynamoDBDocumentClient
const command = new UpdateCommand({
  TableName: TABLE_NAME,
  Key: {
    CustomerId: 'A001',
    OrderNumber: 1,
  },
  ConditionExpression: attribute_exists(CustomerId),
  ExpressionAttributeNames: { '#P': 'Product' },
  ExpressionAttributeValues: { ':P': 'pine' },
  UpdateExpression: 'set #P = :P'
});

基本

  • Itemを1件更新・追加
    • 更新:Keyと一致するItemがある場合
    • 追加:Keyと一致するItemがない場合
      • 追加を回避するにはConditionExpressionで条件式を指定する

TableName(必須)

Key(必須)

  • Item内は(複合)主キーが必須項目

UpdateExpression

参考:DynamoDBでデータを更新する際に使うUpdateExpressionについて一通りまとめてみた

  • Update実行時のアクションを定義する
    • SET:1つ以上の項目を更新・追加する

      • 更新:指定した属性が既に存在する場合
      • 追加:指定した属性が存在しない場合
      • 次の組み込み関数をサポートしている
        • if_not_exists(path, operand):属性の上書きを回避できる
        • list_append(operand, operand):リストの結合ができる
      • 数値の加減算が可能(ただし加減算はADDで行うことが推奨されている)
      • [ SET - UseCase1 ]:数値の加算と要素の追加
        ExpressionAttributeNames: {
          '#P': 'Price',
          '#N': 'Note',
        },
        ExpressionAttributeValues: {
          ':P': 10,
          ':N': 'Price変更',
        },
        UpdateExpression: 'set #P = #P + :P, #N = :N',
        
        ===================== before =====================
        
          {
            "Product": "apple",
            "OrderNumber": 1,
            "CustomerId": "A001",
            "Price": 100,
            "Timestamp": "2023-11-01 00:00:01"
          }
        
        ===================== after =====================
        
          {
            "Product": "apple",
            "OrderNumber": 1,
            "CustomerId": "A001",
            "Price": 110, // 元の数値に加算される
            "Timestamp": "2023-11-01 00:00:01",
            "Note": "Price変更" // 属性が追加される
          }
        

      • [ SET - UseCase2 ]:if_not_exists() で上書き防止
        ExpressionAttributeNames: {
          '#P': 'Price',
          '#N': 'Note',
        },
        ExpressionAttributeValues: {
          ':P': 200,
          ':N': '属性追加',
        },
        UpdateExpression: 'set #P = if_not_exists(#P, :P), #N = if_not_exists(#N, :N)',
        
        ===================== before =====================
        
          {
            "Product": "apple",
            "OrderNumber": 1,
            "CustomerId": "A001",
            "Price": 100,
            "Timestamp": "2023-11-01 00:00:01"
          }
        
        ===================== after =====================
        
          {
            "Product": "apple",
            "OrderNumber": 1,
            "CustomerId": "A001",
            "Price": 100, // Price属性は既に存在していたため上書き防止される
            "Timestamp": "2023-11-01 00:00:01",
            "Note": "属性追加"
          }
        

      • [ SET - UseCase3 ]:list_append() でリストを結合
        ExpressionAttributeNames: { '#P': 'PriceList' },
        ExpressionAttributeValues: { ':P': [300, 400] },
        UpdateExpression: 'set #P = list_append(#P, :P)',
        
        ===================== before =====================
        
          {
            "PriceList": [
              100,
              200
            ],
            "OrderNumber": 1,
            "CustomerId": "A001"
          }
        
        ===================== after =====================
        
          {
            "PriceList": [
              100,
              200,
              300,
              400
            ], // 元リストと結合される
            "OrderNumber": 1,
            "CustomerId": "A001"
          }
        

      • [ SET - UseCase4 ]:マップ要素とリスト要素にアクセスする
        ExpressionAttributeNames: {
          '#PM': 'ProductMap',
          '#F1': 'fruits1',
          '#PMF1': 'ProductMap.fruits1',  // (※1)マップ要素に対しこのアクセスの仕方はできない
          '#PL': 'PriceList',
          '#PL0': 'PriceList[0]',  // (※2)リスト要素に対しこのアクセスの仕方はできない
        },
        ExpressionAttributeValues: {
          ':PMF1': 'grape',
          ':PL0': 300,
        },
        UpdateExpression: 'set #PM.#F1 = :PMF1, #PMF1 = :PMF1, #PL[0] = :PL0, #PL0 = :PL0',
        
        ===================== before =====================
        
          {
            "OrderNumber": 1,
            "CustomerId": "A001",
            "ProductMap": {
              "fruits1": "apple",
              "fruits2": "pine"
            },
            "PriceList": [
              100,
              200
            ]
          }
        
        ===================== after =====================
        
          "Attributes": {
            "PriceList[0]": 300, // (※2)新しい属性として追加されてしまう
            "OrderNumber": 1,
            "ProductMap": {
              "fruits1": "grape", // '#PM.#F1'でマップ要素にアクセスできていることがわかる
              "fruits2": "pine"
            },
            "ProductMap.fruits1": "grape",  // (※1)新しい属性として追加されてしまう
            "CustomerId": "A001",
            "PriceList": [
              300, // '#PL[0]'でリスト要素にアクセスできていることがわかる
              200
            ]
          }
        

    • REMOVE:アイテムから1つ以上の属性を削除する

      • マップやリスト内の要素単位で削除も可能
      • [ REMOVE - UseCase1 ]:属性およびリスト内の要素削除
        ExpressionAttributeNames: {
          '#N': 'Note',
          '#PM': 'ProductMap',
          '#F1': 'fruits1',
          '#PL': 'PriceList',
        },
        UpdateExpression: 'remove #N, #PM.#F1, #PL[0]',
        
        ===================== before =====================
        
          {
            "OrderNumber": 1,
            "CustomerId": "A001",
            "ProductMap": {
              "fruits1": "削除対象",
              "fruits2": "pine"
            },
            "PriceList": [
              100,
              200
            ],
            "Note": "削除対象"
          }
        
        ===================== after =====================
        
         {
            "OrderNumber": 1,
            "ProductMap": {
              "fruits2": "pine"
            },
            "CustomerId": "A001",
            "PriceList": [
              200
            ]
          }
        

    • ADD:属性を追加する

      • 数値型(N)かセット型(NS/SS/BS)へのアクションのみ許可

        • 指定した属性が存在しない場合:指定された属性と共に値を追加
        • 指定した属性が存在する場合:
          • 数値型(N):加減算ができる
          • セット型(NS/SS/BS):要素の追加ができる
      • [ ADD - UseCase1 ]:数値型の加算(属性追加)・減算

        ExpressionAttributeNames: {
          '#P': 'Price',
          '#P2': 'Price2',
        },
        ExpressionAttributeValues: {
          ':P': -100,
          ':P2': 200,
        },
        UpdateExpression: 'add #P :P, #P2 :P2',
        
        ===================== before =====================
        
          {
            "Price": 100,
            "OrderNumber": 1,
            "CustomerId": "A001"
          }
        
        ===================== after =====================
        
          {
            "Price2": 200,
            "OrderNumber": 1,
            "CustomerId": "A001",
            "Price": 0
          }
        

      • [ ADD - UseCase2 ]:セット型の追加

        ExpressionAttributeNames: { '#P': 'PriceSet' },
        ExpressionAttributeValues: { ':P': new Set([300]) },
        UpdateExpression: 'add #P :P',
        
        ===================== before =====================
        
          { OrderNumber: 1, PriceSet: Set(2) { 100, 200 }, CustomerId: 'A001' }
        
        ===================== after =====================
        
          {
            OrderNumber: 1,
            PriceSet: Set(3) { 100, 200, 300 },
            CustomerId: 'A001'
          }
        

    • DELETE:指定した値をデータから削除する

      • セット型のデータのみサポート
      • DELETEアクションにより要素が空になると属性ごと消える(セット型は空の状態が許容されないため)
      • [ DELETE - UseCase1 ]:セット型の要素削除
        ExpressionAttributeNames: {
          '#P': 'PriceSet',
          '#P2': 'PriceSet2',
        },
        ExpressionAttributeValues: {
          ':P': new Set([200]),
          ':P2': new Set([400]),
        },
        UpdateExpression: 'delete #P :P, #P2 :P2',
        
        ===================== before =====================
        
          {
            OrderNumber: 1,
            PriceSet: Set(2) { 100, 200 },
            CustomerId: 'A001',
            PriceSet2: Set(1) { 400 }
          }
        
        ===================== after =====================
        
          { OrderNumber: 1, PriceSet: Set(1) { 100 }, CustomerId: 'A001' }
        

    セット型について

    上記ユースケースにある通り、例えばjavascriptではnew Set([...])で明示的にSet型を作成する必要があります。
    (純粋なDynamoDBClientであればNS等でセット型を指定できそうですが)
    また、セット型をJSON.Stringfy()すると{}で出力されてしまい、リストやオブジェクトとは異なる挙動をします。
    ・型縛り
    ・重複排除
    などがセット型の特性です。(この強力な制約がどうしても必要、というケース以外では混乱を招く可能性高し...?)

Delete

// use DynamoDBDocumentClient
const command = new DeleteCommand({
  TableName: TABLE_NAME,
  Key: {
    CustomerId: 'A001',
    OrderNumber: 1,
  }
});

基本

  • Itemを1件削除

TableName(必須)

Key(必須)

  • Item内は(複合)主キーが必須項目

Get

// use DynamoDBDocumentClient
const command = new GetCommand({
  TableName: TABLE_NAME,
  Key: {
    CustomerId: 'A001',
    OrderNumber: 1,
  },
});

基本

  • Itemを1件取得

TableName(必須)

Key(必須)

  • (複合)主キーの指定が必須
    • 一意の Item を取得するコマンドなので、例えば複合主キーのテーブルに対しPartitionKeyのみ指定は不可

Query

// use DynamoDBDocumentClient
const command = new QueryCommand({
  TableName: TABLE_NAME,
  // ExclusiveStartKey: {
  //   CustomerId: 'A001',
  //   OrderNumber: 1,
  // },
  ExpressionAttributeNames: {
    '#C': 'CustomerId',
    '#P': 'Price',
  },
  ExpressionAttributeValues: {
    ':C': 'A001',
    ':P': 100,
  },
  FilterExpression: '#P > :P',
  // IndexName: 'GSI_1',
  KeyConditionExpression: '#C = :C',
  ProjectionExpression: 'CustomerId, OrderNumber',
  Limit: 3,
  ScanIndexForward: true, // || false
  // Select: 'ALL_ATTRIBUTES', // || ALL_PROJECTED_ATTRIBUTES || COUNT || SPECIFIC_ATTRIBUTES
});

基本

  • RDB の SELECT に近い
  • PKは必ず指定する必要があるため、select * from table_name;のような全件取得はできない。
  • 全件取得はScanを使う
  • SQL互換のクエリ言語を使いたければPartiQLという機能もサポートされている

TableName(必須)

KeyConditionExpression(必須)

  • PartitionKeyの指定が必須
  • 式内で使用する値は ExpressionAttributeValues を用いた置換が必要
  • SortKey も存在する場合はAND条件で絞り込みが可能
    • 比較演算子<>は使用不可
    • BETWEEN :val1 AND :val2begins_with( sortKeyName, :val) を使用可能

ExclusiveStartKey

  • 指定した(複合)主キーの次のItemから取得を開始する
  • LastEvaluatedKeyと組み合わせて使う

FilterExpression

  • (複合)主キー以外の絞り込みが必要な場合に使用する
  • 使用できる構文はConditionExpressionと同じ
  • [ FilterExpression - UseCase1 ]:特定の属性を持つItemをFilter
    ===================== data =====================
    
    { CustomerId: 'A001', OrderNumber: 1, Price: 100 }
    { CustomerId: 'A001', OrderNumber: 2 }
    { CustomerId: 'A001', OrderNumber: 3 }
    
    ExpressionAttributeNames: { '#C': 'CustomerId' },
    ExpressionAttributeValues: { ':C': 'A001' },
    KeyConditionExpression: '#C = :C',
    ProjectionExpression: 'CustomerId, OrderNumber',
    
    FilterExpression: 'attribute_exists(Price)' // 'Price'属性を持つItemのみFilterする
    
    ===================== FilterExpressionなし =====================
    
      [
        {
          "OrderNumber": 1,
          "CustomerId": "A001"
        },
        {
          "OrderNumber": 2,
          "CustomerId": "A001"
        },
        {
          "OrderNumber": 3,
          "CustomerId": "A001"
        }
      ]
    
    ===================== FilterExpressionあり =====================
    
      [
        {
          "OrderNumber": 1,
          "CustomerId": "A001"
        }
      ]
    

IndexName

  • 使用するGSI/LSIのINDEX名を指定
    • DynamoDB では RDB の EXPLAIN のようなことはしてくれず、明示的に指定の必要がある模様
    • RDB だと FORCE INDEX句
  • GSI指定の場合、KeyConditionExpression に指定するPKはもちろんGSI対応のもの
  • デフォルトではテーブルINDEXが使用される

Limit

  • 取得する最大数を指定
  • RDB だと Limit句
  • Limit上限に到達した場合、LastEvaluatedKeyが返却される
    • ExclusiveStartKeyと組み合わせて途中から読み込みを再開できる

ScanIndexForward

  • SortKeyのソート順を決める
    • true:昇順。デフォルト値
    • false:降順
  • RDB だと ORDER BY句

Select

  • ProjectionExpression と同様、RDB の SELECT句
  • 以下のように抽出対象の抽象的な指定ができる
    • ALL_ATTRIBUTES:すべての属性を取得
    • ALL_PROJECTED_ATTRIBUTES:INDEXに投影されたすべての属性を取得
    • COUNT:一致するアイテムの数を返す。RDB の select COUNT(a) FROM
    • SPECIFIC_ATTRIBUTES:ProjectionExpression 指定属性のみ返却
  • SPECIFIC_ATTRIBUTES以外 ProjectionExpression との併用不可
  • ProjectionExpression があればCOUNT以外このオプション要らないのでは...?
  • ただ、このオプションを使うことでスループットコストの最適化ができそうなこともあったので、とりあえず頭には入れておく

Scan

// use DynamoDBDocumentClient
const command = new ScanCommand({
  TableName: TABLE_NAME,
});

基本

  • テーブルデータを全件取得

TableName(必須)

BatchGet

// use DynamoDBDocumentClient
const command = new BatchGetCommand({
  RequestItems: {
    [TABLE_NAME]: {
      Keys: [
        { CustomerId: 'A001', OrderNumber: 1 },
        { CustomerId: 'A001', OrderNumber: 2 },
      ],
      ProjectionExpression: 'CustomerId, Product',
    },
    [TABLE_NAME_2]: {
      Keys: [
        { CustomerId: 'A001', OrderNumber: 1 },
        { CustomerId: 'A001', OrderNumber: 2 },
      ],
      ProjectionExpression: 'CustomerId, Product',
    },
  },
  ReturnConsumedCapacity: 'NONE'
});

基本

  • 1つ以上のテーブルから 1つ以上の項目を取得できる
  • 1回のオペレーションで取得できるデータにはいくつか制約が存在する
    • 合計で最大16MBのデータまで
    • Itemは最大で100個まで

RequestItems(必須)

  • 「1つ以上のテーブル」から「1つ以上の項目を取得」するためのマップ
  • テーブル名単位で要素を指定する
    • Keysに Item の(複合)主キーを指定する
    • その他オプションは getItem と同様

BatchWrite

// use DynamoDBDocumentClient
const command = new BatchWriteCommand({
  RequestItems: {
    [TABLE_NAME]: [
      {
        PutRequest: {
          Item: { CustomerId: 'A001', OrderNumber: 1 },
        },
      },
      {
        DeleteRequest: {
          Key: { CustomerId: 'A001', OrderNumber: 2 },
        },
      },
    ],
    [TABLE_NAME_2]: [
      {
        PutRequest: {
          Item: { CustomerId: 'A001', OrderNumber: 1 },
        },
      },
      {
        DeleteRequest: {
          Key: { CustomerId: 'A001', OrderNumber: 2 },
        },
      },
    ],
  },
  ReturnConsumedCapacity: 'INDEXES',
  ReturnItemCollectionMetrics: 'SIZE',
});

基本

  • 1つ以上のテーブルから 1つ以上の項目を追加・削除できる
  • 1回のオペレーションで書き込みできるデータにはいくつか制約が存在する
    • 合計で最大16MBのデータまで
    • Itemは最大で25個まで
  • 追加・削除をまとめて実行することができる

RequestItems(必須)

  • 「1つ以上のテーブル」から「1つ以上の項目を追加・削除」するためのマップ
  • テーブル名単位で要素を指定する
    • PutRequest
      • Put オペレーションを実行する場合に指定
      • PutRequest 内は Put オペレーション時と同様Itemに(複合)主キーを指定する
      • ConditionExpression が指定できないことから上書き操作を回避できないことに注意
    • DeleteRequest
      • Delete オペレーションを実行する場合に指定
      • DeleteRequest 内は Delete オペレーション時と同様Keyに(複合)主キーを指定する

[ BatchGet - UnprocessedKeys ] / [ BatchWrite - UnprocessedItems ]について

BatchGet/BatchWrite ではスループットやレスポンスサイズの超過で一部の処理しか成功しなかった場合、成功した処理はロールバックされないようです。
上記ケースの場合、レスポンスの UnprocessedKeys または UnprocessedItems 内に未処理の Key/Item が格納されてきます。

全ての処理を正常に完了させたい場合、1回のオペレーションで処理できる上限と合わせて以下のような考慮をした実装が必要になってきそうです。
(複数テーブル取得を考慮するともうちょっと考えないといけない)

例:BatchGet で UnprocessedKeys を考慮する(参考:AWS SDK for JavaScript: DynamoDB batchGet/batchWriteのサンプル

スニペット

// 125個のItemをBatchGetする
const sampleKeys = [] as any;
for (let i = 1; i <= 125; i++) {
  const key = { CustomerId: 'A001', OrderNumber: i };
  sampleKeys.push(key);
}

/***************************************
  batch get item
***************************************/
const batchGetItem = async () => {
  try {
    const result = [];
    const limit = 100; // 1回のbatchGet操作におけるItemの取得上限
    let unprocessedKeys = sampleKeys.slice();

    while (unprocessedKeys.length > 0) {
      const requestKeys = unprocessedKeys.splice(0, limit); // 取得上限までrequestKeysに移植
      const command = new BatchGetCommand({
        RequestItems: {
          [TABLE_NAME]: {
            Keys: requestKeys,
          },
        },
      } as BatchGetCommandInput);

      const output: BatchGetCommandOutput = await ddbDocClient.send(command);
      const res = output?.Responses?.[TABLE_NAME];
      const unprocessed = output?.UnprocessedKeys?.[TABLE_NAME]?.Keys;

      if (res) result.push(...res); // 取得完了分をresultへpush
      if (unprocessed) unprocessedKeys.push(...unprocessed); // 未処理分をunprocessedKeysへpush
    } // unprocessedKeysが0になるまで繰り返し

    console.log('===================== batchGetItem success =====================\n', JSON.stringify(result, null, 2), '\ncount: ', result.length);
    return result;
  } catch (err) {
    console.error('===================== batchGetItem error =====================\n', err);
  }
};

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?