データベース
Amazon DocumentDB
概要
Amazon DocumentDBは、AWSが提供するフルマネージドのMongoDB互換ドキュメントデータベースサービスです。MongoDB APIとの高い互換性を保ちながら、AWS独自の分散ストレージアーキテクチャにより、高い可用性、耐久性、拡張性を実現しています。
詳細
Amazon DocumentDBは2019年にAWSによってリリースされ、MongoDBワークロードをクラウドネイティブに実行するために設計されました。以下の特徴を持ちます。
主要特徴
- MongoDB互換性: MongoDB 3.6、4.0、5.0 APIとの完全互換
- 分散アーキテクチャ: 6個のレプリカに自動複製、3つのAZ間での分散
- 自動スケーリング: ストレージは必要に応じて64TBまで自動拡張
- 高可用性: 99.99%のSLA、自動フェイルオーバー
- セキュリティ: VPC分離、暗号化(保存時・転送時)、IAM統合
- 自動バックアップ: 継続的バックアップと35日間のポイントインタイムリカバリ
- パフォーマンス: SSDストレージによる高速I/O、読み取りレプリカサポート
- 運用簡素化: フルマネージドサービス、自動パッチ適用
アーキテクチャ
- 分離されたコンピュートとストレージ: 独立してスケール可能
- クラスター構成: 最大16個のインスタンス(1プライマリ、15リードレプリカ)
- 分散ストレージ: 6-way複製、自動修復
- 多AZ対応: 高可用性とディザスタリカバリ
対応インスタンスタイプ
- t3.medium〜r5.24xlarge: 様々なワークロードに対応
- メモリ最適化: r5, r6gファミリー
- 汎用: t3, m5ファミリー
メリット・デメリット
メリット
- MongoDB互換: 既存のMongoDBアプリケーションをそのまま移行可能
- 運用負荷軽減: フルマネージドでインフラ管理不要
- 高い可用性: 99.99%のSLA、自動フェイルオーバー
- 自動スケーリング: ストレージとコンピュートの独立したスケーリング
- 強固なセキュリティ: AWS IAM、VPC、暗号化機能の統合
- 自動バックアップ: 継続的バックアップとポイントインタイムリカバリ
- パフォーマンス: 高速SSDストレージ、読み取りレプリカ
- AWSエコシステム: 他のAWSサービスとの緊密な統合
デメリット
- ベンダーロックイン: AWS専用サービス、他クラウドに移行困難
- コスト: セルフマネージドMongoDBより高額
- 機能制限: MongoDBの一部機能(Map-Reduce等)未対応
- リージョン制限: 利用可能リージョンが限定的
- カスタマイズ制限: インフラ・設定の詳細な制御が困難
- レイテンシ: MongoDBネイティブより若干のオーバーヘッド
主要リンク
書き方の例
インストール・セットアップ
# AWS CLIインストール・設定
aws configure
# DocumentDBクラスター作成(AWS CLI)
aws docdb create-db-cluster \
--db-cluster-identifier sample-cluster \
--engine docdb \
--master-username username \
--master-user-password password \
--vpc-security-group-ids sg-12345678 \
--db-subnet-group-name sample-subnet-group
# インスタンス作成
aws docdb create-db-instance \
--db-instance-identifier sample-instance \
--db-instance-class db.t3.medium \
--engine docdb \
--db-cluster-identifier sample-cluster
# 接続文字列の取得
aws docdb describe-db-clusters \
--db-cluster-identifier sample-cluster \
--query 'DBClusters[0].Endpoint'
基本操作(CRUD)
// Node.js MongoDB Driver使用
const MongoClient = require('mongodb').MongoClient;
// 接続設定(SSL必須)
const client = new MongoClient('mongodb://username:password@sample-cluster.cluster-123456789.us-east-1.docdb.amazonaws.com:27017/?tls=true&replicaSet=rs0&readPreference=secondaryPreferred', {
tlsCAFile: 'rds-ca-2019-root.pem', // AWSから取得
retryWrites: false
});
async function basicOperations() {
try {
await client.connect();
const db = client.db('sampledb');
const collection = db.collection('employees');
// ドキュメント挿入
const insertResult = await collection.insertOne({
name: '田中太郎',
email: '[email protected]',
department: 'Engineering',
salary: 75000,
joinDate: new Date(),
skills: ['JavaScript', 'Python', 'AWS']
});
console.log('Inserted document:', insertResult.insertedId);
// 複数ドキュメント挿入
await collection.insertMany([
{
name: '佐藤花子',
email: '[email protected]',
department: 'Marketing',
salary: 65000,
skills: ['Marketing', 'Analytics']
},
{
name: '鈴木次郎',
email: '[email protected]',
department: 'Engineering',
salary: 80000,
skills: ['Java', 'Kubernetes']
}
]);
// ドキュメント検索
const employees = await collection.find({
department: 'Engineering'
}).toArray();
console.log('Engineering employees:', employees);
// ドキュメント更新
await collection.updateOne(
{ email: '[email protected]' },
{
$set: { salary: 82000 },
$push: { skills: 'Docker' }
}
);
// ドキュメント削除
await collection.deleteOne({ email: '[email protected]' });
} finally {
await client.close();
}
}
basicOperations().catch(console.error);
データモデリング
// 複雑なドキュメント構造の例
const customerSchema = {
_id: ObjectId(),
customerId: 'CUST-12345',
profile: {
firstName: '田中',
lastName: '太郎',
email: '[email protected]',
phone: '+81-90-1234-5678',
address: {
zipCode: '100-0001',
prefecture: '東京都',
city: '千代田区',
street: '千代田1-1-1'
}
},
orders: [
{
orderId: 'ORD-001',
date: new Date('2024-01-15'),
items: [
{
productId: 'PROD-A',
name: 'Laptop',
quantity: 1,
price: 150000
}
],
total: 150000,
status: 'shipped'
}
],
preferences: {
language: 'ja',
currency: 'JPY',
notifications: {
email: true,
sms: false
}
},
metadata: {
createdAt: new Date(),
updatedAt: new Date(),
source: 'web'
}
};
// インデックス作成例
async function createIndexes() {
const collection = db.collection('customers');
// 単一フィールドインデックス
await collection.createIndex({ 'profile.email': 1 });
// 複合インデックス
await collection.createIndex({
'customerId': 1,
'orders.date': -1
});
// テキストインデックス
await collection.createIndex({
'profile.firstName': 'text',
'profile.lastName': 'text'
});
// 部分インデックス
await collection.createIndex(
{ 'orders.status': 1 },
{ partialFilterExpression: { 'orders.status': { $ne: 'cancelled' } } }
);
}
インデックス・最適化
// インデックス戦略
async function indexStrategy() {
const collection = db.collection('products');
// 効率的なクエリのためのインデックス
await collection.createIndex({ category: 1, price: -1 });
await collection.createIndex({ 'tags': 1 });
await collection.createIndex({ 'location.coordinates': '2dsphere' });
// インデックス使用状況の確認
const stats = await collection.aggregate([
{ $indexStats: {} }
]).toArray();
console.log('Index usage stats:', stats);
// クエリプランの確認
const explainResult = await collection.find({
category: 'electronics',
price: { $gte: 100 }
}).explain('executionStats');
console.log('Query execution plan:', explainResult);
}
// 集約パイプライン(Analytics)
async function analyticsQueries() {
const orders = db.collection('orders');
// 売上レポート
const salesReport = await orders.aggregate([
{
$match: {
orderDate: {
$gte: new Date('2024-01-01'),
$lt: new Date('2024-02-01')
}
}
},
{
$group: {
_id: '$customerId',
totalAmount: { $sum: '$total' },
orderCount: { $sum: 1 },
avgOrderValue: { $avg: '$total' }
}
},
{
$sort: { totalAmount: -1 }
},
{
$limit: 100
}
]).toArray();
return salesReport;
}
実用例
// 本番環境での設定例
const productionConfig = {
// 接続プール設定
maxPoolSize: 50,
minPoolSize: 5,
connectTimeoutMS: 30000,
socketTimeoutMS: 30000,
// 読み取り設定
readPreference: 'secondaryPreferred',
readConcern: { level: 'majority' },
// 書き込み設定
writeConcern: { w: 'majority', j: true },
// SSL/TLS設定
tls: true,
tlsCAFile: './rds-ca-2019-root.pem',
tlsAllowInvalidHostnames: false,
// 再試行設定
retryWrites: false, // DocumentDBでは無効
retryReads: true
};
// エラーハンドリングとリトライ
async function robustOperation(collection, operation) {
const maxRetries = 3;
let retries = 0;
while (retries < maxRetries) {
try {
return await operation(collection);
} catch (error) {
retries++;
if (retries >= maxRetries) {
throw error;
}
// 一時的なエラーの場合のみリトライ
if (error.code === 11000 || // 重複キーエラー
error.code === 16500 || // シャーディングエラー
error.message.includes('connection')) {
await new Promise(resolve => setTimeout(resolve, 1000 * retries));
continue;
}
throw error; // リトライしない
}
}
}
// CloudWatchメトリクス監視用の接続状態チェック
async function healthCheck() {
try {
await client.db('admin').command({ ismaster: 1 });
return { status: 'healthy', timestamp: new Date() };
} catch (error) {
return {
status: 'unhealthy',
error: error.message,
timestamp: new Date()
};
}
}
ベストプラクティス
// セキュリティのベストプラクティス
const securityConfig = {
// IAM認証使用(推奨)
authMechanism: 'MONGODB-AWS',
authSource: '$external',
// TLS設定
tls: true,
tlsCAFile: 'rds-ca-2019-root.pem',
tlsCertificateKeyFile: 'client.pem'
};
// パフォーマンス最適化
class DocumentDBOptimizer {
constructor(client) {
this.client = client;
}
// バッチ処理
async batchInsert(collection, documents, batchSize = 1000) {
const batches = [];
for (let i = 0; i < documents.length; i += batchSize) {
batches.push(documents.slice(i, i + batchSize));
}
const results = [];
for (const batch of batches) {
const result = await collection.insertMany(batch, { ordered: false });
results.push(result);
}
return results;
}
// 効率的なページネーション
async paginateWithCursor(collection, query, limit = 20, lastId = null) {
const pipeline = [
{ $match: query }
];
if (lastId) {
pipeline[0].$match._id = { $gt: ObjectId(lastId) };
}
pipeline.push(
{ $sort: { _id: 1 } },
{ $limit: limit + 1 }
);
const results = await collection.aggregate(pipeline).toArray();
const hasMore = results.length > limit;
if (hasMore) {
results.pop();
}
return {
data: results,
hasMore,
nextCursor: hasMore ? results[results.length - 1]._id : null
};
}
}
// 監視とログ設定
const logging = {
// 接続イベント
client.on('serverOpening', () => {
console.log('Connected to DocumentDB');
});
client.on('serverClosed', () => {
console.log('Disconnected from DocumentDB');
});
// パフォーマンス監視
client.on('commandStarted', (event) => {
console.log('Command started:', event.commandName);
});
client.on('commandSucceeded', (event) => {
console.log(`Command ${event.commandName} succeeded in ${event.duration}ms`);
});
client.on('commandFailed', (event) => {
console.error(`Command ${event.commandName} failed:`, event.failure);
});
};