Администрирование базы данных MongoDB для веб-приложения
MongoDB — документная база с горизонтальным масштабированием. Flex-схема удобна при разработке, но в продакшне требует дисциплины: без индексов запросы превращаются в collection scan, без компактификации WiredTiger не освобождает место, без мониторинга oplog-а реплики отстают и теряют синхронизацию.
Начальный аудит
// mongosh
use mydb
// Статистика базы
db.stats({ scale: 1024 * 1024 }) // в МБ
// Статистика коллекций
db.runCommand({ listCollections: 1 }).cursor.firstBatch.forEach(c => {
const stats = db[c.name].stats({ scale: 1024 })
printjson({
name: c.name,
docs: stats.count,
size_kb: stats.size,
storageSize_kb: stats.storageSize,
totalIndexSize_kb: stats.totalIndexSize,
nindexes: stats.nindexes
})
})
// Индексы коллекции с размерами
db.orders.stats().indexSizes
// Операции, выполняющиеся прямо сейчас (> 1 секунды)
db.currentOp({ "secs_running": { $gt: 1 } })
Индексирование
// Составной индекс для типичного запроса по пользователю и дате
db.orders.createIndex(
{ user_id: 1, created_at: -1 },
{ background: true, name: "idx_user_date" }
)
// Частичный индекс — только для активных заказов
db.orders.createIndex(
{ created_at: -1 },
{
partialFilterExpression: { status: { $in: ["pending", "processing"] } },
name: "idx_active_orders_date"
}
)
// TTL индекс для автоудаления устаревших сессий
db.sessions.createIndex(
{ expires_at: 1 },
{ expireAfterSeconds: 0, name: "ttl_sessions" }
)
// Проверка использования индексов
db.orders.aggregate([
{ $indexStats: {} }
]).forEach(stat => {
if (stat.accesses.ops === 0) {
print("UNUSED INDEX: " + stat.name)
}
})
background: true позволяет создавать индексы без блокировки базы. В MongoDB 4.2+ это поведение по умолчанию.
Explain и оптимизация запросов
// Анализ плана выполнения запроса
db.orders.find({ user_id: "507f1f77bcf86cd799439011", status: "pending" })
.explain("executionStats")
// Ключевые поля в executionStats:
// - stage: "COLLSCAN" означает полный скан коллекции — нужен индекс
// - stage: "IXSCAN" — использует индекс, хорошо
// - nReturned vs totalDocsExamined — чем ближе к 1:1, тем лучше индекс
// - executionTimeMillis — время выполнения
Включение профайлера для поиска медленных запросов:
// Профилировать запросы > 100ms
db.setProfilingLevel(1, { slowms: 100 })
// Просмотр медленных запросов
db.system.profile.find(
{ millis: { $gt: 100 } },
{ ns: 1, command: 1, millis: 1, ts: 1 }
).sort({ millis: -1 }).limit(20)
// Выключить профайлер (влияет на производительность!)
db.setProfilingLevel(0)
Replica Set: настройка и мониторинг
// Инициализация replica set
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "mongo1:27017", priority: 2 },
{ _id: 1, host: "mongo2:27017", priority: 1 },
{ _id: 2, host: "mongo3:27017", arbiterOnly: true }
]
})
// Статус репликации
rs.status()
// Лаг реплики (критично: если oplog переполнится — реплика потеряет синхронизацию)
rs.printSecondaryReplicationInfo()
// Размер oplog (должен покрывать несколько часов операций)
rs.printReplicationInfo()
// oplog size: 5760MB
// log length start to end: 14400 secs (4 hrs)
// Если меньше 4–6 часов — увеличить:
db.adminCommand({ replSetResizeOplog: 1, size: 10240 }) // 10 GB
Резервное копирование
mongodump — для баз до нескольких ГБ:
# Бэкап с точкой согласованности на replica set
mongodump \
--uri="mongodb://backup_user:password@mongo1:27017,mongo2:27017/mydb?replicaSet=rs0" \
--readPreference=secondary \
--oplog \
--gzip \
--out=/backups/dump_$(date +%Y%m%d_%H%M)
# Восстановление
mongorestore \
--uri="mongodb://admin:password@localhost:27017" \
--gzip \
--oplogReplay \
/backups/dump_20250101_0300
mongodump блокирует чтение через --oplog только на secondary — для продакшн всегда снимать бэкап с secondary.
Для больших баз (>100 ГБ) — снапшоты файловой системы (LVM, AWS EBS Snapshot) быстрее физически.
WiredTiger: компактификация
MongoDB (WiredTiger) не возвращает освобождённое место ОС автоматически. После массового удаления документов — запустить compact:
// Компактификация конкретной коллекции
// ВНИМАНИЕ: блокирует базу на время выполнения!
db.runCommand({ compact: "orders" })
// На replica set — compact поочерёдно на secondary, потом переключить primary
// Запускать в окно обслуживания
Проверить overhead до и после:
const stats = db.orders.stats({ scale: 1024 * 1024 })
console.log({
dataSize: stats.size.toFixed(1) + ' MB',
storageSize: stats.storageSize.toFixed(1) + ' MB',
overhead_pct: ((1 - stats.size / stats.storageSize) * 100).toFixed(1) + '%'
})
Управление пользователями
use mydb
// Создание пользователя приложения
db.createUser({
user: "app_user",
pwd: "strong_password",
roles: [
{ role: "readWrite", db: "mydb" }
]
})
// Только чтение для аналитики
db.createUser({
user: "analytics",
pwd: "analytics_password",
roles: [
{ role: "read", db: "mydb" }
]
})
// Список пользователей
db.getUsers({ showCredentials: false })
Connection pooling
Mongoose (Node.js) по умолчанию открывает 5 соединений. Для высоконагруженных приложений:
mongoose.connect(process.env.MONGO_URI, {
maxPoolSize: 50, // максимум соединений в пуле
minPoolSize: 5,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
connectTimeoutMS: 10000
})
Для PHP (MongoDB extension):
$client = new MongoDB\Client(
"mongodb://app_user:password@mongo1:27017,mongo2:27017/mydb",
["replicaSet" => "rs0", "readPreference" => "secondaryPreferred"],
["typeMap" => ["root" => "array", "document" => "array"]]
);
Мониторинг через mongostat и mongotop
# Статистика операций в реальном времени (каждую секунду)
mongostat --uri="mongodb://admin:password@localhost:27017" --discover
# Топ коллекций по времени чтения/записи
mongotop --uri="mongodb://admin:password@localhost:27017" 5
# Ключевые метрики для мониторинга в Prometheus (через mongodb_exporter):
# - mongodb_ss_globalLock_currentQueue_total > 0 — очередь блокировок
# - mongodb_ss_connections_current vs maxIncomingConnections
# - replication lag на secondary
# - opcounters: insert/query/update/delete per second







