Principi per la modellazione di documenti CouchDB


120

Ho una domanda a cui sto cercando di rispondere da un po 'di tempo ma non riesco a capire:

Come progettate o suddividete i documenti CouchDB?

Prendi un post sul blog per esempio.

Il modo semi "relazionale" per farlo sarebbe creare alcuni oggetti:

  • Inviare
  • Utente
  • Commento
  • Etichetta
  • Frammento

Questo ha molto senso. Ma sto cercando di usare couchdb (per tutti i motivi per cui è fantastico) per modellare la stessa cosa ed è stato estremamente difficile.

La maggior parte dei post sul blog ti danno un semplice esempio di come farlo. Fondamentalmente lo dividono allo stesso modo, ma dicono che puoi aggiungere proprietà "arbitrarie" a ciascun documento, il che è decisamente carino. Quindi avresti qualcosa di simile in CouchDB:

  • Post (con tag e snippet "pseudo" modelli nel documento)
  • Commento
  • Utente

Alcune persone direbbero addirittura che potresti inserire il commento e l'utente lì dentro, quindi avresti questo:


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

Sembra molto carino ed è facile da capire. Capisco anche come potresti scrivere viste che estraevano solo i commenti da tutti i tuoi documenti di post, per inserirli nei modelli di commento, lo stesso con gli utenti e i tag.

Ma poi penso, "perché non mettere tutto il mio sito in un unico documento?":


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

Potresti facilmente creare visualizzazioni per trovare ciò che desideri.

Quindi la domanda che ho è: come determinare quando dividere il documento in documenti più piccoli o quando creare "RELAZIONI" tra i documenti?

Penso che sarebbe molto più "orientato agli oggetti" e più facile mappare agli oggetti valore, se fosse diviso in questo modo:


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

... ma poi inizia ad assomigliare più a un database relazionale. E spesso eredito qualcosa che assomiglia al "tutto il sito-in-un-documento", quindi è più difficile modellarlo con le relazioni.

Ho letto molte cose su come / quando utilizzare i database relazionali rispetto ai database dei documenti, quindi non è questo il problema principale qui. Mi chiedo più semplicemente qual è una buona regola / principio da applicare durante la modellazione dei dati in CouchDB.

Un altro esempio è con file / dati XML. Alcuni dati XML hanno una nidificazione di oltre 10 livelli e vorrei visualizzarli utilizzando lo stesso client (Ajax on Rails per esempio, o Flex) che vorrei per eseguire il rendering JSON da ActiveRecord, CouchRest o qualsiasi altro Object Relational Mapper. A volte ottengo enormi file XML che sono l'intera struttura del sito, come quello qui sotto, e dovrei mapparlo su Value Objects da utilizzare nella mia app Rails in modo da non dover scrivere un altro modo di serializzare / deserializzare i dati :


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

Quindi le domande generali di CouchDB sono:

  1. Quali regole / principi usi per suddividere i tuoi documenti (relazioni, ecc.)?
  2. Va bene mettere l'intero sito in un documento?
  3. In tal caso, come gestisci la serializzazione / deserializzazione dei documenti con livelli di profondità arbitrari (come il grande esempio json sopra o l'esempio xml)?
  4. O non li trasformi in VO, decidi semplicemente "questi sono troppo nidificati nella mappa relazionale degli oggetti, quindi li accederò usando metodi XML / JSON non elaborati"?

Grazie mille per il tuo aiuto, la questione di come dividere i tuoi dati con CouchDB è stato difficile per me dire "questo è come dovrei farlo d'ora in poi". Spero di arrivarci presto.

Ho studiato i seguenti siti / progetti.

  1. Dati gerarchici in CouchDB
  2. CouchDB Wiki
  3. Divano - App CouchDB
  4. CouchDB La guida definitiva
  5. PeepCode CouchDB Screencast
  6. CouchRest
  7. CouchDB README

... ma non hanno ancora risposto a questa domanda.


2
wow hai scritto un intero saggio qui ... :-)
Eero

8
ehi, questa è una buona domanda
elmarco

Risposte:


26

Ci sono già state alcune ottime risposte a questo, ma volevo aggiungere alcune funzionalità più recenti di CouchDB al mix di opzioni per lavorare con la situazione originale descritta da viatropos.

Il punto chiave in cui suddividere i documenti è dove potrebbero esserci conflitti (come accennato in precedenza). Non dovresti mai tenere insieme documenti "ingarbugliati" in un unico documento poiché otterrai un unico percorso di revisione per aggiornamenti completamente non correlati (aggiunta di commenti aggiungendo una revisione all'intero documento del sito, ad esempio). La gestione delle relazioni o delle connessioni tra vari documenti più piccoli può creare confusione all'inizio, ma CouchDB offre diverse opzioni per combinare parti disparate in risposte singole.

Il primo grande è la visualizzazione delle regole di confronto. Quando si creano coppie chiave / valore nei risultati di una query di mappatura / riduzione, le chiavi vengono ordinate in base alle regole di confronto UTF-8 ("a" viene prima di "b"). È inoltre possibile uscita chiavi complesse dalla mappa / ridurre come array JSON: ["a", "b", "c"]. Ciò consentirebbe di includere una sorta di "albero" costruito con chiavi di array. Usando il tuo esempio sopra, possiamo produrre il post_id, quindi il tipo di cosa a cui facciamo riferimento, quindi il suo ID (se necessario). Se quindi emettiamo l'ID del documento di riferimento in un oggetto nel valore restituito, possiamo utilizzare il parametro della query 'include_docs' per includere quei documenti nell'output 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"}}
]}

La richiesta della stessa vista con "? Include_docs = true" aggiungerà una chiave "doc" che utilizzerà il "_id" a cui si fa riferimento nell'oggetto "value" oppure, se non è presente nell'oggetto "value", verrà utilizzato l '"_id" del documento da cui è stata emessa la riga (in questo caso il documento "post"). Si noti che questi risultati includerebbero un campo "id" che fa riferimento al documento di origine da cui è stata eseguita l'emissione. L'ho lasciato fuori per spazio e leggibilità.

Possiamo quindi utilizzare i parametri "start_key" e "end_key" per filtrare i risultati fino ai dati di un singolo post:

? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}]
O anche estrarre specificamente l'elenco per un certo tipo:
? start_key = ["123412804910820", "comment"] & end_key = ["123412804910820", "comment", {}]
Queste combinazioni di parametri di query sono possibili perché un oggetto vuoto (" {}") è sempre in fondo alle regole di confronto e null o "" sono sempre in alto.

La seconda aggiunta utile da CouchDB in queste situazioni è la funzione _list. Ciò ti consentirebbe di eseguire i risultati di cui sopra attraverso un sistema di modelli di qualche tipo (se vuoi HTML, XML, CSV o qualsiasi altra cosa indietro), o di produrre una struttura JSON unificata se vuoi essere in grado di richiedere il contenuto di un intero post (incluso autore e dati di commento) con una singola richiesta e restituito come un unico documento JSON che corrisponde a ciò di cui ha bisogno il codice lato client / interfaccia utente. Ciò ti consentirebbe di richiedere il documento di output unificato del post in questo modo:

/ db / _design / app / _list / posts / unified ?? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}] & include_docs = true
La tua funzione _list (in questo caso denominata "unificata") prenderebbe i risultati della visualizzazione mappa / riduci (in questo caso denominata "post") e li eseguirà attraverso una funzione JavaScript che restituirà la risposta HTTP nel tipo di contenuto che hai bisogno (JSON, HTML, ecc.).

Combinando queste cose, puoi suddividere i tuoi documenti a qualsiasi livello tu ritenga utile e "sicuro" per aggiornamenti, conflitti e repliche, e poi rimetterli insieme secondo le necessità quando vengono richiesti.

Spero che aiuti.


2
Non sono sicuro che questo abbia aiutato Lance, ma so una cosa; mi ha sicuramente aiutato moltissimo! Questo e spettacolare!
Marco

17

So che questa è una vecchia domanda, ma mi sono imbattuto cercando di capire l'approccio migliore a questo identico problema. Christopher Lenz ha scritto un bel post sul blog sui metodi di modellazione dei "join" in CouchDB . Uno dei miei suggerimenti è stato: "L'unico modo per consentire l'aggiunta non conflittuale di dati correlati è inserire tali dati correlati in documenti separati". Quindi, per amor di semplicità, vorresti propendere per la "denormalizzazione". Ma in determinate circostanze incontrerai una barriera naturale a causa di scritture contrastanti.

Nel tuo esempio di Post e commenti, se un singolo post e tutti i suoi commenti si trovassero in un documento, due persone che tentano di pubblicare un commento contemporaneamente (cioè contro la stessa revisione del documento) causerebbero un conflitto. Ciò peggiorerebbe anche nello scenario "intero sito in un unico documento".

Quindi penso che la regola pratica sarebbe "denormalizza finché non fa male", ma il punto in cui "fa male" è quando hai un'alta probabilità che vengano pubblicate più modifiche rispetto alla stessa revisione di un documento.


Risposta interessante. Con questo in mente, ci si dovrebbe chiedere se un sito a traffico ragionevolmente alto avrebbe anche tutti i commenti per un singolo post del blog in un documento. Se ho letto bene, significa che ogni volta che le persone aggiungono commenti in rapida successione, potresti dover risolvere i conflitti. Naturalmente, non so quanto dovrebbero essere veloci in successione per attivare questo.
pc1oad1etter

1
Nel caso in cui i commenti fanno parte del documento in Couch, i post di commenti simultanei potrebbero essere in conflitto perché l'ambito di controllo delle versioni è il "post" con tutti i suoi commenti. Nel caso in cui ciascuno dei tuoi oggetti siano raccolte di documenti, questi diventerebbero semplicemente due nuovi documenti di "commento" con collegamenti al post e nessuna preoccupazione di collisione. Vorrei anche sottolineare che la creazione di viste sulla progettazione di documenti "orientati agli oggetti" è semplice: si passa la chiave di un post per esempio, quindi si emettono tutti i commenti, ordinati in base a un metodo, per quel post.
Riyad Kalla

16

Il libro dice, se ricordo bene, di denormalizzare fino a "fa male", tenendo presente la frequenza con cui i tuoi documenti potrebbero essere aggiornati.

  1. Quali regole / principi usi per suddividere i tuoi documenti (relazioni, ecc.)?

Come regola generale, includo tutti i dati necessari per visualizzare una pagina relativa all'elemento in questione. In altre parole, tutto ciò che stamperesti su un pezzo di carta del mondo reale che vorresti consegnare a qualcuno. Ad esempio, un documento di quotazione di borsa includerebbe il nome della società, la borsa valori, la valuta, oltre ai numeri; un documento contrattuale includerebbe i nomi e gli indirizzi delle controparti, tutte le informazioni su date e firmatari. Ma le quotazioni di borsa di date distinte formerebbero documenti separati, i contratti separati formerebbero documenti separati.

  1. Va bene mettere l'intero sito in un documento?

No, sarebbe sciocco, perché:

  • dovresti leggere e scrivere l'intero sito (il documento) ad ogni aggiornamento, e questo è molto inefficiente;
  • non trarrai vantaggio da alcuna visualizzazione nella cache.

3
Grazie per esserti appassionato un po 'con me. Ho l'idea di "includere tutti i dati necessari per visualizzare una pagina relativa all'elemento in questione", ma è ancora molto difficile da implementare. Una "pagina" potrebbe essere una pagina di commenti, una pagina di utenti, una pagina di post o una pagina di commenti e post, ecc. Come li dividereste quindi, principalmente? Puoi anche visualizzare il tuo contratto con gli utenti. Ottengo i documenti "form-like", che ha senso tenerli separati.
Lance Pollard

6

Penso che la risposta di Jake inchiodi uno degli aspetti più importanti del lavoro con CouchDB che può aiutarti a prendere la decisione di scoping: i conflitti.

Nel caso in cui tu abbia commenti come una proprietà array del post stesso, e hai solo un DB 'post' con un mucchio di enormi documenti 'post' in esso, come Jake e altri hanno correttamente sottolineato, potresti immaginare uno scenario su un post di blog molto popolare in cui due utenti inviano modifiche al documento di post contemporaneamente, provocando una collisione e un conflitto di versione per quel documento.

A PARTE: Come sottolinea questo articolo , considera anche che ogni volta che richiedi / aggiorni quel documento devi ottenere / impostare il documento nella sua interezza, quindi passando in giro una grande quantità di documenti che rappresentano l'intero sito o un post con molto di commenti su di esso può diventare un problema che vorresti evitare.

Nel caso in cui i post sono modellati separatamente dai commenti e due persone inviano un commento su una storia, questi diventano semplicemente due documenti di "commento" in quel DB, senza problemi di conflitto; solo due operazioni PUT per aggiungere due nuovi commenti al db "comment".

Quindi, per scrivere le visualizzazioni che ti restituiscono i commenti per un post, devi passare il postID e quindi emettere tutti i commenti che fanno riferimento a quell'ID del post genitore, ordinati in un ordine logico. Forse si passa anche qualcosa come [postID, byUsername] come chiave per la vista "commenti" per indicare il post principale e come si desidera ordinare i risultati o qualcosa del genere.

MongoDB gestisce i documenti in modo leggermente diverso, consentendo la creazione di indici sui sottoelementi di un documento, quindi potresti vedere la stessa domanda sulla mailing list di MongoDB e qualcuno che dice "rendi i commenti una proprietà del post principale".

A causa del blocco in scrittura e della natura single-master di Mongo, il problema di revisione conflittuale di due persone che aggiungono commenti non si presenterebbe lì e la capacità di interrogazione del contenuto, come accennato, non viene influenzata troppo male a causa di sub- indici.

Detto questo, se i vostri sottoelementi in uno dei DB saranno enormi (diciamo decine di migliaia di commenti) credo che sia la raccomandazione di entrambi i campi di rendere quegli elementi separati; Ho sicuramente visto che è il caso di Mongo in quanto ci sono alcuni limiti superiori su quanto possono essere grandi un documento e i suoi sottoelementi.


Molto utile. Grazie
Ray Suelzer
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.