Раздел 36 · MongoDB с нуля

Эксплуатация: бэкапы, валидация схемы, Atlas

junior~25 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

В прошлом уроке база сама сообщала приложению о каждом изменении. Это последний урок раздела, и он про то, что нужно базе в продакшене каждый день, а не только в день запуска: резервные копии, изменения схемы без простоя, наблюдение за здоровьем, обновление версии. В конце посмотрим, что из этого берёт на себя облачный Atlas.

Медкарта и прививки

Здоровье не держится на одном визите к врачу. Есть плановые прививки, есть медкарта с историей, есть анализы, которые показывают проблему раньше, чем она заболит. Эксплуатация базы устроена так же: бэкап это прививка от катастрофы, мониторинг это анализы, а журнал изменений схемы это медкарта.

Логический бэкап: mongodump

Самый простой способ сохранить данные это mongodump. Он читает документы через обычные запросы и складывает каждую коллекцию в файл BSON. Утилиты лежат прямо в образе mongo:8, поэтому в Docker из урока про установку их можно запустить внутри контейнера:

mongodump --db=test --out=/backup
2026-10-10T04:13:29.642+0000	writing `test.books` to `/backup/test/books.bson`
2026-10-10T04:13:29.642+0000	writing `test.orders` to `/backup/test/orders.bson`
2026-10-10T04:13:29.642+0000	writing `test.authors` to `/backup/test/authors.bson`
2026-10-10T04:13:29.642+0000	writing `test.customers` to `/backup/test/customers.bson`
2026-10-10T04:13:29.644+0000	done dumping `test.orders` (5 documents)
2026-10-10T04:13:29.644+0000	done dumping `test.customers` (4 documents)
2026-10-10T04:13:29.645+0000	done dumping `test.books` (9 documents)
2026-10-10T04:13:29.645+0000	done dumping `test.authors` (6 documents)

Для каждой коллекции появилось два файла: books.bson с документами и books.metadata.json с индексами и опциями.

Теперь представим аварию: кто-то выполнил db.books.deleteMany({}). Восстанавливаем из копии утилитой mongorestore. Флаг --drop сначала удаляет коллекции, чтобы не смешать старое с новым:

mongorestore --drop /backup
...
2026-10-10T04:13:30.431+0000	no indexes to restore for collection `test.books`
2026-10-10T04:13:30.431+0000	no indexes to restore for collection `test.orders`
2026-10-10T04:13:30.431+0000	24 document(s) restored successfully. 0 document(s) failed to restore.

Все 24 документа на месте. «No indexes to restore» значит, что кроме обязательного _id индексов в коллекциях не было.

Хранить копию лучше одним сжатым файлом, а проверять, восстанавливая её в отдельную базу рядом с рабочей:

mongodump --db=test --archive=/backup/shop-2026-10-10.gz --gzip
mongorestore --archive=/backup/shop-2026-10-10.gz --gzip --nsFrom="test.*" --nsTo="test_copy.*"

Вторая команда загрузит всё в базу test_copy. Это и есть главное правило, знакомое по бэкапам PostgreSQL: копия, которую ни разу не восстанавливали, не считается копией.

У mongodump есть слабое место. Пока он читает одну коллекцию, приложение меняет другую, и копия получается из разных моментов времени. На replica set это лечится флагом --oplog: mongodump дописывает в выгрузку изменения из oplog, накопленные за время работы, а mongorestore --oplogReplay проигрывает их при загрузке. Так копия соответствует одному моменту.

Физический бэкап и восстановление на момент времени

mongodump хорош для небольших баз, переноса данных и учебных стендов. На сотнях гигабайт он медленный: читает каждый документ и заново строит индексы при восстановлении. Большие базы копируют физически, снимком диска с файлами данных, обычно со вторичного узла, чтобы не нагружать первичный.

Следующая ступень это восстановление на момент времени. К снимку добавляется непрерывная копия oplog, и базу можно вернуть в состояние «за минуту до того, как выполнили deleteMany». Руками это собирать сложно, поэтому такую копию обычно дают облако Atlas или инструменты вроде Percona Backup for MongoDB.

Миграции схемы без простоя

Схема в MongoDB гибкая, но приложение всё равно ожидает определённую форму документов. Допустим, магазин начинает продавать книги в разных валютах, и у каждой книги должно появиться поле currency. В SQL это ALTER TABLE, а в MongoDB обычно применяют версионирование схемы: в документ кладут поле schemaVersion.

Дальше два пути:

  • Разом. Одна команда updateMany переводит все старые документы на новую версию. Пишется за минуту, но на миллионах документов это долгая нагрузка на первичный узел, поэтому большие коллекции переводят пачками.
  • Постепенно. Приложение читает обе версии. Встретив документ без schemaVersion: 2, оно дописывает недостающее поле и сохраняет документ. Старые документы обновляются по мере обращений.

Когда все документы переведены, правило закрепляют валидатором, чтобы новый код случайно не записал книгу без валюты. Это обычный collMod:

db.books.updateMany({ schemaVersion: { $exists: false } }, { $set: { currency: 'RUB', schemaVersion: 2 } })
db.runCommand({ collMod: 'books', validator: { $jsonSchema: { required: ['title', 'currency'] } } })
db.books.insertOne({ _id: 20, title: 'Без валюты' })
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 9,
  modifiedCount: 9,
  upsertedCount: 0
}
{ ok: 1 }
Uncaught:
MongoServerError: Document failed validation
Additional information: {
  failingDocumentId: 20,
  details: {
    operatorName: '$jsonSchema',
    schemaRulesNotSatisfied: [
      {
        operatorName: 'required',
        specifiedAs: { required: [ 'title', 'currency' ] },
        missingProperties: [ 'currency' ]
      }
    ]
  }
}

Все девять книг перешли на вторую версию, а книга без валюты не записалась. Скрипты миграций хранят в репозитории рядом с кодом и прогоняют по порядку, как миграции SQL. Для этого есть готовые инструменты, например migrate-mongo.

урок из курса

Урок из курса «MongoDB с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.