Получить доступ

Документоориентированные БД (MongoDB)

Модель документа, embedding против referencing, индексы, aggregation pipeline, атомарность документа и шардирование.

Модель документа

Документная БД хранит данные как самодостаточные документы (в MongoDB — BSON, бинарный JSON с типами и датами). Документы группируют в коллекции, схема гибкая: в одной коллекции документы могут отличаться полями.

Гибкость не значит «без схемы»: схема просто живёт в коде и в соглашениях. Без дисциплины коллекция превращается в свалку разнородных документов, которую тяжело поддерживать.

Embedding против referencing

Главное решение моделирования — вкладывать связанные данные в документ (embedding) или хранить ссылки на другие документы (referencing). Embedding даёт чтение агрегата одним запросом и атомарность изменений, но раздувает документ и дублирует данные.

Эмпирика: вкладывайте данные, которые читаются вместе с родителем и принадлежат ему («содержит»), и ссылайтесь на сущности с собственной жизнью, разделяемые многими, или растущие без предела.

Антипаттерн — unbounded array: вкладывать в документ коллекцию, которая растёт неограниченно (например, все комментарии к посту). Документ упрётся в лимит размера и станет дорогим для обновления.

// embedding: адрес читается всегда вместе с пользователем
{ _id: 1, name: "Ann", address: { city: "Riga", zip: "LV-1001" } }

// referencing: заказы живут своей жизнью и растут — ссылка
{ _id: 1, name: "Ann" }
{ _id: 99, userId: 1, total: 4200 }

Индексы

Как и в SQL, без индексов запросы делают полный скан коллекции. Есть single-field, compound (с тем же правилом левого префикса, что у B-tree), multikey (по элементам массива), text и geo индексы.

Индексы ускоряют чтение и фильтрацию, но замедляют запись и едят память — баланс тот же, что в реляционных БД. План запроса смотрят через explain().

Aggregation pipeline

Сложные выборки и аналитику делают через aggregation pipeline — конвейер стадий ($match, $group, $sort, $lookup и др.), где документы текут от стадии к стадии.

$match ставят как можно раньше (аналог раннего WHERE), чтобы сократить поток и задействовать индексы. $lookup умеет джойнить коллекции, но это не дешёвая операция и не замена правильной модели.

db.orders.aggregate([
  { $match: { status: "PAID" } },     // раньше = дешевле
  { $group: { _id: "$userId", total: { $sum: "$amount" } } },
  { $sort: { total: -1 } }
])

Атомарность и транзакции

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

Многодокументные транзакции существуют, но они дороже и медленнее; полагаться на них как в реляционной БД — признак того, что модель документов спроектирована не под задачу.

Шардирование

При горизонтальном масштабировании коллекцию шардируют по shard key. Хороший ключ даёт равномерное распределение и направляет частые запросы в одну партицию; плохой создаёт горячие чанки и заставляет запрос обходить все шарды (scatter-gather).

Монотонно растущий ключ (например, время) — классическая ошибка: вся свежая запись льётся в один шард.

Как отвечать на собеседовании

Сильный ответ: объясните выбор embedding/referencing через «читается вместе/принадлежит» и предел роста, упомяните атомарность документа как способ избежать транзакций, и назовите риск unbounded array и плохого shard key.

Начните с честной оговорки про «schema-less»: схема есть всегда, она просто переезжает из базы в код и соглашения. Гибкость означает, что базой она больше не проверяется, — а значит, дисциплина нужна большая, а не меньшая. Такое начало сразу снимает подозрение, что кандидат выбирает MongoDB «чтобы не думать о схеме».

Ключевой вопрос моделирования формулируйте как выбор с критерием: вкладываем то, что читается вместе с родителем и принадлежит ему, ссылаемся на сущности с собственной жизнью, разделяемые многими или растущие без предела. И назовите антипаттерн unbounded array (все комментарии внутри поста) — документ упирается в лимит размера и дорожает на каждом обновлении.

Про транзакции дайте неочевидную связку: операция над ОДНИМ документом атомарна, поэтому удачный embedding часто позволяет уложить инвариант в один документ и вообще не понадобиться многодокументным транзакциям. Они существуют, но дороже, и постоянная нужда в них — сигнал, что модель спроектирована не под задачу.

Отметьте, что базовые принципы не отменяются: индексы работают по тому же правилу левого префикса, план смотрят через explain(), а $match в pipeline ставят как можно раньше — это тот же ранний WHERE.

Завершите шардированием: хороший shard key даёт равномерное распределение и направляет частые запросы в одну партицию, плохой порождает scatter-gather по всем шардам. И назовите классическую ошибку — монотонно растущий ключ вроде времени, из-за которого вся свежая запись льётся в один шард.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем