Моделирование отношений между CouchDB между документами?

Я пытаюсь моделировать довольно простые отношения в CouchDB, и у меня возникли проблемы с определением наилучшего способа выполнить это. Я бы хотел, чтобы пользователи могли создавать списки объектов видеоигр. У меня есть документы видеоигр, которые хранятся в БД уже с "type":"game". Я хотел бы иметь возможность запросить идентификатор объекта списка (через представление) и вернуть метаданные списка (название, дату создания и т.д.) И части игрового документа (например, название и дата выпуска). Кроме того, я хотел бы иметь возможность добавлять/удалять игры в/из списков, не загружая весь документ списка и не отправляя его обратно (так что это означает, что я не могу просто сохранить информацию о игре в документе списка), как я в конечном итоге, нравится поддерживать нескольких пользователей, вносящих вклад в один и тот же список, и я не хочу вводить конфликты.

После прочтения вики CouchDB в EntityRelationships, я решил, что настройка документов отношений может быть лучшим решением.

Игра:

{
    "_id": "2600emu",
    "type": "game"
}

Список:

{
    "_id": 123,
    "title": "Emulators",
    "user_id": "dstaley",
    "type": "list"
}

Связь с игровым списком:

{
    "_id": "98765456789876543",
    "type": "relationship",
    "list_id": 123,
    "game_id": "2600emu"
}

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

Ответ 1

Отличный вопрос. Вы определяете несколько очень важных причин, по которым использование "нормализованной" модели данных (разные типы документов со ссылками) является оптимальной моделью:

  • У вас есть отношение "многие ко многим" между пользователями < == > lists < == > games.
  • Отношения "один ко многим" легко представить в одном документе, который использует контейнер для части "много", но они становятся большими, и у вас могут быть конфликты concurrency.
  • Расширение модели с одним документом для хранения отношения "многие ко многим" несостоятельно.
  • В целом, неизменность документа отлично подходит для параллельных систем. В CouchDB вы делаете это точно так же, как вы отметили, сохраняя документы "write-once", которые представляют собой край в вашем графике, а затем используют вторичные индексы для восстановления частей ссылок, которые вы хотите, и для получения требуемой информации в одном Запрос запроса API.

Вы также правы, что решение здесь - это "соединение на стороне карты" (для заимствования из сообщества hadoop). В основном вы хотите использовать разные строки на выходе карты для представления разных частей информации. Затем вы можете использовать запрос диапазона (startkey/endkey), чтобы запросить только ту часть результата карты, которая вам нужна, и, вуаля, ваше материализованное представление таблицы "join". Тем не менее, одна часть головоломки, которую вы не нашли в документации, такова:

http://wiki.apache.org/couchdb/Introduction_to_CouchDB_views#Linked_documents

Первая строка:

"Если вы испускаете значение объекта, которое имеет {'_id': XXX}, то include_docs = true будет извлекать документ с идентификатором XXX, а не документом, который был обработан для испускания пары ключ/значение."

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

http://wiki.apache.org/couchdb/View_collation?action=show&redirect=ViewCollation#Collation_Specification

Итак, чтобы ваши строки просмотра были отсортированы так:

["list_1"], null
["list_1", "game"], {"_id":"game_1234"}
["list_1", "game"], {"_id":"game_5678"}
["list_2"], null
["list_2","game"], {"_id":"game1234"}
["list_3"], null
...

Объединяя это вместе с вашей существующей моделью данных, вот несколько (непроверенных) псевдокодов, которые должны сделать трюк:

function(doc) {
    if (doc.type=="list") {
        //this is the one in the one-to-many
        emit( [doc._id]),);
    }
    else if (doc.type=="relationship") {
        //this is the many in the one-to-many
        //doc.list_id is our foreign key to the list.  We use that as the key
        //doc.game_id is the foreign key to the game.  We use that as the value
        emit( [doc.list_id,'game'],  {'_id': doc.game_id});
    }
}   

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

curl -g 'https://usr:[email protected]/db/_design/design_doc_name/_view/view_name?startkey=["123"]&endkey=["123",{}]&include_docs=true'

Параметр -g указывает curl not glob, что означает, что вам не нужно разыменовывать квадратные скобки и т.д., а параметр include_docs=true будет следовать указателю на внешний ключ, который вы указали с помощью game_id в документе relationship.

Анализ:

  • Вы используете по существу неизменяемые документы для хранения изменений состояния, и вы даете базе данных вычислить общее состояние для вас. Это прекрасная модель в масштабе и один из наших самых успешных моделей.
  • Очень эффективен для добавления или удаления списков.
  • Отличные масштабирующие свойства при высоком уровне concurrency
  • В Cloudant (и CouchDB v2.0) у нас еще нет согласованности "read-your-write" для вторичных индексов. В списке приоритетов он высок, но есть потенциальные угловые случаи, когда в сценариях сбоев или при высокой нагрузке вы можете не видеть непосредственной согласованности между первичными и вторичными индексами. Короче говоря, кворум используется для первичных индексов, но кворум не является жизнеспособной моделью для вторичных индексов, поэтому разрабатывается другая стратегия согласования.