Адміністрування баз даних MongoDB для веб-додатків
MongoDB — документна база з горизонтальним масштабуванням. Гнучка схема зручна під час розробки, але у продакшені потребує дисципліни: без індексів запити стають скануванням колекції, без компактифікації 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 — компактифікувати послідовно на secondary, потім переключити primary
// Запускати під час обслуговування
Перевірити оверхед до та після:
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 })
Пулінг з'єднань
Mongoose (Node.js) за замовчуванням відкриває 5 з'єднань. Для високонавантажених додатків:
mongoose.connect(process.env.MONGO_URI, {
maxPoolSize: 50, // максимум з'єднань у пулі
minPoolSize: 5,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
connectTimeoutMS: 10000
})
Для PHP (розширення MongoDB):
$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 за секунду







