Prinsip untuk Memodelkan Dokumen CouchDB


120

Saya memiliki pertanyaan yang telah saya coba jawab selama beberapa waktu sekarang tetapi tidak dapat dipecahkan:

Bagaimana Anda mendesain, atau membagi, dokumen CouchDB?

Ambil Entri Blog sebagai contoh.

Cara semi "relasional" untuk melakukannya adalah dengan membuat beberapa objek:

  • Pos
  • Pengguna
  • Komentar
  • Menandai
  • Potongan

Ini sangat masuk akal. Tapi saya mencoba menggunakan couchdb (untuk semua alasan yang bagus) untuk memodelkan hal yang sama dan itu sangat sulit.

Sebagian besar posting blog di luar sana memberi Anda contoh mudah tentang bagaimana melakukan ini. Mereka pada dasarnya membaginya dengan cara yang sama, tetapi katakanlah Anda dapat menambahkan properti 'sewenang-wenang' ke setiap dokumen, yang tentunya bagus. Jadi Anda akan memiliki sesuatu seperti ini di CouchDB:

  • Posting (dengan tag dan cuplikan model "semu" di dokumen)
  • Komentar
  • Pengguna

Beberapa orang bahkan akan mengatakan Anda bisa melempar Komentar dan Pengguna di sana, jadi Anda akan memiliki ini:


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

Itu terlihat sangat bagus dan mudah dimengerti. Saya juga memahami bagaimana Anda dapat menulis tampilan yang mengekstrak hanya Komentar dari semua dokumen Posting Anda, untuk memasukkannya ke dalam model Komentar, sama dengan Pengguna dan Tag.

Tapi kemudian saya berpikir, "mengapa tidak memasukkan seluruh situs saya ke dalam satu dokumen?":


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

Anda dapat dengan mudah membuat tampilan untuk menemukan apa yang Anda inginkan dengan itu.

Lalu pertanyaan saya adalah, bagaimana Anda menentukan kapan harus membagi dokumen menjadi dokumen yang lebih kecil, atau kapan membuat "RELATIONS" antar dokumen?

Saya pikir itu akan jauh lebih "Berorientasi Objek", dan lebih mudah untuk memetakan ke Objek Nilai, jika dibagi seperti ini:


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

... tapi kemudian mulai terlihat lebih seperti Database Relasional. Dan sering kali saya mewarisi sesuatu yang terlihat seperti "whole-site-in-a-document", jadi lebih sulit untuk memodelkannya dengan relasi.

Saya telah membaca banyak hal tentang bagaimana / kapan menggunakan Database Relasional vs. Database Dokumen, jadi itu bukan masalah utama di sini. Saya lebih hanya bertanya-tanya, apa aturan / prinsip yang baik untuk diterapkan saat memodelkan data di CouchDB.

Contoh lainnya adalah dengan file / data XML. Beberapa data XML memiliki 10+ level bersarang, dan saya ingin memvisualisasikan bahwa menggunakan klien yang sama (misalnya Ajax on Rails, atau Flex) yang akan saya render JSON dari ActiveRecord, CouchRest, atau Pemeta Relasional Objek lainnya. Terkadang saya mendapatkan file XML besar yang merupakan keseluruhan struktur situs, seperti yang di bawah ini, dan saya perlu memetakannya ke Value Objects untuk digunakan di aplikasi Rails saya sehingga saya tidak perlu menulis cara lain untuk membuat serial / deserialisasi data :


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

Jadi pertanyaan umum CouchDB adalah:

  1. Aturan / prinsip apa yang Anda gunakan untuk membagi dokumen Anda (hubungan, dll)?
  2. Bolehkah meletakkan seluruh situs ke dalam satu dokumen?
  3. Jika demikian, bagaimana Anda menangani pembuatan serial / deserialisasi dokumen dengan tingkat kedalaman arbitrer (seperti contoh json besar di atas, atau contoh xml)?
  4. Atau apakah Anda tidak mengubahnya menjadi VO, apakah Anda hanya memutuskan "yang ini terlalu bersarang ke Peta Relasional Objek, jadi saya akan mengaksesnya menggunakan metode XML / JSON mentah"?

Terima kasih banyak atas bantuan Anda, masalah cara membagi data Anda dengan CouchDB sulit bagi saya untuk mengatakan "beginilah cara saya melakukannya mulai sekarang". Saya berharap untuk segera sampai di sana.

Saya telah mempelajari situs / proyek berikut.

  1. Data Hierarki di CouchDB
  2. CouchDB Wiki
  3. Sofa - Aplikasi CouchDB
  4. CouchDB Panduan Definitif
  5. PeepCode CouchDB Screencast
  6. CouchRest
  7. CouchDB README

... tapi mereka masih belum menjawab pertanyaan ini.


2
wow Anda telah menulis seluruh esai di sini ... :-)
Eero

8
hey, itu pertanyaan yang bagus
elmarco

Jawaban:


26

Sudah ada beberapa jawaban bagus untuk ini, tetapi saya ingin menambahkan beberapa fitur CouchDB yang lebih baru ke campuran opsi untuk bekerja dengan situasi asli yang dijelaskan oleh viatropos.

Titik kunci untuk memisahkan dokumen adalah saat mungkin ada konflik (seperti yang disebutkan sebelumnya). Anda tidak boleh menyimpan dokumen yang "kusut" secara besar-besaran dalam satu dokumen karena Anda akan mendapatkan satu jalur revisi untuk pembaruan yang sama sekali tidak terkait (misalnya, penambahan komentar menambahkan revisi ke seluruh dokumen situs). Mengelola hubungan atau koneksi antara berbagai dokumen yang lebih kecil dapat membingungkan pada awalnya, tetapi CouchDB menyediakan beberapa opsi untuk menggabungkan bagian yang berbeda menjadi tanggapan tunggal.

Yang besar pertama adalah susunan tampilan. Saat Anda memancarkan pasangan kunci / nilai ke dalam hasil kueri peta / pengurangan, kunci diurutkan berdasarkan pemeriksaan UTF-8 ("a" datang sebelum "b"). Anda dapat juga keluaran kompleks kunci dari peta Anda / mengurangi sebagai array JSON: ["a", "b", "c"]. Melakukan hal itu akan memungkinkan Anda untuk menyertakan "pohon" yang dibangun dari kunci array. Dengan menggunakan contoh Anda di atas, kita dapat mengeluarkan post_id, lalu jenis hal yang kita referensikan, lalu ID-nya (jika perlu). Jika kita kemudian mengeluarkan id dari dokumen yang direferensikan menjadi sebuah objek dalam nilai yang dikembalikan kita dapat menggunakan parameter kueri 'include_docs' untuk memasukkan dokumen-dokumen itu ke dalam map / mengurangi keluaran:

{"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"}}
]}

Meminta tampilan yang sama dengan '? Include_docs = true' akan menambahkan kunci 'doc' yang akan menggunakan '_id' yang direferensikan dalam objek 'value' atau jika tidak ada di objek 'value', itu akan menggunakan '_id' dari dokumen tempat baris tersebut dikeluarkan (dalam hal ini dokumen 'post'). Harap dicatat, hasil ini akan mencakup bidang 'id' yang merujuk pada dokumen sumber dari mana emisi itu dibuat. Saya meninggalkannya untuk ruang dan keterbacaan.

Kami kemudian dapat menggunakan parameter 'start_key' dan 'end_key' untuk memfilter hasil ke data satu posting:

? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}]
Atau bahkan secara khusus mengekstrak daftar untuk jenis tertentu:
? start_key = ["123412804910820", "comment"] & end_key = ["123412804910820", "comment", {}]
Kombinasi parameter kueri ini dimungkinkan karena objek kosong (" {}") selalu berada di bagian bawah pemeriksaan dan null atau "" selalu di atas.

Penambahan bermanfaat kedua dari CouchDB dalam situasi ini adalah fungsi _list. Ini akan memungkinkan Anda untuk menjalankan hasil di atas melalui sistem templating dari beberapa jenis (jika Anda menginginkan HTML, XML, CSV, atau apa pun kembali), atau mengeluarkan struktur JSON terpadu jika Anda ingin dapat meminta seluruh konten posting (termasuk author and comment data) dengan satu permintaan dan dikembalikan sebagai dokumen JSON tunggal yang sesuai dengan kebutuhan kode sisi klien / UI Anda. Melakukan itu akan memungkinkan Anda untuk meminta dokumen keluaran terpadu dari posting dengan cara ini:

/ db / _design / app / _list / posts / unified ?? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}] & include_docs = true
Fungsi _list Anda (dalam hal ini bernama "unified") akan mengambil hasil dari view map / reduce (dalam hal ini bernama "posts") dan menjalankannya melalui fungsi JavaScript yang akan mengirimkan kembali respon HTTP dalam tipe konten Anda kebutuhan (JSON, HTML, dll).

Dengan menggabungkan hal-hal ini, Anda dapat memisahkan dokumen Anda pada tingkat apa pun yang Anda anggap berguna dan "aman" untuk pembaruan, konflik, dan replikasi, dan kemudian menyatukannya kembali sesuai kebutuhan saat diminta.

Semoga membantu.


2
Tidak yakin apakah ini membantu Lance, tapi aku tahu satu hal; itu pasti sangat membantu saya! Ini luar biasa!
Tandai

17

Saya tahu ini adalah pertanyaan lama, tetapi saya menemukannya saat mencoba mencari pendekatan terbaik untuk masalah yang sama persis. Christopher Lenz menulis posting blog yang bagus tentang metode pemodelan "bergabung" di CouchDB . Salah satu kesimpulan saya adalah: "Satu-satunya cara untuk mengizinkan penambahan data terkait yang tidak menimbulkan konflik adalah dengan meletakkan data terkait itu ke dalam dokumen terpisah." Jadi, demi kesederhanaan Anda ingin condong ke arah "denormalisasi". Tetapi Anda akan menemui hambatan alami karena konflik penulisan dalam keadaan tertentu.

Dalam contoh Postingan dan Komentar Anda, jika satu postingan dan semua komentarnya ada dalam satu dokumen, maka dua orang yang mencoba memposting komentar pada saat yang sama (yaitu terhadap revisi dokumen yang sama) akan menyebabkan konflik. Ini akan menjadi lebih buruk dalam skenario "seluruh situs dalam satu dokumen" Anda.

Jadi menurut saya aturan praktisnya adalah "mendenormalisasi hingga terasa sakit", tetapi titik di mana hal itu akan "merugikan" adalah di mana Anda memiliki kemungkinan besar beberapa hasil edit diposting dengan revisi dokumen yang sama.


Respon yang menarik. Dengan pemikiran tersebut, orang harus mempertanyakan apakah situs dengan lalu lintas yang cukup tinggi bahkan akan memiliki semua komentar untuk satu posting blog dalam satu dokumen. Jika saya membacanya dengan benar, itu berarti bahwa setiap kali Anda memiliki orang yang menambahkan komentar secara berurutan, Anda mungkin harus menyelesaikan konflik. Tentu saja, saya tidak tahu seberapa cepat berturut-turut mereka harus memicu ini.
pc1oad1etter

1
Dalam kasus di mana komentar merupakan bagian dari dokumen di Sofa, posting komentar secara bersamaan dapat menimbulkan konflik karena cakupan pembuatan versi Anda adalah "posting" dengan semua komentarnya. Dalam kasus di mana setiap objek Anda adalah kumpulan dokumen, ini hanya akan menjadi dua dokumen 'komentar' baru dengan tautan kembali ke pos dan tidak ada kekhawatiran tabrakan. Saya juga akan menunjukkan bahwa membangun tampilan pada desain dokumen "berorientasi objek" adalah mudah - Anda memasukkan kunci posting misalnya, kemudian mengeluarkan semua komentar, diurutkan dengan beberapa metode, untuk posting itu.
Riyad Kalla

16

The book mengatakan, jika saya ingat benar, denormalize sampai "sakit", sambil mengingat frekuensi yang dokumen Anda mungkin diperbarui.

  1. Aturan / prinsip apa yang Anda gunakan untuk membagi dokumen Anda (hubungan, dll)?

Sebagai aturan praktis, saya menyertakan semua data yang diperlukan untuk menampilkan halaman tentang item yang dimaksud. Dengan kata lain, semua yang akan Anda cetak di selembar kertas dunia nyata yang akan Anda berikan kepada seseorang. Misalnya, dokumen kutipan saham akan menyertakan nama perusahaan, bursa, mata uang, selain nomor; dokumen kontrak akan mencakup nama dan alamat rekanan, semua informasi tentang tanggal dan penandatangan. Tetapi harga saham dari tanggal yang berbeda akan membentuk dokumen terpisah, kontrak terpisah akan membentuk dokumen terpisah.

  1. Bolehkah meletakkan seluruh situs ke dalam satu dokumen?

Tidak, itu konyol, karena:

  • Anda harus membaca dan menulis seluruh situs (dokumen) pada setiap pembaruan, dan itu sangat tidak efisien;
  • Anda tidak akan mendapatkan keuntungan dari cache tampilan apa pun.

3
Terima kasih sudah sedikit terlibat dengan saya. Saya mendapatkan ide "sertakan semua data yang diperlukan untuk menampilkan halaman tentang item yang dimaksud", tetapi itu masih sangat sulit untuk diterapkan. Sebuah "halaman" bisa menjadi halaman Komentar, halaman Pengguna, halaman Posting, atau halaman Komentar dan Posting, dll. Bagaimana Anda membaginya, pada prinsipnya? Kontrak Anda juga dapat ditampilkan dengan Pengguna. Saya mendapatkan dokumen 'seperti formulir', sehingga masuk akal untuk memisahkannya.
Lance Pollard

6

Saya pikir tanggapan Jake merupakan salah satu aspek terpenting dalam bekerja dengan CouchDB yang dapat membantu Anda membuat keputusan pelingkupan: konflik.

Dalam kasus di mana Anda memiliki komentar sebagai properti array dari posting itu sendiri, dan Anda hanya memiliki DB 'posting' dengan banyak dokumen 'posting' besar di dalamnya, seperti yang ditunjukkan Jake dan yang lainnya dengan benar, Anda dapat membayangkan skenario tentang entri blog yang sangat populer di mana dua pengguna mengirimkan pengeditan ke dokumen entri secara bersamaan, mengakibatkan benturan dan konflik versi untuk dokumen tersebut.

Selain: Seperti yang ditunjukkan artikel ini , pertimbangkan juga bahwa setiap kali Anda meminta / memperbarui dokumen itu, Anda harus mendapatkan / mengatur dokumen secara keseluruhan, jadi menyebarkan dokumen besar yang mewakili keseluruhan situs atau posting dengan banyak. komentar di atasnya bisa menjadi masalah yang ingin Anda hindari.

Dalam kasus di mana posting dimodelkan secara terpisah dari komentar dan dua orang mengirimkan komentar pada sebuah cerita, itu hanya menjadi dua dokumen "komentar" di DB tersebut, tanpa masalah konflik; hanya dua operasi PUT untuk menambahkan dua komentar baru ke db "komentar".

Kemudian untuk menulis tampilan yang memberi Anda kembali komentar untuk sebuah posting, Anda akan mengirimkan postID dan kemudian mengeluarkan semua komentar yang mereferensikan ID posting induk itu, diurutkan dalam beberapa urutan logis. Mungkin Anda bahkan meneruskan sesuatu seperti [postID, byUsername] sebagai kunci ke tampilan 'komentar' untuk menunjukkan entri induk dan bagaimana Anda ingin hasil diurutkan atau sesuatu di sepanjang baris tersebut.

MongoDB menangani dokumen sedikit berbeda, memungkinkan indeks dibangun di atas sub-elemen dokumen, jadi Anda mungkin melihat pertanyaan yang sama di milis MongoDB dan seseorang berkata "buat komentar saja sebagai properti dari kiriman induk".

Karena sifat penguncian tulis dan master tunggal Mongo, masalah revisi yang bertentangan dari dua orang yang menambahkan komentar tidak akan muncul di sana dan kemampuan kueri konten, seperti yang disebutkan, tidak terpengaruh terlalu buruk karena sub- indeks.

Dengan demikian, jika sub-elemen Anda di salah satu DB akan menjadi besar (katakanlah 10 dari ribuan komentar) Saya yakin itu adalah rekomendasi dari kedua kubu untuk membuat elemen-elemen terpisah; Saya pasti telah melihat hal itu terjadi pada Mongo karena ada beberapa batasan batas atas seberapa besar dokumen dan subelemennya.


Sangat membantu. Terima kasih
Ray Suelzer
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.