Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Документные базы: MongoDB и гибкая схема

Документные базы данных: MongoDB и цена гибкой схемы

Документные базы: модель BSON, встраивание против ссылок, индексы, транзакции и типичные ошибки проектирования в MongoDB.

Прямой ответ

Документная база хранит записи как самодостаточные документы — обычно в формате BSON (бинарное представление JSON). Каждый документ содержит пары «поле — значение», где значением может быть вложенный документ или массив. Схема не задаётся заранее для всей коллекции: разные документы в одной коллекции могут иметь разный набор полей.

Ключевые понятия MongoDB:

  • Документ — аналог строки, но с вложенностью. Ограничение размера — 16 МБ.
  • Коллекция — аналог таблицы без фиксированной схемы.
  • _id — обязательный первичный ключ; если не задан, генерируется как ObjectId, содержащий метку времени.
  • Индексы — по умолчанию только по _id; остальные создаются явно, включая составные и по вложенным полям.

Главное решение при проектировании — встраивать или ссылаться:

Подход Когда применять Риск
Встраивание (embed) Данные читаются вместе, отношение «один к немногим», подчинённые не нужны отдельно Разрастание документа, дублирование
Ссылка (reference) Данные нужны отдельно, отношение «ко многим», подчинённые меняются независимо Несколько запросов, $lookup дорог
// встраивание: заказ и его позиции читаются одним запросом
{
  _id: ObjectId("..."),
  number: "A-1042",
  customer: { id: 42, name: "Иванов" },     // денормализовано для чтения
  items: [
    { sku: "KB-01", qty: 1, price: 2500 },
    { sku: "MS-07", qty: 2, price: 900 }
  ],
  total: 4300,
  status: "paid"
}

Типичная ошибка — встраивание неограниченно растущего массива: история событий, комментарии, логи. Документ упирается в лимит 16 МБ, а каждое обновление переписывает его целиком. Правило: встраивать то, что имеет естественную границу.

Важно понимать, что гибкая схема не отменяет схему. Она переносит контроль из базы в приложение: валидация, миграции и согласованность полей становятся задачей разработчика.

Практика: проектирование и эксплуатация

1. Проектируйте от запросов, а не от сущностей. Документная модель строится вокруг того, какие данные нужны в одном чтении. Одна и та же предметная область даёт разные схемы для разных сценариев использования.

2. Индексы обязательны. Запрос без индекса выполняет перебор коллекции (COLLSCAN). Проверка плана — через explain():

db.orders.find({ "customer.id": 42, status: "paid" }).explain("executionStats");
// stage: "COLLSCAN" — индекса нет, нужен составной индекс
db.orders.createIndex({ "customer.id": 1, status: 1 });

Порядок полей в составном индексе имеет значение: сначала поля, по которым filtering равенством, затем диапазонные. О принципах — в статье про индексы.

3. Транзакции существуют, но не бесплатны. Начиная с версии 4.0 MongoDB поддерживает многодокументные транзакции на replica set, с 4.2 — в шардированных кластерах. Они дают привычные гарантии, но дороже одиночных операций и требуют аккуратности с длительностью.

4. Агрегации — основной инструмент отчётности. Конвейер агрегации ($match, $group, $lookup, $project) заменяет соединения и группировки. $lookup — аналог JOIN, но работает заметно дороже, чем соединение в реляционной СУБД.

5. Обновление вложенного массива — частая боль. Операторы $push, $pull, $set с позиционными фильтрами решают задачу, но делают код менее очевидным, чем обычный SQL.

6. JSON есть и в реляционных СУБД. PostgreSQL поддерживает тип jsonb с индексами по вложенным полям. Если основной объём данных реляционный, а гибкая схема нужна для нескольких сущностей, отдельная документная база может быть избыточна.

Рекомендации:

  1. Не храните в документе то, что растёт без границы. События, комментарии, вложения — в отдельные коллекции со ссылкой.
  2. Денормализуйте осознанно. Скопированное имя клиента в заказе удобно для чтения, но требует обновления при изменении. Решите заранее, допустима ли устаревшая копия.
  3. Введите контроль схемы в приложении. Валидация на уровне схемы или модели предотвращает появление документов с недостающими полями.
  4. Планируйте миграции. Изменение структуры существующих документов — это скрипт обновления по всей коллекции, а не одна команда.
  5. Следите за размером документов. Рост среднего размера — признак того, что встраивание зашло слишком далеко.

Практический сценарий: документ с историей событий перестал обновляться

Контекст: карточка клиента содержала встроенный массив events. Через год работы часть записей перестала принимать новые события, а чтение карточки занимало секунды.

  1. Диагностика. Средний размер документа вырос до 4,2 МБ, у активных клиентов — до 14 МБ. Каждое обновление переписывало документ целиком, что приводило к высокой нагрузке на запись.
  2. Причина. История событий не имела естественной границы: у активных клиентов накопилось десятки тысяч записей. При достижении лимита запись начинала отклоняться.
  3. Действия. (а) События вынесли в отдельную коллекцию customer_events с индексом по клиенту и времени; (б) в карточке оставили только счётчики и последнее событие; (в) написали скрипт переноса с обработкой пачками и проверкой контрольных сумм; (г) добавили метрику на размер документа с порогом.
  4. Результат. Средний размер карточки вернулся к 6 КБ, время обновления снизилось с 300 мс до 8 мс, чтение истории стало постраничным.
  5. Профилактика. В правила код-ревью добавили вопрос: «может ли этот массив расти неограниченно».

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

FAQ

Подходит ли MongoDB, если схема постоянно меняется? Частично. Гибкая схема помогает на этапе исследования, когда структура ещё не определена. Но в зрелом продукте структура стабилизируется, и отсутствие схемы в базе превращается в дополнительную работу по валидации и миграциям.

Чем документная база отличается от реляционной с JSON-колонкой? Документная база строит вокруг документов индексы, агрегации и репликацию. Реляционная СУБД с jsonb даёт ту же вложенность, но в рамках транзакционной модели и знакомых инструментов. Выбор зависит от того, что преобладает в данных.

Нужны ли транзакции в документной базе? Если операция затрагивает один документ, транзакция не нужна — обновления отдельного документа атомарны. Многодокументные транзакции требуются там, где согласованность между записями критична.

Что делать с отчётностью по документным данным? Конвейер агрегации закрывает большинство задач. Для тяжёлой аналитики данные обычно выгружают в хранилище — о различии нагрузок в статье про OLTP и OLAP.

Источники: MongoDB docs (data modeling, transactions), PostgreSQL docs (JSON types) — ссылки в шапке.