MongoDB Atlas
データベースプラットフォーム
MongoDB Atlas
概要
MongoDB AtlasはMongoDBのフルマネージドクラウドデータベースサービスです。グローバル分散、自動スケーリング、組み込みセキュリティ機能を統合し、NoSQLアプリケーション開発を大幅に簡素化します。開発者フレンドリーなドキュメント指向モデルと柔軟なスキーマにより、モダンアプリケーション開発で広く採用されています。AWS、Google Cloud、Microsoft Azureの複数クラウドプロバイダーで利用可能です。
詳細
ドキュメント指向データベース
MongoDBは柔軟なJSONライクなドキュメント形式でデータを格納し、複雑なデータ構造を自然に表現できます。スキーマレスの設計により、アプリケーションの進化に合わせてデータモデルを容易に変更できます。
グローバル分散とシャーディング
データを複数のリージョンとデータセンターに自動分散し、高可用性と災害復旧を実現します。水平スケーリング(シャーディング)により、大規模なデータセットとトラフィックに対応できます。
Atlas Search とデータレイク
全文検索、ファセット検索、オートコンプリート機能を内蔵し、Elasticsearchなしに高度な検索機能を実装できます。Atlas Data Lakeにより、アーカイブデータに対する分析クエリも効率的に実行できます。
組み込みセキュリティとコンプライアンス
暗号化、ネットワーク分離、アクセス制御、監査ログを標準提供し、GDPR、HIPAA、SOC 2などの規制要件に対応します。
メリット・デメリット
メリット
- フルマネージドサービス: インフラ管理、バックアップ、監視を完全自動化
- 柔軟なスキーマ: アプリケーション開発の初期段階から本格運用まで対応
- 強力なクエリ機能: 豊富な集約パイプラインと地理空間クエリをサポート
- 自動スケーリング: 需要に応じたコンピュートとストレージの動的調整
- マルチクラウド対応: AWS、GCP、Azureで一貫した体験を提供
- 豊富な統合: BI ツール、アナリティクス、機械学習プラットフォームとの連携
- 開発者エクスペリエンス: 直感的なWeb UI、CLI、豊富なSDKを提供
デメリット
- SQL非対応: 既存のSQLスキルや RDBMS ツールが直接活用できない
- ACID制限: 複雑なトランザクション処理でRDBMSより制約がある
- メモリ使用量: WiredTigerストレージエンジンで大きなメモリ使用量が必要
- ベンダーロックイン: MongoDB特有の機能への依存が移行を困難にする
- 学習コスト: NoSQLとドキュメント指向DBの概念理解が必要
- 高トラフィック時のコスト: 大規模利用では従来の自己管理型より高価
参考ページ
- 公式サイト: https://www.mongodb.com/atlas/
- ドキュメント: https://www.mongodb.com/docs/atlas/
- コミュニティ: https://www.mongodb.com/community/
- University: https://university.mongodb.com/
- ブログ: https://www.mongodb.com/blog/
実装の例
セットアップ
# MongoDB CLIをインストール
npm install -g mongodb-atlas-cli
# Atlasにログイン
atlas auth login
# 新しいクラスターを作成
atlas clusters create myCluster --provider AWS --region US_EAST_1
# Node.js ドライバーをインストール
npm install mongodb
# 接続文字列を取得
atlas clusters describe myCluster --output json
スキーマ設計
// コレクション設計例
// ユーザーコレクション
const userSchema = {
_id: ObjectId,
email: String,
username: String,
profile: {
firstName: String,
lastName: String,
avatar: String,
bio: String
},
settings: {
theme: String,
notifications: Boolean,
privacy: String
},
createdAt: Date,
updatedAt: Date
}
// 投稿コレクション(埋め込みコメント)
const postSchema = {
_id: ObjectId,
authorId: ObjectId,
title: String,
content: String,
tags: [String],
status: String, // "draft", "published", "archived"
metadata: {
views: Number,
likes: Number,
readTime: Number
},
comments: [{
_id: ObjectId,
authorId: ObjectId,
content: String,
createdAt: Date,
replies: [{
_id: ObjectId,
authorId: ObjectId,
content: String,
createdAt: Date
}]
}],
createdAt: Date,
updatedAt: Date
}
// インデックス作成
db.users.createIndex({ email: 1 }, { unique: true })
db.users.createIndex({ username: 1 }, { unique: true })
db.posts.createIndex({ authorId: 1, status: 1 })
db.posts.createIndex({ tags: 1 })
db.posts.createIndex({ createdAt: -1 })
// 複合インデックス
db.posts.createIndex({
"status": 1,
"createdAt": -1,
"metadata.likes": -1
})
// 地理空間インデックス
db.locations.createIndex({ coordinates: "2dsphere" })
// テキスト検索インデックス
db.posts.createIndex({
title: "text",
content: "text",
tags: "text"
})
データ操作
import { MongoClient, ObjectId } from 'mongodb'
const uri = process.env.MONGODB_URI
const client = new MongoClient(uri)
async function connectToDatabase() {
await client.connect()
const db = client.db('myapp')
return db
}
// CRUD操作
class UserService {
constructor(db) {
this.collection = db.collection('users')
}
async createUser(userData) {
const user = {
...userData,
createdAt: new Date(),
updatedAt: new Date()
}
const result = await this.collection.insertOne(user)
return { ...user, _id: result.insertedId }
}
async getUserById(id) {
return await this.collection.findOne({ _id: new ObjectId(id) })
}
async updateUser(id, updates) {
const result = await this.collection.updateOne(
{ _id: new ObjectId(id) },
{
$set: {
...updates,
updatedAt: new Date()
}
}
)
return result.modifiedCount > 0
}
async deleteUser(id) {
const result = await this.collection.deleteOne({
_id: new ObjectId(id)
})
return result.deletedCount > 0
}
// 複雑なクエリ例
async getUsersWithStats() {
return await this.collection.aggregate([
{
$lookup: {
from: 'posts',
localField: '_id',
foreignField: 'authorId',
as: 'posts'
}
},
{
$addFields: {
postCount: { $size: '$posts' },
totalLikes: {
$sum: '$posts.metadata.likes'
}
}
},
{
$project: {
username: 1,
email: 1,
postCount: 1,
totalLikes: 1,
createdAt: 1
}
},
{
$sort: { totalLikes: -1 }
}
]).toArray()
}
}
// 投稿サービス
class PostService {
constructor(db) {
this.collection = db.collection('posts')
}
async createPost(postData) {
const post = {
...postData,
metadata: {
views: 0,
likes: 0,
readTime: this.calculateReadTime(postData.content)
},
comments: [],
createdAt: new Date(),
updatedAt: new Date()
}
const result = await this.collection.insertOne(post)
return { ...post, _id: result.insertedId }
}
async addComment(postId, comment) {
const newComment = {
_id: new ObjectId(),
...comment,
createdAt: new Date(),
replies: []
}
await this.collection.updateOne(
{ _id: new ObjectId(postId) },
{
$push: { comments: newComment },
$inc: { 'metadata.views': 1 }
}
)
return newComment
}
async searchPosts(query, options = {}) {
const pipeline = []
// テキスト検索
if (query) {
pipeline.push({
$match: {
$text: { $search: query }
}
})
}
// フィルター
if (options.tags && options.tags.length > 0) {
pipeline.push({
$match: {
tags: { $in: options.tags }
}
})
}
// ソート
pipeline.push({
$sort: options.sortBy || { createdAt: -1 }
})
// ページネーション
if (options.skip) {
pipeline.push({ $skip: options.skip })
}
if (options.limit) {
pipeline.push({ $limit: options.limit })
}
return await this.collection.aggregate(pipeline).toArray()
}
calculateReadTime(content) {
const wordsPerMinute = 200
const wordCount = content.split(' ').length
return Math.ceil(wordCount / wordsPerMinute)
}
}
スケーリング
// シャーディング設定
class DatabaseManager {
constructor() {
this.client = new MongoClient(process.env.MONGODB_URI, {
maxPoolSize: 100,
minPoolSize: 5,
maxIdleTimeMS: 30000,
serverSelectionTimeoutMS: 5000,
})
}
async setupSharding() {
const admin = this.client.db('admin')
// シャーディングを有効化
await admin.command({ enableSharding: 'myapp' })
// シャードキーを設定
await admin.command({
shardCollection: 'myapp.posts',
key: { authorId: 1, createdAt: 1 }
})
}
// 読み取り設定の最適化
async getReadOnlyConnection() {
return new MongoClient(process.env.MONGODB_URI, {
readPreference: 'secondary',
readConcern: { level: 'available' }
})
}
// バッチ処理の最適化
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,
writeConcern: { w: 'majority', j: true }
})
results.push(result)
}
return results
}
}
バックアップ・復旧
// MongoDB Atlasの自動バックアップは管理コンソールで設定
// プログラマティックなバックアップ操作
class BackupManager {
constructor(db) {
this.db = db
}
async exportCollection(collectionName, query = {}) {
const collection = this.db.collection(collectionName)
const cursor = collection.find(query)
const documents = []
await cursor.forEach(doc => {
documents.push(doc)
})
return {
collection: collectionName,
count: documents.length,
data: documents,
exportedAt: new Date()
}
}
async importCollection(collectionName, data) {
const collection = this.db.collection(collectionName)
// 既存データのクリア(オプション)
// await collection.deleteMany({})
if (data.length > 0) {
const result = await collection.insertMany(data, {
ordered: false
})
return result.insertedCount
}
return 0
}
async createPointInTimeSnapshot() {
// Atlas APIを使用したスナップショット作成
const response = await fetch(
`https://cloud.mongodb.com/api/atlas/v1.0/groups/${process.env.ATLAS_PROJECT_ID}/clusters/${process.env.CLUSTER_NAME}/backup/snapshots`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${process.env.ATLAS_API_TOKEN}`
},
body: JSON.stringify({
description: `Manual snapshot ${new Date().toISOString()}`,
retentionInDays: 7
})
}
)
return await response.json()
}
}
統合
// Express.js REST API
import express from 'express'
import { MongoClient } from 'mongodb'
const app = express()
app.use(express.json())
let db
MongoClient.connect(process.env.MONGODB_URI)
.then(client => {
db = client.db('myapp')
console.log('Connected to MongoDB Atlas')
})
// REST エンドポイント
app.get('/api/posts', async (req, res) => {
try {
const { page = 1, limit = 10, tag, search } = req.query
const skip = (page - 1) * limit
const query = { status: 'published' }
if (tag) query.tags = tag
if (search) query.$text = { $search: search }
const posts = await db.collection('posts')
.find(query)
.sort({ createdAt: -1 })
.skip(skip)
.limit(parseInt(limit))
.toArray()
const total = await db.collection('posts').countDocuments(query)
res.json({
posts,
pagination: {
page: parseInt(page),
limit: parseInt(limit),
total,
pages: Math.ceil(total / limit)
}
})
} catch (error) {
res.status(500).json({ error: error.message })
}
})
// GraphQL との統合
import { ApolloServer } from '@apollo/server'
import { startStandaloneServer } from '@apollo/server/standalone'
const typeDefs = `
type User {
id: ID!
username: String!
email: String!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
createdAt: String!
}
type Query {
users: [User!]!
posts: [Post!]!
post(id: ID!): Post
}
type Mutation {
createPost(title: String!, content: String!, authorId: ID!): Post!
}
`
const resolvers = {
Query: {
users: async () => {
return await db.collection('users').find({}).toArray()
},
posts: async () => {
return await db.collection('posts')
.aggregate([
{
$lookup: {
from: 'users',
localField: 'authorId',
foreignField: '_id',
as: 'author'
}
},
{ $unwind: '$author' }
])
.toArray()
}
},
Mutation: {
createPost: async (_, { title, content, authorId }) => {
const post = {
title,
content,
authorId: new ObjectId(authorId),
createdAt: new Date()
}
const result = await db.collection('posts').insertOne(post)
return { ...post, id: result.insertedId }
}
}
}
const server = new ApolloServer({ typeDefs, resolvers })