Amazon DynamoDBの使い方の心得

公開日 2025年1月17日
更新日 2025年1月30日
28 分で読める
AWS

Amazon DynamoDB は、サーバーレスの NoSQL データベースであり、あらゆる規模のアプリケーションに対して高いパフォーマンスとスケーラビリティを提供します。本記事では、DynamoDB のコアコンセプトについて詳しく説明します。これには、主キー設計、データ型、テーブル定義、CRUD 操作、トランザクション処理、グローバルセカンダリインデックス (GSI)、およびテーブル設計のベストプラクティスが含まれます。


1. 主キー

主キーの構造

DynamoDB では、すべてのテーブルに主キー(Primary Key)を定義する必要があります。主キーには以下の 2 種類があります:

  1. パーティションキー(Partition Key, PK):1 つの属性で構成される単一キー。データがどのパーティションに格納されるかを決定します。
  2. パーティションキー+ソートキー(Sort Key, SK):2 つの属性で構成される複合キー。パーティションキーでパーティションを決定し、ソートキーでそのパーティション内の並び順を決定します。

いずれの場合も主キーの 一意性 を保証する必要があります。

例:

{
  "PK": "USER#12345",
  "SK": "USER_INFO"
}

主キー設計の推奨

パーティションキー + ソートキー の形式を推奨します。例えば、ユーザーデータを保存する場合、PK を USER#<user_id>、SK を USER_INFO に設定します。

  1. PK の最初の部分はデータの種類を表す定数(例:USER)で、2 番目の部分は ユーザーID です。
  2. SK はデータの種類や内容を示す定数や動的値を使用します。

この設計により一意性を保証し、効率的なクエリが可能になります。


2. データ型

DynamoDB は以下のデータ型をサポートしています:

  • スカラー型:String(S)、Number(N)、Binary(B)、Boolean(BOOL)
  • ドキュメント型:Map(M)、List(L)
  • セット型:String Set(SS)、Number Set(NS)、Binary Set(BS)

括弧内は、DynamoDB テーブル定義時に使用する型表記です。

注意事項:

  • 柔軟性:Map や List などのドキュメント型はネストされたデータの保存に最適です。
  • インデックスサポート:スカラー型のみがパーティションキーやソートキーとして使用できます。

例:

{
  "PK": "USER#12345",
  "SK": "USER_INFO",
  "Name": "Alice",
  "Age": 30,
  "Preferences": {
    "Language": "English",
    "TimeZone": "UTC+9"
  },
  "Tags": ["Developer", "Writer"]
}

3. テーブル定義

DynamoDB のテーブルは CloudFormation または CDK を使用して定義できます。

CloudFormation 定義例:

Resources:
  <asset_name>:
    Type: AWS::DynamoDB:Table
    Properties:
      TableName: <table_name>
      AttributeDefinitions:
        - AttributeName: <pk_name>
          AttributeType: <pk_type>
        - AttributeName: <sk_name>
          AttributeType: <sk_type>
      KeySchema:
        - AttributeName: <pk_name>
          AttributeType: HASH
        - AttributeName: <sk_name>
          AttributeType: RANGE
      BillingMode: PAY_PER_REQUEST

CDK 定義例:

import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';

const table = new dynamodb.Table(this, '<asset_name>', {
  tableName: '<table_name>',
  partitionKey: { name: '<pk_name>', type: dynamodb.AttributeType.<pk_type> },
  sortKey: { name: '<sk_name>', type: dynamodb.AttributeType.STRING.<sk_type> },
  billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
});

パラメーターの説明:

  • <asset_name>:CloudFormation/CDK のリソース名
  • <table_name>:DynamoDB のテーブル名
  • <pk_name>:パーティションキーの名前
  • <pk_type>:パーティションキーの型
  • <sk_name>:ソートキーの名前
  • <sk_type>:ソートキーの型

4. データベース操作

以下は DynamoDB の初期設定と基本的な操作例です。

初期設定

必要な依存関係をインストール:

npm install @aws-sdk/client-dynamodb @aws-sdk/lib-dynamodb

DynamoDB クライアントの初期化:

// dynamodb.util.ts
import { DynamoDB } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';

const dbClient = new DynamoDB({});

const marshallOptions = {
  // 空の文字列、バイナリ、およびセットを自動的に「null」に変換するか
  convertEmptyValues: false, // デフォルト値:false
  // 挿入時に未定義の値を削除するか
  removeUndefinedValues: false, // デフォルト値:false
  // オブジェクトを map 型に変換するか
  convertClassInstanceToMap: false, // デフォルト値:false
};

const unmarshallOptions = {
  // 数値をJavaScriptのNumber型に変換するのではなく、文字列として返すか
  wrapNumbers: false, // デフォルト値:false
};

const translateConfig = { marshallOptions, unmarshallOptions };
/**
 * DynamoDBの「CRUD」操作するには、「DocumentClient」を使用する
 */
const docClient = DynamoDBDocumentClient.from(dbClient, translateConfig);

export { docClient };

主要なデータ操作例

以下に DynamoDB の代表的な操作例を示します。

データ追加

user_table に新しいユーザーを追加する例です:

import { docClient } from './dynamodb.util';
import { PutCommand } from '@aws-sdk/lib-dynamodb';

const command = new PutCommand({
  TableName: 'user_table',
  Item: {
    pk: 'USER#12345',
    sk: 'USER_INFO',
    name: 'Alice',
    age: 30,
  },
});
const result = await docClient.send(command);

データ削除

user_table から ID が 12345 のユーザーを削除する例です:

import { docClient } from './dynamodb.util';
import { DeleteCommand } from '@aws-sdk/lib-dynamodb';

const command = new DeleteCommand({
  TableName: 'user_table',
  Key: {
    pk: 'USER#12345',
    sk: 'USER_INFO',
  },
});
const result = await docClient.send(command);

データ更新

user_table 内の ID が 12345 のユーザーの name 属性を Bob に変更する例です:

import { docClient } from './dynamodb.util';
import { UpdateCommand } from '@aws-sdk/lib-dynamodb';

const command = new UpdateCommand({
  TableName: 'user_table',
  Key: {
    pk: 'USER#12345',
    sk: 'USER_INFO',
  },
  UpdateExpression: 'set #name = :name',
  ExpressionAttributeNames: {
    '#name': 'name',
  },
  ExpressionAttributeValues: {
    ':name': 'Bob',
  },
});
const result = await docClient.send(command);

単一データの取得

user_table から ID が 12345 のユーザーを取得する例です:

import { docClient } from './dynamodb.util';
import { GetCommand } from '@aws-sdk/lib-dynamodb';

const command = new GetCommand({
  TableName: 'user_table',
  Key: {
    pk: 'USER#12345',
    sk: 'USER_INFO',
  },
});
const result = await docClient.send(command);

複数データの取得

user_table から age20 以上 30 以下のユーザーを取得する場合:

import { docClient } from './dynamodb.util';
import { ScanCommand } from '@aws-sdk/lib-dynamodb';

const command = new ScanCommand({
  TableName: 'user_table',
  FilterExpression: '#sk = :sk and #age between :start and :end',
  ExpressionAttributeNames: {
    '#sk': 'sk',
    '#age': 'age',
  },
  ExpressionAttributeValues: {
    ':sk': 'USER',
    ':start': 20,
    ':end': 30,
  },
});
const result = await docClient.send(command);

注意: このような全表スキャン (ScanCommand) は効率が低いため、後述する GSI (グローバルセカンダリインデックス) を活用する方が適切です。

トランザクションを使用した複数データの同時更新

DynamoDB は ACID トランザクションをサポートし、複数の操作を一括で成功または失敗させることが可能です。

以下の例では、2 人のユーザーの name 属性を同時に更新します:

import { docClient } from './dynamodb.util';
import { TransactWriteCommand } from '@aws-sdk/lib-dynamodb';

const command = new TransactWriteCommand({
  TransactItems: [
    {
      Update: {
        TableName: 'user_table',
        Key: {
          pk: 'USER#12345',
          sk: 'USER_INFO',
        },
        UpdateExpression: 'set #name = :name',
        ExpressionAttributeNames: {
          '#name': 'name',
        },
        ExpressionAttributeValues: {
          ':name': 'Bob',
        },
      },
    },
    {
      Update: {
        TableName: 'user_table',
        Key: {
          pk: 'USER#56789',
          sk: 'USER_INFO',
        },
        UpdateExpression: 'set #name = :name',
        ExpressionAttributeNames: {
          '#name': 'name',
        },
        ExpressionAttributeValues: {
          ':name': 'Lisa',
        },
      },
    },
  ],
});
const result = await docClient.send(command);

5. グローバルセカンダリインデックス (GSI)

GSI の概要

グローバルセカンダリインデックス (GSI) を使用すると、テーブルの既存のパーティションキーやソートキーとは異なるキーを使用してクエリを実行できます。これにより、クエリ性能が向上します。

例えば、user_table で age が 20~30 のユーザーを取得する際、全表スキャンを行わずに GSI を活用することで効率的にデータを取得できます。

GSI の設定方法

以下は、CloudFormation を使用して GSI を設定する例です:

Resources:
  WorkTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: user_table
      AttributeDefinitions:
        - AttributeName: pk
          AttributeType: S
        - AttributeName: sk
          AttributeType: S
        - AttributeName: age # age の定義を追加
          AttributeType: N # タイプはNumber
      KeySchema:
        - AttributeName: pk
          KeyType: HASH
        - AttributeName: sk
          KeyType: RANGE
      BillingMode: PAY_PER_REQUEST
      GlobalSecondaryIndexes: 
        - IndexName: UserAgeIndex # GSI名称
          KeySchema:
            - AttributeName: sk # GSIのパーティションキー
              KeyType: HASH
            - AttributeName: age # GSIのソートキー
              KeyType: RANGE
          Projection:
            ProjectionType: ALL

GSI を使用したクエリ例

上記の GSI (UserAgeIndex) を使用してクエリを実行する例:

import { docClient } from './dynamodb.util';
import { QueryCommand } from '@aws-sdk/lib-dynamodb';

const command = new QueryCommand({
  TableName: 'user_table',
  IndexName: 'UserAgeIndex',
  KeyConditionExpression: '#sk = :sk and #age between :start and :end',
  ExpressionAttributeNames: {
    '#sk': 'sk',
    '#age': 'age',
  },
  ExpressionAttributeValues: {
    ':sk': 'USER',
    ':start': 20,
    ':end': 30,
  },
  ScanIndexForward: false, // false: 降順、true: 昇順 (デフォルトは true)
});
const result = await docClient.send(command);

6. テーブル設計のベストプラクティス

DynamoDB の設計は、リレーショナルデータベースとは異なり、アクセスパターンを最適化する形で行います。以下にサンプルを示します:

データ構成

  • 部署:部署ID、部署名
  • 社員:社員ID、社員名、邮箱
  • 出勤情報:日付、出勤時刻、退勤時刻、休憩時間

検索要望

  • 部署一覧を取得する
  • 従業員 ID で従業員情報を取得する
  • 部署 ID を基に、その部署に所属する従業員を取得する
  • 従業員 ID と年月を基に、その従業員の月次勤怠記録を取得する

リレーショナルデータベースでは以下のような設計になります:

rds

DynamoDB の場合、以下のように設計します:

dynamodb

部署一覧を取得する

EmployeeTable から全ての部署一覧を取得します。主キーの pkDEPARTMENT としてクエリを実行します:

import { docClient } from './dynamodb.util';
import { QueryCommand } from '@aws-sdk/lib-dynamodb';

const command = new QueryCommand({
  TableName: 'EmployeeTable',
  KeyConditionExpression: 'pk = :pk',
  ExpressionAttributeValues: {
    ':pk': 'DEPARTMENT',
  },
});
const result = await docClient.send(command);

従業員 ID で従業員情報を取得する

特定の従業員の情報を取得します。pkEmployee#<employee_id>skINFO と指定します:

import { docClient } from './dynamodb.util';
import { GetCommand } from '@aws-sdk/lib-dynamodb';

const command = new GetCommand({
  TableName: 'EmployeeTable',
  Key: {
    pk: 'Employee#<employee_id>',
    sk: 'INFO',
  },
});
const result = await docClient.send(command);

部署 ID を基に、その部署に所属する従業員を取得する

GSI (DepartmentIndex) を使用して、部署 ID を基にその部署の従業員一覧を取得します:

import { docClient } from './dynamodb.util';
import { QueryCommandInput } from '@aws-sdk/lib-dynamodb';

const command = new QueryCommandInput({
  TableName: 'EmployeeTable',
  IndexName: 'DepartmentIndex',
  KeyConditionExpression: 'departmentId = :departmentId',
  ExpressionAttributeValues: {
    ':departmentId': '<department_id>',
  },
});
const result = await docClient.send(command);

従業員 ID と年月を基に、その従業員の月次勤怠記録を取得する

pkEmployee#<employee_id>skWORK#<年月> で絞り込みます:

import { docClient } from './dynamodb.util';
import { QueryCommandInput } from '@aws-sdk/lib-dynamodb';

const command = new QueryCommandInput({
  TableName: 'EmployeeTable',
  KeyConditionExpression: 'pk = :pk and begins_with(sk, :sk)',
  ExpressionAttributeValues: {
    ':pk': 'Employee#<employee_id>',
    ':sk': 'WORK#202501', // 2025年1月の勤怠記録
  },
});
const result = await docClient.send(command);

ベストプラクティスの要点

  1. アクセスパターンの明確化

DynamoDB の設計は、データの保存方法ではなく「データのアクセス方法」に基づいて行います。

  1. 単一テーブル設計

異なる種類のデータを 1 つのテーブルに保存し、パーティションキーやソートキー、GSI を活用して効率的なクエリを実現します。

  1. インデックスの活用

GSI や LSI(ローカルセカンダリインデックス)を適切に使用して、クエリの柔軟性を確保します。

  1. スキャンを最小限に抑える

全表スキャンはパフォーマンスが低下するため、可能な限り GSI や特定のキーを使用したクエリを採用します。

  1. トランザクションの利用

DynamoDB のトランザクションを活用して、データの整合性を確保します。


これで DynamoDB を活用した効率的なデータ操作について一通り説明しました。さらに詳しい内容や実践的な例をご希望であればお知らせください!

概要

技術的洞察、経験、思考を共有する個人ブログ

クイックリンク

お問い合わせ

  • Email: hushukang_blog@proton.me
  • GitHub

© 2025 CODE赤兎. 無断転載禁止