MongoDB — проектирование документной схемы
Материал о проектировании документных схем в MongoDB. Базовый курс — MongoDB; синтаксис — справочник.
С чего начать
Зафиксируйте шаблоны доступа — топ-5 запросов по частоте, что читается вместе, что обновляется вместе. Документ проектируют так, чтобы горячий запрос укладывался в один find или короткий pipeline. Поля, не участвующие в этих запросах, выносят в другую коллекцию.
Ограничения, которые задают рамки:
- документ ≤ 16 МБ;
- чтение/запись идут целым документом (WiredTiger переписывает документ при изменении поля);
- атомарность по умолчанию — на уровне одного документа;
- индекс по массиву → multikey (отдельная запись индекса на элемент).
Кардинальность связей
| Связь | Вопросы | Типичное решение |
|---|---|---|
| 1:1 | Всегда читаются вместе? | Вложение или одна коллекция |
| 1:N | N небольшой и стабильный? | Массив в родителе |
| 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).
Пример "студент → занятия" (упрощённо):
- Три коллекции (
students,classes,studentClassesс массивом id) — три запроса на экран расписания. - Массив id в студенте — два запроса (
student+classes.find({ _id: { $in: … } })). - Полная денормализация — вложенные объекты занятий в
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 не отменяет дисциплины:
- Версия документа — поле
schemaVersion: 2; код читает обе версии или мигрирует лениво при записи. - Batch migration —
find({ schemaVersion: { $lt: 2 } }).limit(1000)+updateв фоне; мониторинг прогресса. - 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 concern —
majorityдля "не потерять" запись при failover; см. §10 справочника.
Когда MongoDB — слабый выбор
- доминируют SQL-отчёты с десятками join на operational кластере;
- каждая операция требует строгих FK между многими коллекциями;
- данные естественно табличны и редко меняют форму (чистый OLTP в 3NF).
Подробнее — раздел в курсе.
Связанные материалы
- Микросервисы и выбор БД — bounded context и пары SQL/NoSQL
- 12 концепций — шардирование — ключ шарда и schema design связаны
- Официально: Data Modeling