Налаштування бази даних MongoDB для веб-застосунку
MongoDB виправдана там, де схема даних часто змінюється, документи складно нормалізувати, або коли потрібен гнучкий пошук по вкладеним структурам без JOIN. Каталоги товарів із довільними атрибутами, системи управління контентом, збір аналітичних подій — типові випадки.
Встановлення MongoDB 7.0
# Ubuntu 22.04 / 24.04
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | gpg --dearmor -o /usr/share/keyrings/mongodb-server-7.0.gpg
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" > /etc/apt/sources.list.d/mongodb-org-7.0.list
apt update && apt install -y mongodb-org
systemctl enable mongod && systemctl start mongod
Первісна настройка безпеки:
// mongosh
use admin
db.createUser({
user: "admin",
pwd: "strong_admin_password",
roles: ["root"]
})
// Створити користувача додатка
use myapp
db.createUser({
user: "myapp",
pwd: "app_password",
roles: [{ role: "readWrite", db: "myapp" }]
})
/etc/mongod.conf
net:
port: 27017
bindIp: 127.0.0.1 # тільки localhost; для репліації — указати IP
security:
authorization: enabled
storage:
dbPath: /var/lib/mongodb
wiredTiger:
engineConfig:
cacheSizeGB: 2 # 50% доступної RAM для WiredTiger кеш
operationProfiling:
slowOpThresholdMs: 100
mode: slowOp
replication:
replSetName: "rs0" # включити якщо потрібна репліація
Індекси
// Основні індекси — створювати відразу при проектуванні
db.users.createIndex({ email: 1 }, { unique: true, background: true })
db.orders.createIndex({ userId: 1, createdAt: -1 })
db.orders.createIndex({ status: 1, createdAt: -1 })
// Частковий індекс — тільки активні документи
db.sessions.createIndex(
{ userId: 1, expiresAt: 1 },
{ partialFilterExpression: { revokedAt: { $exists: false } } }
)
// TTL-індекс — автовидалення застарілих документів
db.logs.createIndex({ createdAt: 1 }, { expireAfterSeconds: 2592000 }) // 30 днів
// Текстовий пошук
db.articles.createIndex({ title: "text", body: "text" }, { default_language: "russian" })
// Compound wildcard для каталога з довільними атрибутами
db.products.createIndex({ "attributes.$**": 1 })
Aggregation Pipeline
Потужний інструмент для аналітики прямо в базі:
// Дохід за категоріями за період
db.orders.aggregate([
{
$match: {
createdAt: { $gte: ISODate("2024-01-01"), $lt: ISODate("2024-04-01") },
status: "paid"
}
},
{ $unwind: "$items" },
{
$lookup: {
from: "products",
localField: "items.productId",
foreignField: "_id",
as: "product"
}
},
{ $unwind: "$product" },
{
$group: {
_id: "$product.category",
revenue: { $sum: { $multiply: ["$items.price", "$items.quantity"] } },
orders: { $addToSet: "$_id" }
}
},
{
$project: {
category: "$_id",
revenue: { $round: ["$revenue", 2] },
orderCount: { $size: "$orders" }
}
},
{ $sort: { revenue: -1 } }
])
Replica Set
// mongosh на одному з вузлів
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "mongo1:27017", priority: 2 },
{ _id: 1, host: "mongo2:27017", priority: 1 },
{ _id: 2, host: "mongo3:27017", priority: 0, hidden: true, votes: 0 }
// третій вузол — прихований для бекапів, не бере участь у виборах
]
})
// Перевірка статусу
rs.status()
Рядок підключення застосунку з failover:
mongodb://myapp:password@mongo1:27017,mongo2:27017/myapp?replicaSet=rs0&readPreference=secondaryPreferred&w=majority
Mongoose (Node.js)
import mongoose, { Schema, Document } from 'mongoose'
interface IProduct extends Document {
sku: string
name: string
category: string
price: number
attributes: Record<string, unknown>
createdAt: Date
}
const productSchema = new Schema<IProduct>({
sku: { type: String, required: true, unique: true, index: true },
name: { type: String, required: true },
category: { type: String, required: true, index: true },
price: { type: Number, required: true, min: 0 },
attributes: { type: Schema.Types.Mixed, default: {} }
}, {
timestamps: true,
toJSON: { virtuals: true }
})
productSchema.index({ name: 'text', 'attributes.description': 'text' })
export const Product = mongoose.model<IProduct>('Product', productSchema)
// Запит з пагінацією
export async function listProducts(category: string, page = 1, limit = 24) {
const skip = (page - 1) * limit
const [items, total] = await Promise.all([
Product.find({ category }).sort({ createdAt: -1 }).skip(skip).limit(limit).lean(),
Product.countDocuments({ category })
])
return { items, total, pages: Math.ceil(total / limit) }
}
Резервне копіювання
# mongodump — логічний бекап
mongodump --uri="mongodb://myapp:password@localhost:27017/myapp" \
--gzip --archive=/backup/myapp_$(date +%Y%m%d).archive
# Відновлення
mongorestore --uri="mongodb://admin:password@localhost:27017" \
--gzip --archive=/backup/myapp_20240315.archive
# mongodump однієї колекції
mongodump --uri="..." --collection=orders --query='{"createdAt":{"$gte":{"$date":"2024-01-01T00:00:00Z"}}}'
Терміни
Настройка з replica set, конфігурація індексів для конкретної схеми: 1–2 дні. Шардування великого кластера з балансуванням даних: 3–5 днів. Міграція з реляційної БД з трансформацією схеми: 1–3 тижні залежно від складності доменної моделі.







