Принципы моделирования документов CouchDB


120

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

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

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

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

  • Почта
  • пользователь
  • Комментарий
  • Тег
  • отрывок

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

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

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

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


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, чтобы использовать их в моделях комментариев, то же самое с пользователями и тегами.

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


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"
                    ...
                }
            }
        }
    }
}

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

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

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


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
    }
}

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

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

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


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

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

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

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

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

  1. Иерархические данные в CouchDB
  2. CouchDB Вики
  3. Диван - приложение CouchDB
  4. CouchDB - полное руководство
  5. Скринкаст PeepCode CouchDB
  6. CouchRest
  7. CouchDB README

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


2
вау, вы написали здесь целое эссе ... :-)
Ээро

8
эй, это хороший вопрос
elmarco

Ответы:


26

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

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

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

{"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', на который есть ссылка в объекте 'value', либо, если он отсутствует в объекте 'value', он будет использовать «_id» документа, из которого была создана строка (в данном случае - «почтовый» документ). Обратите внимание, что эти результаты будут включать поле id, указывающее на исходный документ, из которого была произведена передача. Я оставил это место для удобства чтения.

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

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

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

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

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

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


2
Не уверен, что это помогло Лэнсу, но я знаю одно; это определенно мне очень помогло! Это круто!
Марк

17

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

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

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


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

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

16

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

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

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

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

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

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

3
Спасибо, что немного пообщались со мной. У меня возникла идея «включить все данные, необходимые для отображения страницы, касающейся рассматриваемого элемента», но это все еще очень сложно реализовать. "Страница" может быть страницей комментариев, страницей пользователей, страницей сообщений или страницей комментариев и сообщений и т. Д. Как бы вы тогда разделили их в основном? Вы также можете показать свой Контракт пользователям. Я получаю документы, похожие на форму, поэтому есть смысл хранить их отдельно.
Лэнс Поллард,

6

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

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

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

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

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

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

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

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


Очень полезно. Спасибо
Ray Suelzer
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.