Статьи

Прохождение моделирования данных MongoDB

Прохождение: Моделирование данных MongoDB 5 месяцев назад

Сообщение на прошлой неделе о MongoDB Map / Reduce  было довольно хорошо получено, поэтому, похоже, необходимо еще немного обсудить детали, связанные с развертыванием MongoDB в реальном мире. Я подумал, что мы попробуем сделать еще пару постов и подробно рассмотрим, как мы используем MongoDB на Fiesta .

гибкость

Одной из самых рекламируемых возможностей MongoDB является ее гибкость. Я лично подчеркивал гибкость в бесчисленных разговорах, представляющих MongoDB технической аудитории. Гибкость, однако, является обоюдоострым мечом; Большая гибкость означает больше вариантов выбора при моделировании данных (это напоминает мне дзен Python: « Должен быть один — и желательно только один — очевидный способ сделать это »). Тем не менее, мне нравится гибкость, которую обеспечивает MongoDB, просто важно рассмотреть некоторые передовые практики, прежде чем выбирать модель данных.

Эта проблема

В этом посте мы рассмотрим, как мы смоделировали списки рассылки и людей, которые к ним относятся. Вот требования:

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

Эти требования, очевидно, несколько упрощены, но их достаточно, чтобы выразить механику ядра, на котором основана Fiesta.

0-Код

Давайте рассмотрим, как выглядит наша модель данных, если мы никогда ничего не встраиваем — мы назовем это  стратегией 0-вложения .

У нас есть Люди , у которых есть имя и пароль:

{
  _id: PERSON_ID,
  name: "Mike Dirolf"
  pw: "Some Hashed Password"
}

У нас есть отдельная коллекция адресов , где каждый адрес содержит ссылку на одного человека:

{
  _id: ADDRESS_ID,
  person: PERSON_ID,
  address: "mike@corp.fiesta.cc"
}

У нас есть группы , каждая из которых в основном является просто идентификатором (в IRL есть еще несколько специфичных для группы метаданных, которые также будут здесь, но мы собираемся игнорировать их, чтобы сосредоточиться на отношениях):

{
  _id: GROUP_ID
}

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

{
  _id: MEMBERSHIP_ID,
  person: PERSON_ID,
  group: GROUP_ID,
  address: ADDRESS_ID,
  group_name: "family"
}

Эту модель данных легко спроектировать, легко обдумать и легко обслуживать. Тем не менее, мы в основном моделируем данные, как в РСУБД; мы не используем документно-ориентированный подход MongoDB. Например, давайте посмотрим, как мы можем получить адреса других членов группы, учитывая один входящий адрес и имя группы (это очень распространенный запрос для Fiesta):

  1. Запросите коллекцию адресов, чтобы получить идентификатор соответствующего лица.
  2. Запросите членство в коллекции с идентификатором человека из шага 1 и именем группы, чтобы получить идентификатор группы.
  3. Снова запросите коллекцию Memberships, чтобы получить все членства с идентификатором группы из шага 2.
  4. Запросите коллекцию адресов, чтобы получить адрес, который будет использоваться для каждого из участников на шаге 3.

Все становится немного сложнее :).

Вставить все

Стратегию, которую используют многие новички при моделировании своих данных, мы будем называть  стратегией внедрения всего . Чтобы использовать эту стратегию для Fiesta, мы бы взяли все членства в группе и внедрили их непосредственно в документ группы. Мы также встраиваем метаданные адресов и лиц непосредственно в каждое членство:

{
  _id: GROUP_ID,
  memberships: [{
    address: "mike@corp.fiesta.cc",
    name: "Mike Dirolf",
    pw: "Some Hashed Password",
    person_addresses = ["mike@corp.fiesta.cc", "mike@dirolf.com", ...],
    group_name: "family"
  }, ...]
}

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

  1. Запросите коллекцию Groups для группы, содержащей членство, адрес которого указан в person_addresses и group_name совпадает.
  2. Переберите получившийся документ, чтобы получить другие адреса участников.

Это так просто, как только может. Но что, если мы хотим изменить имя или пароль человека? Мы должны были бы изменить это в каждом встроенном членстве. То же самое касается добавления нового person_address или удаления существующего. Это подчеркивает характеристики модели embed everything: она может быть полезна для выполнения одного конкретного запроса (потому что мы в основном выполняем предварительное присоединение), но может стать кошмаром для долгосрочной ремонтопригодности. Я очень рекомендую против такого подхода в целом.

Вставить тривиальные дела

Подход, который мы использовали в Fiesta, и подход, который я чаще всего рекомендую, состоит в том, чтобы начать с размышления о модели 0-embed. После того, как вы выяснили эту модель, вы можете выбрать простые случаи, когда встраивание имеет смысл. В большинстве случаев эти случаи имеют тенденцию быть отношениями «один ко многим».

Например, каждый из наших адресов принадлежит одному пользователю (и на него также ссылаются участники). Адреса также вряд ли будут меняться очень часто. Давайте встроим их в массив, чтобы сохранить некоторые запросы и синхронизировать нашу модель данных с нашей ментальной моделью человека.

Каждое членство связано с одним человеком и одной группой, поэтому мы могли бы представить, что они встроены либо в модель человека, либо в группу. В таких случаях важно учитывать как шаблоны доступа к данным, так и величину взаимосвязей. Мы ожидаем, что у людей будет не более 1000 членств в группах, а у групп — не более 1000 членств, поэтому величина не говорит нам о многом. Однако наш шаблон доступа — когда мы отображаем панель управления Fiesta, нам нужен доступ ко всем членствам человека. Чтобы упростить этот запрос, давайте встроим членство в модель Person. Это также имеет то преимущество, что все адреса персоны хранятся в модели Person (поскольку на них ссылаются как на самом высоком уровне, так и в рамках членства). Если адрес должен быть удален или изменен,мы можем сделать все это в одном месте.

Вот как все выглядит сейчас (это модель Person — единственной другой моделью является Group, которая идентична случаю 0-embed):

{
  _id: PERSON_ID,
  name: "Mike Dirolf",
  pw: "Some Hashed Password",
  addresses: ["mike@corp.fiesta.cc", "mike@dirolf.com", ...],
  memberships: [{
    address: "mike@corp.fiesta.cc",
    group_name: "family",
    group: GROUP_ID
  }, ...]
}

Запрос, который мы обсуждали, теперь выглядит так:

  1. Запрос на пользователя с соответствующим адресом и встроенным членством с правильным именем группы.
  2. Используйте идентификатор группы во встроенном членстве, начиная с шага 1, чтобы запросить других людей, имеющих членство в этой группе, — получить адреса непосредственно из их встроенного членства.

Это по-прежнему почти так же просто, как и в случае встраивания, но наша модель данных намного чище и проще в обслуживании. Надеемся, что это пошаговое руководство было полезным — если у вас есть какие-либо вопросы, дайте нам знать!