Принципи моделювання документів 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"
        }
    }
}

Це виглядає дуже приємно і легко зрозуміти. Я також розумію, як ви можете написати перегляди, які витягували лише коментарі з усіх ваших поштових документів, щоб перетворити їх у моделі коментарів, як і користувачі та теги.

Але тоді я думаю: "чому б просто не помістити весь мій сайт в єдиний документ?":


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 на Rails або Flex), який я мав би віддати JSON з ActiveRecord, CouchRest або будь-якого іншого об'єктивного реляційного Mapper. Іноді я отримую величезні файли XML, які складаються з усієї структури сайту, як-от наведена нижче, і мені потрібно буде відобразити його у Value Objects для використання в моєму додатку Rails, тому мені не доведеться писати інший спосіб серіалізації / десеріалізації даних :


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

Отже, загальними питаннями CouchDB є:

  1. Які правила / принципи ви використовуєте для поділу своїх документів (відносини тощо)?
  2. Чи добре розмістити весь сайт в одному документі?
  3. Якщо так, як ви обробляєте документи з серіалізацією / дезаріалізацією з довільними рівнями глибин (наприклад, великим прикладом json вище чи прикладом xml)?
  4. Або ви не перетворите їх на VO, ви просто вирішите, що "вони занадто вкладені в об'єктно-реляційну карту, тому я просто отримаю доступ до них за допомогою сирих методів XML / JSON"?

Велике спасибі за вашу допомогу, питання про те, як розділити свої дані за допомогою CouchDB, мені було важко сказати "ось так я повинен робити це відтепер". Я сподіваюся, що скоро потраплять туди

Я вивчив наступні сайти / проекти.

  1. Ієрархічні дані в CouchDB
  2. CouchDB Wiki
  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' для включення цих документів у карту / зменшення виводу:

{"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' документа, з якого було випущено рядок (у цьому випадку документ 'post'). Зауважте, ці результати включатимуть поле '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 (у даному випадку названа "уніфікована") буде приймати результати карти перегляду / зменшувати (у цьому випадку названі "повідомлення") та запускати їх через функцію JavaScript, яка б повертала відповідь HTTP у відповідному типі вмісту потреба (JSON, HTML тощо).

Поєднуючи ці речі, ви можете розділити свої документи на будь-якому рівні, який вам здасться корисним та "безпечним" для оновлень, конфліктів та реплікацій, а потім скласти їх разом, якщо потрібно, на їх запит.

Сподіваюся, що це допомагає.


2
Не впевнений, чи це допомогло Ленсу, але я знаю одне; це, безумовно, мені дуже допомогло! Це круто!
Марк

17

Я знаю, що це старе питання, але я натрапив на нього, намагаючись визначити найкращий підхід до цієї саме такої проблеми. Крістофер Ленц написав приємну публікацію в блозі про методи моделювання "приєднання" в CouchDB . Одним із моїх відходів було: "Єдиний спосіб дозволити безконфліктне додавання пов’язаних даних - це розміщення цих пов'язаних даних в окремих документах". Отже, для простоти ви хочете схилятися до "денормалізації". Але ви стикаєтесь із природним бар’єром через конфліктні записи при певних обставинах.

У вашому прикладі повідомлень та коментарів, якщо одна публікація та всі її коментарі містилися в одному документі, то двоє людей, які намагаються опублікувати коментар одночасно (тобто проти того ж перегляду документу), спричинили б конфлікт. Це стане ще гіршим у вашому сценарії "весь сайт у єдиному документі".

Тому я думаю, що правило буде "денормалізувати, поки це не боляче", але пункт, коли це буде "боляче", це те, коли ви маєте велику ймовірність декількох редагувань проти того самого перегляду документа.


Цікава відповідь. Зважаючи на це, варто поставити під сумнів, чи матиме будь-який достатньо високий трафік на сайті всі коментарі до однієї публікації блогу в одному документі. Якщо я прочитав це право, то це означає, що кожного разу, коли у вас люди швидко додають коментарі, вам, можливо, доведеться вирішувати конфлікти. Звичайно, я не знаю, наскільки швидко вони мали би бути, щоб спровокувати це.
pc1oad1etter

1
У випадку, коли коментарі є частиною документа в Couch, одночасні коментарі до коментарів можуть бути конфліктними, оскільки ваша версія версії - це "пост" з усіма його коментарями. У випадку, коли кожен із ваших об'єктів - це колекція документів, вони стануть просто двома новими документами з коментарями із посиланнями на пост і не мають жодних проблем зіткнення. Я також хотів би зазначити, що побудова поглядів на "об'єктно-орієнтований" doc-дизайн прямо вперед - ви, наприклад, переходите в ключ повідомлення, а потім випромінюєте всі коментарі, відсортовані певним методом, для цієї публікації.
Ріяд Калла

16

У книзі сказано, якщо я правильно пригадую, денормалізувати, поки «це не боляче», пам’ятаючи про частоту, з якою ваші документи можуть оновлюватися.

  1. Які правила / принципи ви використовуєте для поділу своїх документів (відносини тощо)?

Як правило, я включаю всі дані, необхідні для відображення сторінки щодо предмета, про який йдеться. Іншими словами, все, що ви надрукували б на папері в реальному світі, який ви передали б комусь. Наприклад, документ про котирування акцій, крім номерів, міститиме назву компанії, біржу, валюту; договірний документ містив би імена та адреси контрагентів, всю інформацію про дати та підписантів. Але котирування акцій з різних дат формували б окремі документи, окремі договори формували б окремі документи.

  1. Чи добре розмістити весь сайт в одному документі?

Ні, це було б нерозумно, бо:

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

3
Дякую за те, що ви трохи зібралися зі мною. Мені здається, що "включати всі дані, необхідні для відображення сторінки щодо питання, про який йде мова", але це все ще дуже важко реалізувати. "Сторінка" може бути сторінкою коментарів, сторінкою користувачів, сторінкою повідомлень або сторінкою коментарів і дописів тощо. Як би ви розділили їх, головним чином? Ви також можете відображати свій договір з користувачами. Я отримую документи, подібні до форми, і є сенс зберігати їх окремо.
Ленс Поллард

6

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

У випадку, коли у вас є коментарі як властивість масиву самої публікації, і ви просто маєте "пост" БД з купою величезних "поштових" документів, як Джейк та інші правильно вказали, що ви можете уявити собі сценарій дійсно популярна публікація в блозі, де два користувачі одночасно надсилають зміни до документа, що призводить до зіткнення та конфлікту версії для цього документа.

ВІДПОВІДЬ: Як вказує ця стаття , також враховуйте, що кожного разу, коли ви запитуєте / оновлюєте цей документ, вам доведеться отримати / встановити документ у повному обсязі, таким чином обходячись масивними документами, що представляють собою весь сайт або публікацію з великою кількістю коментарів до нього можуть стати проблемою, якої ви хочете уникнути.

У випадку, коли публікації моделюються окремо від коментарів, і двоє людей подають коментар до історії, вони просто стають двома документами з "коментарями" у цій БД, без жодних питань конфлікту; лише дві операції PUT, щоб додати два нових коментаря до "коментаря".

Потім, щоб написати перегляди, які повертають вам коментарі до публікації, ви перейдете в postID, а потім випустите всі коментарі, що посилаються на цей ідентифікатор батьківського допису, відсортовані в певному логічному порядку. Можливо, ви навіть передаєте щось подібне до [postID, byUsername] як ключ до перегляду коментарів, щоб вказати батьківський пост та те, як ви хочете відсортувати результати або щось у цьому напрямку.

MongoDB обробляє документи дещо інакше, що дозволяє будувати індекси на піделементах документа, тож ви можете побачити те саме питання у списку розсилки MongoDB, а хтось каже, що "просто зробіть коментарі властивістю батьківського допису".

Через заблокованість запису та єдиний манговий характер Монго, конфліктне питання перегляду двох людей, що додають коментарі, не з’явиться там, і запит на вміст, як уже згадувалося, не надто поганий через під- покажчики.

Однак, якщо ваші піделементи в будь-якій БД будуть величезними (скажімо, 10 тисяч тисяч коментарів), я вважаю, що це рекомендація обох таборів зробити ці окремі елементи; Я, звичайно, бачив, що це стосується Монго, оскільки існують деякі верхні межі щодо того, наскільки великий може бути документ і його субелементи.


Дуже корисний. Дякую
Рей Суельцер
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.