Learning Platform
Глоссарий Troubleshooting
Урок 19.01 · 18 мин
Начальный
nosqldocument-databasemongodbembedding

Document-моделирование: embedding и referencing

Все предыдущие модули курса жили в реляционном мире: таблицы, строки, нормализация, JOIN. Но реляционная модель — не единственная. Существует целое семейство NoSQL-хранилищ, и они требуют другого мышления о данных. Этот модуль — про моделирование для NoSQL, и начнём мы с самого популярного его вида: документных баз (document databases), флагман которых — MongoDB.

Сразу развеем опасное заблуждение, с которым новички часто приходят в NoSQL: «NoSQL не требует моделирования, просто кидаешь JSON». Это неправда — и дорогая. NoSQL требует моделирования не меньше, а больше дисциплины, чем реляционные базы. Просто другой дисциплины. И первое ключевое решение document-моделирования — выбор между embedding и referencing.


Что такое документ

В реляционной базе данные — это строки в таблицах с фиксированной схемой. В документной базе единица данных — документ: структура в формате JSON (внутри MongoDB — бинарный вариант, BSON). Документ может содержать вложенные объекты и массивы, и у документов одной коллекции не обязательно одинаковый набор полей.

Вот как выглядит документ заказа:

{
  "_id": "ORD-5501",
  "order_date": "2026-05-20",
  "status": "paid",
  "amount": 4500,
  "items": [
    { "product": "Клавиатура", "qty": 1, "price": 3000 },
    { "product": "Мышь",       "qty": 1, "price": 1500 }
  ]
}

Обратите внимание: позиции заказа (items) лежат внутри документа заказа, массивом. В реляционной модели это были бы отдельная таблица order_items и JOIN. Документная модель позволяет хранить связанные данные вместе, вложенными. Позволяет — но не обязывает. И вот тут начинается главный выбор.


Два способа связать данные

Когда одна сущность связана с другой (заказ и его позиции, клиент и его адреса, статья и её комментарии), document-модель предлагает два пути.

Embedding (вложение) — поместить связанные данные внутрь родительского документа. Позиции заказа лежат массивом прямо в документе заказа, как в примере выше. Одна сущность физически содержит другую.

Referencing (ссылка) — хранить связанные данные в отдельной коллекции, а в родительском документе держать только ссылку (_id связанного документа). Это похоже на foreign key из реляционного мира.

// Referencing: заказ ссылается на товары по _id
{
  "_id": "ORD-5501",
  "status": "paid",
  "items": [
    { "product_id": "SKU-100", "qty": 1 },
    { "product_id": "SKU-200", "qty": 1 }
  ]
}
// Документы товаров живут в отдельной коллекции products:
{ "_id": "SKU-100", "name": "Клавиатура", "price": 3000, "stock": 42 }
{ "_id": "SKU-200", "name": "Мышь",       "price": 1500, "stock": 17 }
Embedding против referencing
EmbeddingСвязанные данные вложены в родительский документ. Одно чтение достаёт всё
альтернатива
ReferencingСвязанные данные в отдельной коллекции, в родителе только ссылка по _id

Выбор между ними — не вкусовщина. Это центральное проектное решение document-моделирования, и от него зависит, будет база быстрой или медленной.


Почему embedding часто выигрывает: физика чтения

Чтобы выбирать осознанно, нужно понять физику. Главная операция, под которую оптимизируют документную базу, — достать документ по ключу.

При embedding связанные данные лежат внутри документа. Запрос «дай заказ ORD-5501 со всеми позициями» — это одно чтение одного документа. База нашла документ по _id и вернула его целиком, с вложенным массивом позиций. Один поход к диску.

При referencing данные разнесены. Тот же запрос — это: прочитать документ заказа, увидеть в нём ссылки на товары, затем сходить за каждым товаром в коллекцию products. Несколько отдельных чтений. У документных баз нет дешёвого серверного JOIN, как в SQL, — связывание ссылок обычно ложится на приложение или на отдельную операцию.

Стоимость чтения: embedding против referencing
Запрос заказаНужен заказ со всеми его позициями
embedding: 1 чтение
Готовый документЗаказ с вложенными позициями возвращается одним чтением
referencing: N+1 чтений
Сборка из коллекцийСначала заказ, потом отдельное чтение за каждым товаром

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


Когда нужен referencing

Если embedding так хорош для чтения — почему не вкладывать всё всегда? Потому что у embedding есть пределы, и в нескольких ситуациях referencing необходим.

Документ растёт без ограничений. Документная база ограничивает размер одного документа (в MongoDB — 16 МБ). Если вкладывать в документ массив, который растёт бесконечно (все события пользователя за годы, все комментарии вирусного поста), документ рано или поздно упрётся в лимит. Безграничные «многие» нужно referencing.

Данные меняются независимо. Если вложенные данные часто обновляются сами по себе, embedding мешает. Цена товара, вложенная копией в тысячи заказов, при изменении потребовала бы обновить тысячи документов. В отдельной коллекции products цена правится в одном месте. Часто и независимо изменяемое — referencing.

Связь many-to-many. Студенты и курсы: студент записан на много курсов, курс содержит много студентов. Вложить одно в другое нельзя без огромного дублирования. Many-to-many — referencing (обычно с массивом ссылок).

К данным нужен независимый доступ. Если товар надо запрашивать сам по себе — каталог, поиск, остатки, — а не только в контексте заказа, ему нужна своя коллекция. Сущность, к которой обращаются независимо, — referencing.

СитуацияРешение
Данные читаются вместе с родителемEmbedding
Связь «содержит», 1:N ограниченной мощностиEmbedding
Безграничный рост вложенного массиваReferencing
Вложенное часто меняется независимоReferencing
Связь many-to-manyReferencing
Нужен независимый доступ к сущностиReferencing
TIP

Практическое правило для выбора. Задайте три вопроса: (1) Эти данные читаются вместе или по отдельности? (2) Сколько их — ограниченное число или растёт безгранично? (3) Меняются вместе с родителем или своей жизнью? Вместе + ограниченно + меняются с родителем -> embedding. Иначе -> referencing. Большинство реальных случаев решается этими тремя вопросами.

Document-хранилища в DE-контексте — когда MongoDB vs реляционная БД

Денормализация как норма

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

Пример. В документе заказа удобно хранить имя клиента прямо в заказе — даже если у клиента есть отдельная коллекция. Да, имя продублировано. Но запрос «покажи заказ с именем покупателя» становится одним чтением, без обращения к коллекции клиентов.

{
  "_id": "ORD-5501",
  "customer_id": "CUST-001",
  "customer_name": "Анна Петрова",
  "status": "paid"
}

Это сознательная денормализация ради скорости чтения — та же идея, что OBT в аналитике из прошлого модуля. Цена та же: если Анна сменит имя, его придётся обновить во всех заказах (либо смириться, что в старых заказах останется имя «как было на момент заказа» — что часто даже правильно). Document-моделирование постоянно делает этот выбор: дублировать ради быстрого чтения или ссылаться ради лёгкого обновления.

Вот ключевая смена мышления всего модуля. Реляционная модель проектируется от структуры данных и связей — а запросы потом приспосабливаются к схеме. NoSQL, включая document, проектируется от запросов — сначала понимаем, как приложение будет читать данные, и под это строим структуру. В реляционном мире нормализация первична, доступ вторичен. В document-мире access pattern первичен, структура подстраивается под него. Эту идею «query-first» мы будем развивать весь модуль.


Попробуй сам

Возьмите блог с сущностями: пользователь, статья, комментарий, тег.

  1. Для пары «статья и её комментарии» решите: embedding или referencing? Пройдите три вопроса из правила выше. Учтите, что у вирусной статьи комментариев может быть очень много.
  2. Для пары «статья и её теги» решите то же самое. Теги — это many-to-many (один тег у многих статей).
  3. Спроектируйте документ статьи в JSON: что вложите, на что сошлётесь.
  4. Решите, стоит ли дублировать имя автора в документ статьи. Какой запрос это ускорит и какой ценой при смене имени автора?

В следующем уроке перейдём к key-value базам, где моделирование сводится к дизайну структуры ключа.


Проверка знанийKnowledge check
В чём разница между embedding и referencing в document-моделировании, по каким признакам выбирать между ними, и почему это требует "query-first" мышления вместо реляционной нормализации?
ОтветAnswer
Embedding — это вложение связанных данных внутрь родительского документа (позиции заказа массивом прямо в документе заказа). Referencing — хранение связанных данных в отдельной коллекции, а в родителе только ссылка по _id, похоже на foreign key. Разница в физике чтения: при embedding запрос "заказ со всеми позициями" — это одно чтение одного документа, при referencing — несколько отдельных чтений, потому что у документных баз нет дешёвого серверного JOIN. Главный принцип: данные, которые читаются вместе, должны храниться вместе. Embedding выбирают, когда данные читаются вместе с родителем, связь "содержит" и 1:N ограниченной мощности. Referencing необходим, когда вложенный массив растёт безгранично (упрётся в лимит размера документа), когда данные часто меняются независимо (иначе пришлось бы обновлять копию во множестве документов), при связи many-to-many и когда к сущности нужен независимый доступ. Это требует query-first мышления: реляционная модель проектируется от структуры данных и связей через нормализацию, а запросы приспосабливаются к схеме; document-модель проектируется от запросов — сначала понимаем, как приложение читает данные, и под это строим структуру. В document-мире дублирование данных часто нормально и желательно (денормализация ради быстрого чтения), тогда как в реляционном оно аномалия.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. В чём разница между embedding и referencing в document-моделировании?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5