データベース

Amazon DynamoDB

概要

Amazon DynamoDBは、AWSが提供するフルマネージド型のNoSQLデータベースサービスです。どんな規模のデータでも保存・取得でき、あらゆるレベルのリクエストトラフィックに対応し、サーバーレスで一桁ミリ秒のパフォーマンスを提供します。

詳細

DynamoDBは2012年にAmazonによって発表され、Amazon.comの内部で使用されていたDynamoデータベースをベースに開発されました。完全マネージド型のサービスとして、サーバーの管理、パッチ適用、ソフトウェアのアップグレード、レプリケーション、バックアップを自動化し、開発者がアプリケーション開発に集中できるよう設計されています。

DynamoDBの主な特徴:

  • サーバーレス・フルマネージド
  • 一桁ミリ秒のレスポンス時間
  • 自動スケーリング
  • グローバルテーブル(マルチリージョン)
  • ACID トランザクション
  • 細かい粒度のアクセス制御
  • DynamoDB Accelerator (DAX) によるインメモリキャッシュ
  • DynamoDB Streams によるデータ変更の監視
  • 暗号化(保存時・転送時)
  • ポイントインタイム復旧

メリット・デメリット

メリット

  • サーバーレス: サーバー管理が不要で運用負荷が軽減
  • 高性能: 一桁ミリ秒の低レイテンシ
  • 自動スケーリング: トラフィックに応じて自動的にスケール
  • 高可用性: 99.999%のSLA保証
  • セキュリティ: 暗号化やIAM統合による包括的なセキュリティ
  • AWS統合: 他のAWSサービスとの密な連携
  • コスト効率: 使用した分だけの従量課金

デメリット

  • ベンダーロックイン: AWS に依存する設計
  • SQL制限: SQLクエリが使用できない
  • データモデル制約: NoSQLの制約による設計の複雑さ
  • コスト: 大規模利用時の費用が高額になる場合
  • 学習コスト: DynamoDB特有の概念と制限の習得が必要

主要リンク

書き方の例

セットアップ・設定

# AWS CLI インストール・設定
aws configure

# DynamoDB ローカル(開発用)
docker run -p 8000:8000 amazon/dynamodb-local

# AWS SDK インストール(Node.js)
npm install aws-sdk

# AWS SDK インストール(Python)
pip install boto3

基本操作(テーブル管理)

const AWS = require('aws-sdk')

// DynamoDB クライアント設定
const dynamodb = new AWS.DynamoDB.DocumentClient({
  region: 'ap-northeast-1'
})

// テーブル作成
const createTableParams = {
  TableName: 'Users',
  KeySchema: [
    { AttributeName: 'userId', KeyType: 'HASH' },  // パーティションキー
    { AttributeName: 'timestamp', KeyType: 'RANGE' }  // ソートキー
  ],
  AttributeDefinitions: [
    { AttributeName: 'userId', AttributeType: 'S' },
    { AttributeName: 'timestamp', AttributeType: 'N' }
  ],
  BillingMode: 'PAY_PER_REQUEST'  // オンデマンド
}

// テーブル一覧取得
const tables = await dynamodb.listTables().promise()
console.log(tables.TableNames)

基本操作(CRUD)

// アイテム作成(Create)
const putParams = {
  TableName: 'Users',
  Item: {
    userId: 'user123',
    timestamp: Date.now(),
    name: '田中太郎',
    email: '[email protected]',
    age: 30,
    address: {
      city: '東京',
      prefecture: '東京都'
    },
    tags: ['engineer', 'javascript']
  }
}

await dynamodb.put(putParams).promise()

// アイテム読み取り(Read)
const getParams = {
  TableName: 'Users',
  Key: {
    userId: 'user123',
    timestamp: 1640995200000
  }
}

const result = await dynamodb.get(getParams).promise()
console.log(result.Item)

// アイテム更新(Update)
const updateParams = {
  TableName: 'Users',
  Key: {
    userId: 'user123',
    timestamp: 1640995200000
  },
  UpdateExpression: 'SET age = :age, email = :email',
  ExpressionAttributeValues: {
    ':age': 31,
    ':email': '[email protected]'
  },
  ReturnValues: 'UPDATED_NEW'
}

await dynamodb.update(updateParams).promise()

// アイテム削除(Delete)
const deleteParams = {
  TableName: 'Users',
  Key: {
    userId: 'user123',
    timestamp: 1640995200000
  }
}

await dynamodb.delete(deleteParams).promise()

クエリとスキャン

// クエリ(パーティションキーで検索)
const queryParams = {
  TableName: 'Users',
  KeyConditionExpression: 'userId = :userId',
  ExpressionAttributeValues: {
    ':userId': 'user123'
  },
  ScanIndexForward: false,  // 降順
  Limit: 10
}

const queryResult = await dynamodb.query(queryParams).promise()
console.log(queryResult.Items)

// スキャン(全テーブル検索)
const scanParams = {
  TableName: 'Users',
  FilterExpression: 'age > :age',
  ExpressionAttributeValues: {
    ':age': 25
  }
}

const scanResult = await dynamodb.scan(scanParams).promise()
console.log(scanResult.Items)

// バッチ読み取り
const batchGetParams = {
  RequestItems: {
    'Users': {
      Keys: [
        { userId: 'user123', timestamp: 1640995200000 },
        { userId: 'user456', timestamp: 1640995300000 }
      ]
    }
  }
}

const batchResult = await dynamodb.batchGet(batchGetParams).promise()

インデックス・最適化

// グローバルセカンダリインデックス(GSI)作成
const updateTableParams = {
  TableName: 'Users',
  GlobalSecondaryIndexUpdates: [
    {
      Create: {
        IndexName: 'EmailIndex',
        KeySchema: [
          { AttributeName: 'email', KeyType: 'HASH' }
        ],
        Projection: { ProjectionType: 'ALL' },
        BillingMode: 'PAY_PER_REQUEST'
      }
    }
  ],
  AttributeDefinitions: [
    { AttributeName: 'email', AttributeType: 'S' }
  ]
}

// GSI を使ったクエリ
const gsiQueryParams = {
  TableName: 'Users',
  IndexName: 'EmailIndex',
  KeyConditionExpression: 'email = :email',
  ExpressionAttributeValues: {
    ':email': '[email protected]'
  }
}

const gsiResult = await dynamodb.query(gsiQueryParams).promise()

実用例

// トランザクション
const transactParams = {
  TransactItems: [
    {
      Put: {
        TableName: 'Orders',
        Item: {
          orderId: 'order123',
          userId: 'user123',
          amount: 1000,
          status: 'created'
        }
      }
    },
    {
      Update: {
        TableName: 'Users',
        Key: { userId: 'user123' },
        UpdateExpression: 'ADD totalOrders :inc',
        ExpressionAttributeValues: { ':inc': 1 }
      }
    }
  ]
}

await dynamodb.transactWrite(transactParams).promise()

// 条件付き書き込み
const conditionalPutParams = {
  TableName: 'Users',
  Item: {
    userId: 'user789',
    name: '佐藤花子',
    email: '[email protected]'
  },
  ConditionExpression: 'attribute_not_exists(userId)'
}

// DynamoDB Streams の設定
const enableStreamsParams = {
  TableName: 'Users',
  StreamSpecification: {
    StreamEnabled: true,
    StreamViewType: 'NEW_AND_OLD_IMAGES'
  }
}

ベストプラクティス

// バッチ書き込み
const batchWriteParams = {
  RequestItems: {
    'Users': [
      {
        PutRequest: {
          Item: { userId: 'user001', name: 'ユーザー1' }
        }
      },
      {
        DeleteRequest: {
          Key: { userId: 'user002' }
        }
      }
    ]
  }
}

await dynamodb.batchWrite(batchWriteParams).promise()

// ページネーション
async function scanAllItems(tableName) {
  let items = []
  let lastEvaluatedKey = null
  
  do {
    const params = {
      TableName: tableName,
      ...(lastEvaluatedKey && { ExclusiveStartKey: lastEvaluatedKey })
    }
    
    const result = await dynamodb.scan(params).promise()
    items = items.concat(result.Items)
    lastEvaluatedKey = result.LastEvaluatedKey
  } while (lastEvaluatedKey)
  
  return items
}

// エラーハンドリング
try {
  await dynamodb.put(putParams).promise()
} catch (error) {
  if (error.code === 'ConditionalCheckFailedException') {
    console.log('条件チェックエラー')
  } else if (error.code === 'ProvisionedThroughputExceededException') {
    console.log('スループット制限エラー')
  }
}

AWS CLI での操作

# テーブル作成
aws dynamodb create-table \
  --table-name Users \
  --attribute-definitions \
    AttributeName=userId,AttributeType=S \
  --key-schema \
    AttributeName=userId,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

# アイテム追加
aws dynamodb put-item \
  --table-name Users \
  --item '{"userId": {"S": "user123"}, "name": {"S": "田中太郎"}}'

# アイテム取得
aws dynamodb get-item \
  --table-name Users \
  --key '{"userId": {"S": "user123"}}'

# テーブル一覧
aws dynamodb list-tables