Principes de modélisation des documents CouchDB


120

J'ai une question à laquelle j'essaie de répondre depuis un certain temps maintenant mais je n'arrive pas à comprendre:

Comment concevez-vous ou divisez-vous les documents CouchDB?

Prenons un article de blog par exemple.

La manière semi "relationnelle" de le faire serait de créer quelques objets:

  • Publier
  • Utilisateur
  • Commentaire
  • Marque
  • Fragment

Cela a beaucoup de sens. Mais j'essaie d'utiliser couchdb (pour toutes les raisons que c'est génial) pour modéliser la même chose et cela a été extrêmement difficile.

La plupart des articles de blog vous donnent un exemple simple de la façon de procéder. Ils le divisent fondamentalement de la même manière, mais disent que vous pouvez ajouter des propriétés «arbitraires» à chaque document, ce qui est vraiment bien. Donc vous auriez quelque chose comme ça dans CouchDB:

  • Message (avec des balises et des extraits de modèles "pseudo" dans le document)
  • Commentaire
  • Utilisateur

Certaines personnes diraient même que vous pouvez y ajouter le commentaire et l'utilisateur, donc vous auriez ceci:


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

Cela semble très joli et facile à comprendre. Je comprends également comment vous pouvez écrire des vues qui n'extraient que les commentaires de tous vos documents de publication, pour les intégrer dans des modèles de commentaires, de même avec les utilisateurs et les balises.

Mais alors je pense, "pourquoi ne pas simplement mettre tout mon site dans un seul document?":


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

Vous pourriez facilement faire des vues pour trouver ce que vous vouliez avec cela.

Alors la question que j'ai est, comment déterminez-vous quand diviser le document en plus petits documents, ou quand établir des "RELATIONS" entre les documents?

Je pense que ce serait beaucoup plus "orienté objet", et plus facile à mapper aux objets de valeur, s'il était divisé comme suit:


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

... mais ensuite cela ressemble plus à une base de données relationnelle. Et souvent, j'hérite de quelque chose qui ressemble au "site entier-dans-un-document", il est donc plus difficile de le modéliser avec des relations.

J'ai lu beaucoup de choses sur comment et quand utiliser les bases de données relationnelles par rapport aux bases de données de documents, ce n'est donc pas le problème principal ici. Je me demande plus simplement quelle est la bonne règle / principe à appliquer lors de la modélisation de données dans CouchDB.

Un autre exemple est celui des fichiers / données XML. Certaines données XML ont une imbrication de plus de 10 niveaux de profondeur, et j'aimerais visualiser cela en utilisant le même client (Ajax on Rails par exemple, ou Flex) que je voudrais rendre JSON à partir d'ActiveRecord, CouchRest ou de tout autre mappeur relationnel d'objets. Parfois, j'obtiens d'énormes fichiers XML qui représentent la structure entière du site, comme celui ci-dessous, et je devrais le mapper aux objets de valeur à utiliser dans mon application Rails afin de ne pas avoir à écrire une autre façon de sérialiser / désérialiser les données :


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

Les questions générales de CouchDB sont donc:

  1. Quelles règles / principes utilisez-vous pour diviser vos documents (relations, etc.)?
  2. Est-il acceptable de mettre tout le site dans un seul document?
  3. Si tel est le cas, comment gérez-vous la sérialisation / désérialisation de documents avec des niveaux de profondeur arbitraires (comme le grand exemple json ci-dessus ou l'exemple xml)?
  4. Ou ne les transformez-vous pas en VO, décidez-vous simplement "ceux-ci sont trop imbriqués dans Object-Relational Map, donc je vais simplement y accéder en utilisant des méthodes XML / JSON brutes"?

Merci beaucoup pour votre aide, la question de savoir comment diviser vos données avec CouchDB a été difficile pour moi de dire "voici comment je dois le faire à partir de maintenant". J'espère y arriver bientôt.

J'ai étudié les sites / projets suivants.

  1. Données hiérarchiques dans CouchDB
  2. Wiki CouchDB
  3. Canapé - Application CouchDB
  4. CouchDB Le guide définitif
  5. PeepCode CouchDB Screencast
  6. CouchRest
  7. CouchDB README

... mais ils n'ont toujours pas répondu à cette question.


2
wow vous avez écrit un essai entier ici ... :-)
Eero

8
hé, c'est une bonne question
elmarco

Réponses:


26

Il y a déjà eu de bonnes réponses à cela, mais je voulais ajouter des fonctionnalités plus récentes de CouchDB au mélange d'options pour travailler avec la situation originale décrite par viatropos.

Le point clé à partir duquel séparer les documents est celui où il peut y avoir des conflits (comme mentionné précédemment). Vous ne devriez jamais garder des documents massivement "emmêlés" ensemble dans un seul document car vous obtiendrez un seul chemin de révision pour des mises à jour totalement indépendantes (ajout de commentaire ajoutant une révision à l'ensemble du document du site par exemple). La gestion des relations ou des connexions entre divers documents plus petits peut être déroutante au début, mais CouchDB propose plusieurs options pour combiner des éléments disparates en réponses uniques.

Le premier grand est le classement des vues. Lorsque vous émettez des paires clé / valeur dans les résultats d'une requête mapper / réduire, les clés sont triées en fonction du classement UTF-8 ("a" vient avant "b"). Vous pouvez également les clés complexes sortie de votre map / reduce sous forme de tableaux JSON: ["a", "b", "c"]. Faire cela vous permettrait d'inclure un "arbre" de sortes construit à partir de clés de tableau. En utilisant votre exemple ci-dessus, nous pouvons afficher le post_id, puis le type de chose que nous référençons, puis son ID (si nécessaire). Si nous sortons ensuite l'id du document référencé dans un objet dans la valeur renvoyée, nous pouvons utiliser le paramètre de requête 'include_docs' pour inclure ces documents dans la carte / réduire la sortie:

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

Demander cette même vue avec '? Include_docs = true' ajoutera une clé 'doc' qui utilisera soit le '_id' référencé dans l'objet 'value' ou si cela n'est pas présent dans l'objet 'value', il utilisera le '_id' du document à partir duquel la ligne a été émise (dans ce cas, le document 'post'). Veuillez noter que ces résultats incluraient un champ «id» faisant référence au document source à partir duquel l'émission a été faite. Je l'ai laissé de côté pour l'espace et la lisibilité.

Nous pouvons ensuite utiliser les paramètres 'start_key' et 'end_key' pour filtrer les résultats jusqu'aux données d'un seul article:

? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}]
Ou même extraire spécifiquement la liste pour un certain type:
? start_key = ["123412804910820", "comment"] & end_key = ["123412804910820", "comment", {}]
Ces combinaisons de paramètres de requête sont possibles car un objet vide (" {}") est toujours en bas du classement et null ou "" sont toujours en haut.

Le deuxième ajout utile de CouchDB dans ces situations est la fonction _list. Cela vous permettrait d'exécuter les résultats ci-dessus via un système de création de modèles (si vous voulez du HTML, XML, CSV ou autre), ou de générer une structure JSON unifiée si vous souhaitez pouvoir demander le contenu d'un article entier (y compris author and comment data) avec une seule demande et renvoyé sous la forme d'un seul document JSON qui correspond aux besoins de votre code côté client / interface utilisateur. Cela vous permettrait de demander le document de sortie unifié de la publication de cette façon:

/ db / _design / app / _list / posts / unified ?? start_key = ["123412804910820"] & end_key = ["123412804910820", {}, {}] & include_docs = true
Votre fonction _list (dans ce cas nommée "unified") prendrait les résultats de la vue map / reduction (dans ce cas nommée "posts") et les exécuterait via une fonction JavaScript qui renverrait la réponse HTTP dans le type de contenu que vous besoin (JSON, HTML, etc.).

En combinant ces éléments, vous pouvez diviser vos documents à tout niveau que vous jugez utile et "sûr" pour les mises à jour, les conflits et la réplication, puis les reconstituer si nécessaire quand ils sont demandés.

J'espère que cela pourra aider.


2
Je ne sais pas si cela a aidé Lance, mais je sais une chose; cela m'a vraiment beaucoup aidé! C'est génial!
Mark

17

Je sais que c'est une vieille question, mais je suis tombée dessus en essayant de trouver la meilleure approche à ce problème exact. Christopher Lenz a écrit un joli billet de blog sur les méthodes de modélisation des «jointures» dans CouchDB . L'un de mes points à retenir était: "La seule façon d'autoriser l'ajout non conflictuel de données connexes est de placer ces données connexes dans des documents séparés." Donc, par souci de simplicité, vous voudrez vous pencher vers la «dénormalisation». Mais vous rencontrerez une barrière naturelle en raison d'écritures contradictoires dans certaines circonstances.

Dans votre exemple de Posts et Commentaires, si un seul post et tous ses commentaires vivaient dans un seul document, alors deux personnes essayant de poster un commentaire en même temps (c'est-à-dire contre la même révision du document) provoqueraient un conflit. Ce serait encore pire dans votre scénario «tout le site dans un seul document».

Je pense donc que la règle de base serait de «dénormaliser jusqu'à ce que ça fasse mal», mais le point où cela «fera mal» est celui où vous avez une forte probabilité que plusieurs modifications soient publiées contre la même révision d'un document.


Réponse intéressante. Dans cet esprit, il faut se demander si un site à trafic raisonnablement élevé aurait même tous les commentaires pour un seul article de blog dans un seul document. Si j'ai bien lu cela, cela signifie que chaque fois que des personnes ajoutent des commentaires rapidement, vous devrez peut-être résoudre des conflits. Bien sûr, je ne sais pas à quelle vitesse ils devraient être successifs pour déclencher cela.
pc1oad1etter

1
Dans le cas où les commentaires font partie du document dans Couch, les publications de commentaires simultanées pourraient être en conflit car votre portée de contrôle de version est le «post» avec tous ses commentaires. Dans le cas où chacun de vos objets sont des collections de documents, ceux-ci deviendraient simplement deux nouveaux documents de «commentaire» avec des liens vers la publication et sans souci de collision. Je voudrais également souligner que la construction de vues sur la conception de documents "orientée objet" est simple - vous passez la clé d'un message par exemple, puis émettez tous les commentaires, triés par une méthode, pour ce message.
Riyad Kalla

16

Le livre dit, si je me souviens bien, de dénormaliser jusqu'à ce que "ça fait mal", tout en gardant à l'esprit la fréquence à laquelle vos documents pourraient être mis à jour.

  1. Quelles règles / principes utilisez-vous pour diviser vos documents (relations, etc.)?

En règle générale, j'inclus toutes les données nécessaires pour afficher une page concernant l'élément en question. En d'autres termes, tout ce que vous imprimeriez sur un morceau de papier du monde réel que vous remettriez à quelqu'un. Par exemple, un document de cotation d'actions comprendrait le nom de la société, la bourse, la devise, en plus des numéros; un document contractuel comprendrait les noms et adresses des contreparties, toutes les informations sur les dates et les signataires. Mais les cotations boursières de dates distinctes formeraient des documents séparés, des contrats séparés formeraient des documents séparés.

  1. Est-il acceptable de mettre tout le site dans un seul document?

Non, ce serait idiot, car:

  • il faudrait lire et écrire tout le site (le document) à chaque mise à jour, ce qui est très inefficace;
  • vous ne bénéficieriez d'aucune mise en cache de vue.

3
Merci d'être un peu impliqué. J'ai l'idée de "inclure toutes les données nécessaires pour afficher une page concernant l'élément en question", mais cela reste très difficile à mettre en œuvre. Une «page» pourrait être une page de commentaires, une page d'utilisateurs, une page de messages, ou une page de commentaires et de messages, etc. Comment les diviseriez-vous alors, principalement? Vous pouvez également afficher votre contrat avec les utilisateurs. J'obtiens les documents «sous forme de formulaire», il est donc logique de les garder séparés.
Lance Pollard

6

Je pense que la réponse de Jake cloue l'un des aspects les plus importants du travail avec CouchDB qui peut vous aider à prendre la décision de cadrage: les conflits.

Dans le cas où vous avez des commentaires en tant que propriété de tableau de l'article lui-même, et que vous avez juste une base de données `` post '' avec un tas d'énormes documents `` post '', comme Jake et d'autres l'ont correctement souligné, vous pouvez imaginer un scénario sur un article de blog très populaire où deux utilisateurs soumettent simultanément des modifications au document de publication, ce qui entraîne une collision et un conflit de version pour ce document.

À part : comme le souligne cet article , considérez également que chaque fois que vous demandez / mettez à jour ce document, vous devez obtenir / définir le document dans son intégralité, donc en faisant circuler des documents massifs qui représentent l'ensemble du site ou un article avec beaucoup des commentaires peuvent devenir un problème que vous voudriez éviter.

Dans le cas où les articles sont modélisés séparément des commentaires et que deux personnes soumettent un commentaire sur une histoire, ceux-ci deviennent simplement deux documents de «commentaire» dans cette base de données, sans problème de conflit; juste deux opérations PUT pour ajouter deux nouveaux commentaires à la base de données "comment".

Ensuite, pour écrire les vues qui vous renvoient les commentaires d'un article, vous passeriez le postID, puis émettriez tous les commentaires faisant référence à cet ID de publication parent, triés dans un ordre logique. Peut-être que vous passez quelque chose comme [postID, byUsername] comme clé de la vue 'commentaires' pour indiquer le message parent et comment vous voulez que les résultats soient triés ou quelque chose du genre.

MongoDB gère les documents un peu différemment, permettant aux index d'être construits sur des sous-éléments d'un document, donc vous pourriez voir la même question sur la liste de diffusion MongoDB et quelqu'un disant "faites simplement des commentaires une propriété du message parent".

En raison du verrouillage en écriture et de la nature mono-maître de Mongo, le problème de révision conflictuel de deux personnes ajoutant des commentaires ne surgirait pas là-bas et la capacité de requête du contenu, comme mentionné, n'est pas trop mal affectée en raison de sous- index.

Cela étant dit, si vos sous-éléments dans l'un ou l'autre DB sont énormes (disons des dizaines de milliers de commentaires), je crois que c'est la recommandation des deux camps de créer ces éléments séparés; J'ai certainement vu que c'était le cas avec Mongo car il y a des limites supérieures sur la taille d'un document et de ses sous-éléments.


Très utile. Merci
Ray Suelzer
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.