Проектирование схемы в хранилище без схемы

Что это такое

Проектирование коллекции документов — это решение о том, какие поля принадлежат одному документу, какие связи встраиваются, а какие задаются ссылками, какие индексы понадобятся запросам и какую форму документам разрешено принимать. Хранилище принимает всё, поэтому проект живёт в читающем коде — и именно его запись останавливает накопление в коллекции десятка вариантов одной и той же записи.

Почему это важно

Хранилище без схемы не убирает схему — оно убирает её принудительное исполнение. Каждое поле, на которое рассчитывает читатель, — принятое решение, которое может измениться без предупреждения. Оставить его неявным значит получить коллекцию, где поле иногда строка, иногда число, иногда отсутствует, а каждый фрагмент кода обрабатывает те случаи, которые встречал. Определить форму и валидировать её на границе куда дешевле, чем исправлять это потом.

Ключевые идеи

Сначала проектируйте под чтение, потом под запись

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

всегда читают целиком      -> один документ со встроенными частями
части запрашивают отдельно -> отдельные коллекции со ссылками

Встраивание и ссылки — решение об ограниченном размере

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

Поля следует валидировать на границе и версионировать

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

Индексы строятся из запросов, а запрос без индекса сканирует

Каждому шаблону запроса, который должна поддерживать коллекция, нужен подходящий индекс — включая порядок сортировки, который использует запрос. Поскольку запросы пишут на собственном языке хранилища, привычка читать план сохраняется: проверяйте, какой индекс использован и не было ли сканирования коллекции. Индекс, добавленный под запрос, который никто не выполняет, — чистый расход.

Денормализованные копии допустимы, но требуют поддержки

Дублирование значения в документ, чтобы чтение обходилось без второго запроса, — законный приём, и он означает, что каждая запись в источник должна обновлять копии; если эту поддержку забыть, копии расходятся, и замечать это некому. Там, где такая копия есть, её следует называть копией, чтобы поздний читатель знал, что искать путь обновления.

Коллекция должна хранить один вид сущностей

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

Практика

Спроектируйте коллекцию для одной сущности

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

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

Добавьте проверку формы и версию

Возьмите путь записи и добавьте валидацию формы документа плюс поле версии схемы, затем опишите, как читается старый документ.

Готово, когда: некорректная запись отклоняется на границе, а документ, записанный до изменения, всё ещё читается правильно.

Частые ошибки

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

Что читать дальше