Amazon DynamoDB

2020年10月18日

はじめに

key-value 型データベース Amazon DynamoDB の使い方について。

テーブルの作成

  1. AWS マネジメントコンソールで DynamoDB を開く。
  2. [テーブルの作成] ボタンをクリック。
  3. "テーブル名" と "プライマリキー" を設定する。"ソートキーの追加" を有効にすると、プライマリキーをパーティションキーとソートキーの 2 つの属性のペアにできる。たとえば、テーブル名を "Music" として、プライマリキーを "Artist" と "SongTitle" など。テーブル設定はデフォルトでよい。[作成] ボタンをクリック。 ・テーブル編集画面になる。[項目] タブで [項目の作成] をクリックする。項目を編集する。プライマリキーの属性値は必須。十字のマークをクリックして、"Append" で属性を追加できる。
  4. 検索は "スキャン" と "クエリー" で行う。スキャンは全項目に対してフィルターを適用するもので、クエリーはキーの条件に合った項目を表示する。[スキャン] で "フィルターの追加" をクリックすると、条件に合ったテーブルの項目だけを表示できる。

検索は "スキャン" と "クエリー" で行う。スキャンは、全項目に対して属性についてのフィルターを適用するものである。クエリーは、プライマリキーの条件に合った項目を表示する。パーティションキーしかない場合のクエリーは特に面白くないので、クエリーは特にソートキーを持つ場合に意味があるのだろう。クエリーに対してフィルターを用いることもできる。

Lambda からのアクセス。 ロールに AmazonDynamoDBFullAccess をアタッチする。

例: チャットルームの接続管理用テーブル

例として、チャットルームの接続管理用のテーブルを考えてみる。名前は "ConnectionTable" とする。部屋 ID と接続 ID を結びつけたいので、プライマリキーのパーティションキーとして "roomId"、ソートキーとして "connectionId" を用意する。データは次のようになる。

roomIdconnectionId
a1
a2
b3
b4

AWS Lambda による DynamoDB テーブルへのアクセス方法

AWS Lambda から DynamoDB テーブルにアクセスする方法として、AWS SDK の DynamoDB を用いる方法と、DynamoDB.DocumentClient を用いる方法がある。

DynamoDB

const ddb = new AWS.DynamoDB({ apiVersion: "2012-10-08" });

DynamoDB.DocumentClient

const docClient = new AWS.DynamoDB.DocumentClient();

項目の追加

DocumentClient 版

const AWS = require("aws-sdk");
AWS.config.update({ region: process.env.AWS_REGION });
const docClient = new AWS.DynamoDB.DocumentClient();

exports.handler = (event) => {
    const params = {
        TableName: "ConnectionTable",
        Item: {
            roomId: "b",
            connectionId: "5"
        }
    };

    docClient.put(params, function(err) {
        if (err) {
            console.error("Error", err);
        } else {
            console.log("Sucess");
        }
    });

    const response = {
        statusCode: 200,
        body: JSON.stringify(""),
    };

    return response;
};

params でテーブル名、項目を指定して、put() で追加している。

DynamoDB 版

const AWS = require("aws-sdk");
AWS.config.update({ region: process.env.AWS_REGION });
const ddb = new AWS.DynamoDB({ apiVersion: "2012-10-08" });

exports.handler = (event) => {
    const params = {
        TableName: "ConnectionTable",
        Item: {
            roomId: { S: "c"},
            connectionId: { S: "6"}
        }
    };

    ddb.putItem(params, function (err) {
        if (err) {
            console.error("Error", err);
        } else {
            console.log("Success");
        }
      });

    const response = {
        statusCode: 200,
        body: JSON.stringify(""),
    };

    return response;
};

params でテーブル名、項目を指定して、putItem() で追加している。こちらの場合、属性の値を '{ S: "c" }' のように型と組み合わせて指定する必要がある。

項目の取得

DocumentClient 版

    const params = {
        TableName: "ConnectionTable",
        Key: {
            roomId: "a",
            connectionId: "1"
        }
    };

    docClient.get(params, function(err, data) {
        if (err) {
            console.error("Error", err);
        } else {
            console.log("Success", data);
        }
    });

出力

{ Item: { roomId: 'a', connectionId: '1' } }

DynamoDB 版

    const params = {
        TableName: "ConnectionTable",
        Key: {
            roomId: { S: "b" },
            connectionId: { S: "3" }
        }
    };

    ddb.getItem(params, function(err, data) {
        if (err) {
            console.error("Error", err);
        } else {
            console.log("Success", data);
        }
    });

出力

{ Item: { roomId: { S: 'b' }, connectionId: { S: '3' } } }

項目の更新

ここでは、部屋 ID と対応したメッセージを保存するテーブル MessageTable にメッセージを書き込むことを考える。

DocumentClient 版

    const params = {
        TableName: "MessageTable",
        Key: {
            roomId: "a"
        },
        UpdateExpression: "set message = :m",
        ExpressionAttributeValues:{
            ":m": "メッセージ"
        }
    };

    docClient.update(params, function(err) {
        if (err) {
            console.error("Error", err);
            callback(new Error("Error"));
        } else {
            console.log("Sucess");
        }
    });

DynamoDB 版

    const params = {
        TableName: "MessageTable",
        Key: {
            roomId: { S: "a" }
        },
        UpdateExpression: "set message = :m",
        ExpressionAttributeValues:{
            ":m": { S: "メッセージ" }
        }
    };
    
    ddb.updateItem(params, function(err) {
        if (err) {
            console.error("Error", err);
            callback(new Error("Error"));
        } else {
            console.log("Sucess");
        }
    });

項目の削除

DocumentClient 版

    const params = {
        TableName: "ConnectionTable",
        Key: {
            roomId: "b",
            connectionId: "5"
        }
    };

    docClient.delete(params, function(err) {
        if (err) {
            console.error("Error", err);
        } else {
            console.log("Sucess");
        }
    });

DynamoDB 版

    const params = {
        TableName: "ConnectionTable",
        Key: {
            roomId: { S: "c"},
            connectionId: { S: "6"}
        }
    };

    ddb.deleteItem(params, function (err) {
        if (err) {
            console.error("Error", err);
        } else {
            console.log("Success");
        }
    });

クエリー

パーティションキーで問い合わせする。属性に対してフィルター (FilterExpression) も使える。

DocumentClient 版

    const params = {
        TableName: "ConnectionTable",
        ProjectionExpression: "roomId, connectionId",
        KeyConditionExpression: "roomId = :rid",
        ExpressionAttributeValues: {
            ":rid": "a"
        }
    };

    docClient.query(params, function(err, data) {
        if (err) {
            console.error("Error", err);
        } else {
            console.log("Success");
            data.Items.map(({ roomId, connectionId }) => {
              console.log(roomId, connectionId);
            });
        }
    });

出力

a 1
a 2

DynamoDB 版

    const params = {
        TableName: "ConnectionTable",
        ProjectionExpression: "roomId, connectionId",
        KeyConditionExpression: "roomId = :rid",
        ExpressionAttributeValues: {
            ":rid": { S:"b" }
        }
    };

    ddb.query(params, function(err, data) {
        if (err) {
            console.error("Error", err);
        } else {
            console.log("Success");
            data.Items.map(({ roomId, connectionId }) => {
              console.log(roomId, connectionId);
            });
        }
    });

出力

{ S: 'b' } { S: '3' }
{ S: 'b' } { S: '4' }

ソートキーからパーティションキーを検索することはできない。そうしたい場合はスキャンを使う。

スキャン

全項目をスキャンする。フィルター (FilterExpression) が使える。

DocumentClient 版

    const params = {
        TableName: "ConnectionTable",
        ProjectionExpression: "roomId, connectionId",
        FilterExpression: "roomId = :rid",
        ExpressionAttributeValues: {
            ":rid": "a"
        }
    };

    docClient.scan(params, function(err, data) {
        if (err) {
            console.error("Error", err);
        } else {
            console.log("Success");
            data.Items.map(({ roomId, connectionId }) => {
              console.log(roomId, connectionId);
            });
        }
    });

出力

a 1
a 2

params は、次のような書き方もできる。

    const params = {
        TableName: "ConnectionTable",
        ProjectionExpression: "roomId, connectionId",
        FilterExpression: "#rid = :rid",
        ExpressionAttributeNames: {
            "#rid": "roomId"
        },
        ExpressionAttributeValues: {
            ":rid": "a"
        }
    };

DynamoDB 版

    const params = {
        TableName: "ConnectionTable",
        ProjectionExpression: "roomId, connectionId",
        FilterExpression: "roomId = :rid",
        ExpressionAttributeValues: {
            ":rid": { S: "b" }
        }
    };

    ddb.scan(params, function(err, data) {
        if (err) {
            console.error("Error", err);
        } else {
            console.log("Success");
            data.Items.map(({ roomId, connectionId }) => {
              console.log(roomId, connectionId);
            });
        }
    });

出力

{ S: 'b' } { S: '3' }
{ S: 'b' } { S: '4' }

ある文字列で始まるパーティションキーを探す

たとえば、部屋 ID が "xxx" で始まるものを探したいとすると、次のようにする。

    const params = {
        TableName: "ConnectionTable",
        ProjectionExpression: "roomId, connectionId",
        FilterExpression: "begins_with(roomId, :rid)",
        ExpressionAttributeValues: {
            ":rid": "xxx"
        }
    };

    docClient.scan(params, function(err, data) {
        if (err) {
            console.error("Error", err);
        } else {
            console.log("Success");
            data.Items.map(({ roomId, connectionId }) => {
              console.log(roomId, connectionId);
            });
        }
    });

非同期処理

非同期でクエリーを実行する場合は、非同期関数を用いる。

exports.handler = async (event) => {
    const params = {
        TableName: "ConnectionTable",
        ProjectionExpression: "roomId, connectionId",
        KeyConditionExpression: "roomId = :rid",
        ExpressionAttributeValues: {
            ":rid": "a"
        }
    };

    try {
        const data = await docClient.query(params).promise();

        console.log("Success");
        data.Items.map(({ roomId, connectionId }) => {
          console.log(roomId, connectionId);
        });
    } catch (e) {
        console.error("Error", e);
    }
    ...

クエリーに対する処理が終わるのを待ちたい場合は、次のようにする。

    let data;

    try {
        data = await docClient.query(params).promise();
        console.log("Success");
    } catch (e) {
        console.error("Error", e);
        callback(new Error("Error"));
    }

    let roomIds = {};

    const postCalls = data.Items.map(async ({ roomId, connectionId }) => {
        console.log(roomId, connectionId);
    });

    try {
        await Promise.all(postCalls);
    } catch (e) {
        console.log("Error", e);
        callback(new Error("Error"));
    }

参考