Схема JSON с динамическим ключевым полем в MongoDB

Хотите иметь поддержку i18n для объектов, хранящихся в коллекции mongodb

в настоящее время наша схема похожа:

{
  _id: "id"
  name: "name"
  localization: [{
    lan: "en-US",
    name: "name_in_english"
  }, {
    lan: "zh-TW",
    name: "name_in_traditional_chinese"
  }]
}

но я думаю, что поле "lan" уникально, могу ли я просто использовать это поле в качестве ключа, поэтому структура была бы

{
  _id: "id"
  name: "name"
  localization: {
    "en-US": "name_in_english",
    "zh-TW": "name_in_traditional_chinese"
  }
}

который был бы более аккуратным и легким для анализа (просто локализация [язык] получит значение, которое я хочу для определенного языка).

Но тогда возникает вопрос: является ли это хорошей практикой при хранении данных в MongoDB? И как передать проверку json-схемы?

Ответ 1

Неправильно использовать значения как ключи. Коды языков являются значениями, и, как вы говорите, вы не можете проверить их по схеме. Это делает невозможным опрос. Например, вы не можете понять, есть ли у вас языковой перевод для "nl-NL", поскольку вы не можете сравнивать с клавишами, и нет возможности легко индексировать это. Вы должны всегда иметь описательные ключи.

Однако, как вы говорите, наличие языков в качестве ключей значительно облегчает извлечение данных, поскольку вы можете просто получить к нему доступ через ['nl-NL'] (или независимо от вашего синтаксиса языка).

Я бы предложил альтернативную схему:

{
    your_id: "id_for_name"
    lan: "en-US",
    name: "name_in_english"
}
{
    your_id: "id_for_name"
    lan: "zh-TW",
    name: "name_in_traditional_chinese"
}

Теперь вы можете:

  • установить индекс на { your_id: 1, lan: 1 } для быстрого поиска
  • запрос для каждого перевода индивидуально и просто получить этот перевод:
    db.so.find( { your_id: "id_for_name", lan: 'en-US' } )
  • запрос для всех версий для каждого идентификатора с использованием этого же индекса:
    db.so.find( { your_id: "id_for_name" } )
  • а также гораздо проще обновить перевод для определенного языка:

    db.so.update(
        { your_id: "id_for_name", lan: 'en-US' }, 
        { $set: { name: "ooga" } } 
    )
    

Ни одна из этих точек не возможна с предложенными вами схемами.

Ответ 2

Очевидно, что второй пример схемы намного лучше для вашей задачи (конечно, если поле lan уникально, как вы упомянули, это кажется верным и мне).

Получение элемента из dictionary/associated array/mapping/whatever_it_is_called_in_your_language намного дешевле, чем сканирование всего массива значений (и в этом случае он также очень эффективен с точки зрения размера хранилища (помните, что все поля хранятся в MongoDB as-is), поэтому каждая запись содержит полное имя ключа для json-поля, а не его представление или индекс или что-то еще).

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

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

Ответ 3

Существует случай, когда объект обязательно лучше, чем массив: поддержка upserts в наборе. Например, если вы хотите обновить элемент, имеющий name 'item1', чтобы иметь val 100, или вставить такой элемент, если он не существует, все в одной атомной операции. С помощью массива вам нужно будет выполнить одну из двух операций. Учитывая такую ​​схему, как

{ _id: 'some-id', itemSet: [ { name: 'an-item', val: 123 } ] }

у вас будут команды

// Update:
db.coll.update(
  { _id: id, 'itemSet.name': 'item1' },
  { $set: { 'itemSet.$.val': 100 } }
);

// Insert:
db.coll.update(
  { _id: id, 'itemSet.name': { $ne: 'item1' } },
  { $addToSet: { 'itemSet': { name: 'item1', val: 100 } } }
);

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

db.coll.update({
  { _id: id },
  { $set: { 'itemSet.name': 'item1', 'itemSet.val': 100 } }
});

Если это вариант использования, вы должны пойти с объектным подходом. Один из недостатков заключается в том, что запрос на конкретное имя требует сканирования. Если это также необходимо, вы можете добавить отдельный массив специально для индексирования. Это компромисс с MongoDB. Upserts станет

db.coll.update({
  { _id: id },
  { 
    $set: { 'itemSet.name': 'item1', 'itemSet.val': 100 },
    $addToSet: { itemNames: 'item1' } 
  }
});

и тогда запрос будет просто

db.coll.find({ itemNames: 'item1' })

(Примечание: оператор позиционирования $ не поддерживает массивы upserts.)