Настройка базы данных 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()
Connection string приложения с 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 недели в зависимости от сложности доменной модели.







