Перейти к основному содержимому

MongoDB — проектирование документной схемы

Разработчику Архитектору Аналитику

Материал о проектировании документных схем в MongoDB. Базовый курс — MongoDB; синтаксис — справочник.


С чего начать

Запросы раньше ER-диаграммы
Зафиксируйте шаблоны доступа: топ-5 запросов по частоте, что читается вместе, что обновляется вместе. Документ проектируют так, чтобы горячий запрос укладывался в один find или короткий pipeline. Поля, не участвующие в этих запросах, выносят в другую коллекцию.

Ограничения, которые задают рамки:

  • документ ≤ 16 МБ;
  • чтение/запись идут целым документом (WiredTiger переписывает документ при изменении поля);
  • атомарность по умолчанию — на уровне одного документа;
  • индекс по массиву → multikey (отдельная запись индекса на элемент).

Кардинальность связей

СвязьВопросыТипичное решение
1:1Всегда читаются вместе?Вложение или одна коллекция
1:NN небольшой и стабильный?Массив в родителе
1:N большойКомментарии, события, логиОтдельная коллекция + parentId, bucket
N:MДрузья, подписки, ролиКоллекция рёбер { userId, targetId }, не массивы id в профиле
1 : millionsЛента у знаменитостиOutlier pattern + overflow

Для N:M массив followers: [ObjectId, …] в документе пользователя ломается при росте: 16 МБ, дорогие $push/$pull, hot document. Коллекция follows с индексами { followerId: 1 } и { followeeId: 1 } масштабируется; «список подписчиков» — find с лимитом или агрегация.


Нормализация и денормализация

Нормализация — несколько коллекций, связь через _id, при необходимости $lookup:

  • плюс: одно место правды, дешевле обновлять каноническое поле;
  • минус: несколько round-trip или тяжёлый $lookup на hot path.

Денормализация — копии полей в документе-потребителе:

  • плюс: один запрос на экран;
  • минус: при смене канона нужно обновить много документов (batch, Change Stream, eventual consistency).

Пример «студент → занятия» (упрощённо):

  1. Три коллекции (students, classes, studentClasses с массивом id) — три запроса на экран расписания.
  2. Массив id в студенте — два запроса (student + classes.find({ _id: { $in: … } })).
  3. Полная денормализация — вложенные объекты занятий в student.classes[], один запрос; синхронизация при смене аудитории затрагивает всех студентов.

Выбор зависит от соотношения чтение/запись и допустимой задержки согласованности копий.


Именованные шаблоны (patterns)

Краткий каталог. Развёрнутая таблица — в курсе MongoDB.

Bucket (ведро)

Поток метрик или событий группируют по интервалу (час, сутки) в один документ:

{
sensorId: "s-42",
bucketStart: ISODate("2025-05-30T14:00:00Z"),
bucketEnd: ISODate("2025-05-30T15:00:00Z"),
readings: [
{ t: ISODate("2025-05-30T14:01:00Z"), v: 23.1 },
{ t: ISODate("2025-05-30T14:02:00Z"), v: 23.4 }
]
}

С MongoDB 5.0+ для чистых временных рядов рассмотрите time series collection — отдельный тип с оптимизацией хранения.

Extended reference

Заказ хранит копии полей, нужных UI, без join при каждом открытии:

{
_id: ObjectId("..."),
customerId: ObjectId("..."),
shipTo: { name: "Иванов", city: "Уфа", zip: "450000" },
lines: [ { sku: "A1", qty: 2, title: "Кабель" } ]
}

customerId остаётся для синхронизации; shipTo — снимок на момент заказа.

Outlier (выброс)

У «тяжёлого» документа флаг hasOverflow: true; массив подписчиков/комментариев продолжается в user_overflow с тем же _id. Приложение по флагу делает второй запрос только для outliers.

Subset (подмножество)

В product — последние 10 отзывов; полный архив — product_reviews_archive. Каталог грузит быстро; «все отзывы» — отдельный endpoint.

Computed и Approximation

  • Computed: orderStats.totalSpent пересчитывается cron/триггером, а не aggregate на каждом профиле.
  • Approximation: счётчик просмотров +100 раз вместо +1 — меньше write contention.

Tree (материализованный путь)

Каталог: ancestors: ["Electronics", "Components", "Storage", "HDD"] + category: "HDD" для прямых детей; multikey-индекс по ancestors.


Управление схемой во времени

Schema-on-read не отменяет дисциплины:

  1. Версия документа — поле schemaVersion: 2; код читает обе версии или мигрирует лениво при записи.
  2. Batch migrationfind({ schemaVersion: { $lt: 2 } }).limit(1000) + update в фоне; мониторинг прогресса.
  3. JSON Schema validator на коллекции — новые поля только с $jsonSchema / collMod:
db.createCollection("orders", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["customerId", "status"],
properties: {
status: { enum: ["new", "paid", "shipped"] }
}
}
},
validationLevel: "moderate",
validationAction: "error"
});

moderate — проверка только обновлений существующих полей; старые документы без новых полей не ломают insert.


Согласованность без транзакций

  • Один документ — атомарный $set / $inc.
  • Два документа — идempotent upsert + compensating action, или Change Stream + worker; multi-doc transaction — когда иначе нельзя.
  • Read concern / write concernmajority для «не потерять» запись при failover; см. §10 справочника.

Когда MongoDB — слабый выбор

  • доминируют SQL-отчёты с десятками join на operational кластере;
  • каждая операция требует строгих FK между многими коллекциями;
  • данные естественно табличны и редко меняют форму (чистый OLTP в 3NF).

Подробнее — раздел в курсе.


Связанные материалы

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").