Принципы моделирования документов CouchDB - PullRequest
116 голосов
/ 07 октября 2009

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

Как вы разрабатываете или разделяете документы CouchDB?

Взять, к примеру, сообщение в блоге.

Полу "реляционный" способ сделать это - создать несколько объектов:

  • Сообщение
  • Пользователь
  • Комментарий
  • Метка
  • Отрывок

Это имеет большой смысл. Но я пытаюсь использовать couchdb (по всем причинам, что это здорово), чтобы смоделировать то же самое, и это было чрезвычайно сложно.

Большинство постов в блоге дают вам простой пример того, как это сделать. Они в основном делят его одинаково, но говорят, что вы можете добавить «произвольные» свойства к каждому документу, что, безусловно, приятно. Таким образом, у вас будет что-то вроде этого в CouchDB:

  • Пост (с тегами и фрагментами "псевдо" моделей в документе)
  • Комментарий
  • Пользователь

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


<code>post {
    id: 123412804910820
    title: "My Post"
    body: "Lots of Content"
    html: "<p>Lots of Content</p>"
    author: {
        name: "Lance"
        age: "23"
    }
    tags: ["sample", "post"]
    comments {
        comment {
            id: 93930414809
            body: "Interesting Post"
        } 
        comment {
            id: 19018301989
            body: "I agree"
        }
    }
}</code>

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

Но тогда я думаю: «Почему бы просто не поместить весь мой сайт в один документ?»:


<code>site {
    domain: "www.blog.com"
    owner: "me"
    pages {
        page {
            title: "Blog"
            posts {
                post {
                    id: 123412804910820
                    title: "My Post"
                    body: "Lots of Content"
                    html: "<p>Lots of Content</p>"
                    author: {
                        name: "Lance"
                        age: "23"
                    }
                    tags: ["sample", "post"]
                    comments {
                        comment {
                            id: 93930414809
                            body: "Interesting Post"
                        } 
                        comment {
                            id: 19018301989
                            body: "I agree"
                        }
                    }
                }
                post {
                    id: 18091890192984
                    title: "Second Post"
                    ...
                }
            }
        }
    }
}</code>

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

Тогда у меня возникает вопрос: как вы определяете, когда разделить документ на более мелкие документы или когда сделать «ОТНОШЕНИЯ» между документами?

Я думаю, что было бы гораздо более "объектно-ориентированным", и было бы проще сопоставить с объектами значения, если бы они были разделены следующим образом:


<code>posts {
    post {
        id: 123412804910820
        title: "My Post"
        body: "Lots of Content"
        html: "<p>Lots of Content</p>"
        author_id: "Lance1231"
        tags: ["sample", "post"]
    }
}
authors {
    author {
        id: "Lance1231"
        name: "Lance"
        age: "23"
    }
}
comments {
    comment {
        id: "comment1"
        body: "Interesting Post"
        post_id: 123412804910820
    } 
    comment {
        id: "comment2"
        body: "I agree"
        post_id: 123412804910820
    }
}</code>

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

Я прочитал много вещей о том, как / когда использовать реляционные базы данных и базы данных документов, так что это не главная проблема здесь. Меня больше интересует, какое хорошее правило / принцип следует применять при моделировании данных в CouchDB.

Другой пример - с файлами / данными XML. Некоторые XML-данные имеют глубину вложенности более 10 уровней, и я хотел бы визуализировать это, используя тот же клиент (например, Ajax on Rails или Flex), который я бы использовал для рендеринга JSON из ActiveRecord, CouchRest или любого другого Object Relational Mapper. Иногда я получаю огромные XML-файлы, которые представляют собой всю структуру сайта, как показано ниже, и мне нужно сопоставить их с объектами Value для использования в моем приложении Rails, чтобы мне не пришлось писать другой способ сериализации / десериализации данных :


<code><pages>
    <page>
        <subPages>
            <subPage>
                <images>
                    <image>
                        <url/>
                    </image>
                </images>
            </subPage>
        </subPages>
    </page>
</pages></code>

Итак, общие вопросы CouchDB:

  1. Какие правила / принципы вы используете для разделения ваших документов (отношений и т. Д.)?
  2. Можно ли поместить весь сайт в один документ?
  3. Если это так, как вы справляетесь с сериализацией / десериализацией документов с произвольными уровнями глубины (как пример большого json выше или пример xml)?
  4. Или вы не превращаете их в виртуальные организации, вы просто решаете, что "они слишком вложены в объектно-реляционную карту, поэтому я просто получу доступ к ним с помощью необработанных методов XML / JSON"?

Большое спасибо за вашу помощь, мне было трудно сказать, как с этого момента делиться вашими данными с CouchDB: «Вот как я должен это делать с этого момента». Я надеюсь скоро туда добраться.

Я изучил следующие сайты / проекты.

  1. Иерархические данные в CouchDB
  2. CouchDB Wiki
  3. Диван - CouchDB App
  4. CouchDB Полное руководство
  5. PeepCode CouchDB Screencast
  6. CouchRest
  7. CouchDB README

... но они все еще не ответили на этот вопрос.

Ответы [ 4 ]

25 голосов
/ 30 сентября 2011

Уже было несколько хороших ответов на этот вопрос, но я хотел добавить некоторые более поздние функции CouchDB в набор опций для работы с исходной ситуацией, описанной viatropos.

Ключевым моментом, по которому можно разделить документы, могут быть конфликты (как упоминалось ранее). Вы никогда не должны хранить массово «запутанные» документы в одном документе, так как вы получите единый путь редакции для совершенно не связанных обновлений (например, добавление комментариев, добавляющее редакцию ко всему документу сайта). Поначалу управление отношениями или связями между различными более мелкими документами может сбивать с толку, но CouchDB предоставляет несколько вариантов объединения разрозненных фрагментов в отдельные ответы.

Первый большой - это сопоставление представлений. Когда вы отправляете пары ключ / значение в результаты запроса map / Reduce, ключи сортируются на основе параметров сортировки UTF-8 («a» предшествует «b»). Вы также можете вывести сложные ключи из вашей карты / уменьшить как массивы JSON: ["a", "b", "c"]. Это позволит вам включить «дерево», построенное из ключей массива. Используя приведенный выше пример, мы можем вывести post_id, затем тип объекта, на который мы ссылаемся, затем его идентификатор (при необходимости). Если затем мы выводим идентификатор ссылочного документа в объект в возвращаемом значении, мы можем использовать параметр запроса include_docs, чтобы включить эти документы в вывод map / уменьшить:

{"rows":[
  {"key":["123412804910820", "post"], "value":null},
  {"key":["123412804910820", "author", "Lance1231"], "value":{"_id":"Lance1231"}},
  {"key":["123412804910820", "comment", "comment1"], "value":{"_id":"comment1"}},
  {"key":["123412804910820", "comment", "comment2"], "value":{"_id":"comment2"}}
]}

Запрос этого же представления с помощью «? Include_docs = true» добавит ключ «doc», который будет использовать либо «_id», на который ссылается объект «значение», либо, если он отсутствует в объекте «значение», он будет использовать '_id' документа, из которого была выдана строка (в данном случае документ 'post'). Обратите внимание, что эти результаты будут включать поле 'id', ссылающееся на исходный документ, из которого был сделан emit. Я оставил это для пространства и читабельности.

Затем мы можем использовать параметры 'start_key' и 'end_key', чтобы отфильтровать результаты до данных одного сообщения:

?start_key=["123412804910820"]&end_key=["123412804910820", {}, {}]
Или даже специально извлечь список для определенного типа:
?start_key=["123412804910820", "comment"]&end_key=["123412804910820", "comment", {}]
Возможны эти комбинации параметров запроса потому что пустой объект ("{}") всегда находится внизу сопоставления, а null или "" всегда находятся вверху.

Вторым полезным дополнением от CouchDB в этих ситуациях является функция _list. Это позволит вам запустить приведенные выше результаты через какую-то систему шаблонов (если вам нужен HTML, XML, CSV или что-то еще) или вывести унифицированную структуру JSON, если вы хотите иметь возможность запрашивать весь контент поста (включая данные об авторе и комментариях) с одним запросом и возвращаются в виде единого документа JSON, который соответствует потребностям вашего клиентского / пользовательского интерфейса. Это позволит вам запросить унифицированный выходной документ поста следующим образом:

/db/_design/app/_list/posts/unified??start_key=["123412804910820"]&end_key=["123412804910820", {}, {}]&include_docs=true
Ваша функция _list (в данном случае с именем «унифицированный») будет принимать результаты просмотра / уменьшения карты (в данном случае с именем «posts») запустите их через функцию JavaScript, которая отправит ответ HTTP в нужном вам типе контента (JSON, HTML и т. д.).

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

Надеюсь, это поможет.

15 голосов
/ 18 февраля 2011

Я знаю, что это старый вопрос, но я натолкнулся на него, пытаясь выяснить лучший подход к этой точно такой же проблеме. Кристофер Ленц написал хороший пост в блоге о методах моделирования "соединений" в CouchDB . Один из моих выводов был: «Единственный способ разрешить бесконфликтное добавление связанных данных - это поместить эти связанные данные в отдельные документы». Итак, для простоты вы хотите склониться к «денормализации». Но вы столкнетесь с естественным барьером из-за противоречивых записей при определенных обстоятельствах.

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

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

15 голосов
/ 07 октября 2009

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

  1. Какие правила / принципы вы используете для разделения ваших документов (отношения и т. Д.)?

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

  1. Можно ли поместить весь сайт в один документ?

Нет, это было бы глупо, потому что:

  • вам придется читать и писать весь сайт (документ) при каждом обновлении, а это очень неэффективно;
  • вы не получите никакой выгоды от кэширования представления.
5 голосов
/ 17 сентября 2011

Я думаю, что ответ Джейка является одним из наиболее важных аспектов работы с CouchDB, который может помочь вам принять решение об определении объема: конфликты.

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

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

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

Затем, чтобы написать представления, которые возвращают вам комментарии к сообщению, вы должны передать postID и затем выдать все комментарии, которые ссылаются на этот родительский идентификатор сообщения, отсортированные в некотором логическом порядке. Возможно, вы даже передаете что-то вроде [postID, byUsername] в качестве ключа к представлению «комментарии», чтобы указать родительский пост и то, как вы хотите, чтобы результаты были отсортированы или что-то в этом духе.

MongoDB обрабатывает документы немного по-другому, позволяя строить индексы на подэлементах документа, поэтому вы можете увидеть тот же вопрос в списке рассылки MongoDB, и кто-то скажет: «просто сделайте комментарии свойством родительского поста» .

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

При этом, если ваши подэлементы в либо DB будут огромными (скажем, 10 тысяч комментариев), я считаю, что оба лагеря рекомендуют создавать эти отдельные элементы; Я наверняка видел, что в случае с Mongo существуют некоторые верхние границы того, насколько большим может быть документ и его подэлементы.

...