3. Modélisation de bases NoSQL

Ce chapitre est consacré à la notion de document qui est à la base de la représentation des données dans l’ensemble du cours. Cette notion est volontairement choisie assez générale pour couvrir la large palette des situations rencontrées: une valeur atomique (un entier, une chaîne de caractères) est un document; une paire clé-valeur est un document; un tableau de valeurs est un document; un agrégat de paires clé-valeur est un document; et de manière générale, toute composition des possibilités précédentes (un tableau d’agrégats de paires clé-valeur par exemple) est un document.

Nos documents sont caractérisés par l’existence d’une structure, et on parlera donc de documents structurés. Cette structure peut aller du très simple au très compliqué, ce qui permet de représenter de manière autonome des informations arbitrairement complexes.

Deux formats sont maintenant bien établis pour représenter les documents structurés: XML et JSON. Le premier est très complet mais très lourd, le second a juste les qualités inverses. Ces formats sont, entre autres, conçus pour que le codage des documents soit adapté aux échanges dans un environnement distribué. Un document en JSON ou XML peut être transféré par réseau entre deux machines sans perte d’information et sans problème de codage/décodage.

Il s’ensuit que les documents structurés sont à la base des systèmes distribués visant à des traitements à très grande échelle, autrement dit le “NoSQL” pour faire bref. Plusieurs de ces systèmes utilisent directement XML et surtout JSON, mais le modèle utilisé par d’autres est le plus souvent, à la syntaxe près, tout à fait équivalent. Ce chapitre se concentre sur JSON. XML, beaucoup plus riche, est un peu trop complexe pour les systèmes NoSQL. Quoique son étude soit pertinente et instructive dans le cadre de ce cours, j’ai choisi de placer le contenu relatif à la gestion de bases XML en complément, tout le matériel étant maintenant rassemblé dans le chapitre Complément: Bases XML. Je vous invite fortement à le consulter, au moins la première session consacrée à la modélisation.

Il est donc tout à fait intéressant d’étudier la construction de documents structurés comme base de la représentation des données. Une question très importante dans cette perspective est celle de la modélisation préalable de collections de documents. Cette modélisation est une étape essentielle dans la construction de bases relationnelles, et assez négligée pour les bases NoSQL où on semble parfois considérer qu’il suffit d’accumuler des données sans se soucier de leur forme. Ce chapitre aborde donc la question, ne serait-ce que pour vous sensibiliser: construire une collection de documents comme une décharge de données est une très mauvaise idée et se paye très cher à terme.

Important

Ce chapitre (comme tous les autres) se termine par un Quiz destiné à vous permettre une auto-évaluation: vous devriez pouvoir y répondre si vous avez bien assimilé les notions du chapitre. Sinon il est fortement conseillé de relire et d’approfondir.

S1: documents structurés

Représentation de documents structurés

L’objectif est donc de représenter des informations plus ou moins complexes en satisfaisant les besoins suivants:

  • Flexibilité: la structure doit pouvoir s’adapter à des variations plus ou moins importantes; prenons un document représentant un livre ou une documentation technique: on peut avoir (ou non) des annexes, des notes de bas de pages, tout un ensemble d’éléments éditoriaux qu’il faut pouvoir assembler souplement.
  • Richesse de la structure: il faut pouvoir imbriquer des tableaux, des listes, des agrégats pour créer des structures complexes.
  • Autonomie: quand deux systèmes échangent un document, toutes les informations doivent être incluses dans la représentation; en particulier, les données doivent être auto-décrites: le contenu vient avec sa propre description.
  • Sérialisation: la sérialisation désigne la capacité à coder un document sous la forme d’une séquence d’octets qui peut “voyager” sans dégradation sur le réseau, une propriété essentielle dans le cadre d’un système distribué.

D’une manière générale, les documents structurés sont des graphes dont chaque partie est auto-décrite. Commençons par la structure de base: les paires (clé, valeur). En voici un exemple, codé en JSON.

"nom": "philippe"

Et le même, codé en XML.

<nom>philippe</nom>

La construction (clé, valeur) permet de créer des agrégats. Voici un exemple JSON, dans lequel les agrégats sont codés avec des accolades ouvrante/fermante.

{"nom": "Philippe Rigaux", "tél": 2157786,  "email": "philippe@cnam.fr"}

En XML, il faut “nommer” l’agrégat.

<personne>
  <nom>Philippe Rigaux</nom>
  <tel>2157786</tel>
  <email>philippe@cnam.fr</email>
</personne>

On constate tout de suite que le codage XML est beaucoup plus bavard que celui de JSON.

Important

les termes varient pour désigner ce que nous appelons agrégat; on pourra parler d’objet (JSON), d’élément (XML), de dictionnaire (Python), de tableau associatif (PHP), de hash map (Java), etc. D’une manière générale ne vous laissez pas troubler par la terminologie variable, et ne lui accordez pas plus d’importance qu’elle n’en mérite. Un agrégat est un ensemble de paires (clé, valeur), chaque clé étant unique.

Nous avons parlé de la nécessité de composer des structures comme condition essentielle pour obtenir une puissance de représentation suffisante. Sur la base des paires (clé, valeur) et des agrégats vus ci-dessus, une extension immédiate par composition consiste à considérer qu’un agrégat est une valeur. On peut alors créer une paire clé-valeur dans laquelle la valeur est un agrégat, et imbriquer les agrégats les uns dans les autres, comme le montre l’exemple ci-dessous.

{
 "nom": {
         "prénom": "Philippe",
         "famille": "Rigaux"
        },
 "tél": 2157786,
 "email": "philippe@cnam.fr"
}

Continuons à explorer les structures de base. Une liste est une valeur constituée d’une séquence de valeurs. Les listes sont représentés en JSON (où on les appelle tableaux) avec des crochets ouvrant/fermant.

[2157786, 2498762]

Une liste est une valeur, et on peut donc l’associer à une clé dans un agrégat.

{nom: "Philippe", "téls": [2157786, 2498762] }

XML en revanche ne connaît pas explicitement la notion de tableau. Tout est uniformément représenté par balisage.

<personne>
  <nom>philippe</nom>
  <tels>
    <tel>2157786</tel>
    <tel>2498762</tel>
  </tels>
</personne>

Note

Un des inconvénients de XML est qu’il existe plusieurs manières de représenter les mêmes données, ce qui donne lieu à des réflexions et débats inutiles. Un langage comme JSON propose un ensemble minimal et suffisant de structures, représentées avec concision. La puissance de XML ne vient pas de sa syntaxe mais de la richesse des normes et outils associés.

Et voilà pour l’essentiel. On peut faire des agrégats d’agrégats d’agrégats, sans limitation de profondeur. On peut combiner des agrégats, des listes et des paires (clé, valeur) pour créer des structures riches et flexibles. Enfin, le codage (JSON ou XML) donne à ce que nous appellerons maintenant “documents” l’autonomie et la robustesse (pour des transferts sur le réseau) attendu.

Les documents sont des graphes

Prenons un peu de hauteur pour comprendre le modèle de données. Un document structuré représente en fait un arbre dans lequel on représente à la fois le contenu (les valeurs) et la structure (les noms des clés et l’imbrication des constructeurs élémentaires). La figure Représentation arborescente (JSON: arêtes étiquetées par les clés) montre deux arbres correspondant aux exemples qui précèdent. Les noms sont sur les arêtes, les valeurs sur les feuilles.

_images/label_on_edges1.png

Fig. 3.1 Représentation arborescente (JSON: arêtes étiquetées par les clés)

Le codage associe bien une structure (l’arbre) et le contenu (le texte dans les feuilles). Une autre possibilité est de représenter à la fois la structure et les valeurs comme des nœuds. C’est ce que fait XML (figure Représentation arborescente (XML: clés représentées par des nœuds)).

_images/simple_xml_tree.png

Fig. 3.2 Représentation arborescente (XML: clés représentées par des nœuds)

Abrégé de la syntaxe JSON

Résumons maintenant la syntaxe de JSON qui remplace, il faut bien le dire, tout à fait avantageusement XML dans la plupart des cas à l’exception sans doute de documents “rédigés” contenant beaucoup de texte: rapports, livres, documentation, etc. JSON est concis, simple dans sa définition, et très facile à associer à un langage de programmation (les structures d’un document JSON se transposent directement en structures du langage de programmation, valeurs, listes et objets).

Note

JSON est l’acronyme de JavaScript Object Notation. Comme cette expression le suggère, il a été initialement créé pour la sérialisation et l’échange d’objets Javascript entre deux applications. Le scénario le plus courant est sans doute celui des applications Ajax dans lesquelles le serveur (Web) et le client (navigateur) échangent des informations codées en JSON. Cela dit, JSON est un format texte indépendant du langage de programmation utilisé pour le manipuler, et se trouve maintenant utilisé dans des contextes très éloignés des applications Web.

C’est le format de données principal que nous aurons à manipuler. Il est utilisé comme modèle de données natif dans des systèmes NoSQL comme MongoDB, CouchDB, CouchBase, RethinkDB, et comme format d’échange sur le Web par d’innombrables applications, notamment celles basées sur l’architecture REST que nous verrons bientôt.

La syntaxe est très simple et a déjà été en grande partie introduite précédemment. Elle est présentée ci-dessous, mais vous pouvez aussi vous référer à des sites comme http://www.json.org/.

Valeurs simples

La structure de base est la paire (clé, valeur) (key-value pair).

"title": "The Social network"

Qu’est-ce qu’une valeur? On distingue les valeurs atomiques et les valeurs complexes (construites). Les valeurs atomiques sont:

  • les chaînes de caractères (entourées par les classiques apostrophes doubles anglais (droits)),
  • les nombres (entiers, flottants)
  • les valeurs booléennes (true ou false).

Voici une paire (clé, valeur) où la valeur est un entier (NB: pas d’apostrophes).

"year": 2010

Et une autre avec un Booléen (toujours pas d’apostrophes).

"oscar": false

Valeurs complexes

Les valeurs complexes sont soit des objets (agrégats de paires clé-valeur) soit des listes (séquences de valeurs). Un objet est un ensemble de paires clé-valeur dans lequel chaque clé n’apparaît qu’une fois. C’est ce que nous avons appelé agrégat dans un contexte plus général.

{"last_name": "Fincher", "first_name": "David", "oscar": true}

Un objet est une valeur complexe et peut être utilisé comme valeur dans une paire clé-valeur.

"director": {
  "last_name": "Fincher",
  "first_name": "David",
  "birth_date": 1962,
  "oscar": true
}

Une liste (array) est une séquence de valeurs dont les types peuvent varier: Javascript est un langage non typé et les tableaux peuvent contenir des éléments hétérogènes, même si ce n’est sans doute pas recommandé. Une liste est une valeur complexe, utilisable dans une paire clé-valeur.

"actors": ["Eisenberg", "Mara", "Garfield", "Timberlake"]

La liste suivante est valide, bien que contenant des valeurs hétérogènes.

"bricabrac": ["Eisenberg", 1948, {"prenom", "Philippe", "nom": "Rigaux"}, true, [1, 2, 3]]

Ici, on peut commencer à réfléchir: imaginez que vous écriviez une application qui doit traiter un document come celui ci-dessus. Vous savez que c’est une liste (du moins vous le supposez), mais vous ne savez pas du tout à priori quelles valeurs elle contient. Pendant le parcours de la liste, vous allez donc devoir multiplier les tests pour savoir si vous avez affaire à un entier, à une chaîne de caractères, ou même à une valeur complexe, liste, ou objet. Bref, vous devez, dans votre application, effectuer le “nettoyage” et les contrôles qui n’ont pas été faits au moment de la constitution du document. Ce point est un aspect très négatif de la production incontrolée de documents (faiblement) structurés, et de l’absence de contraintes (et de schéma) qui est l’une des caractéristiques (négatives) commune aux système NoSQL. Il est développé dans la prochaine section.

L’imbrication est sans limite: on peut avoir des tableaux de tableaux, des tableaux d’objets contenant eux-mêmes des tableaux, etc. Pour représenter un document avec JSON, nous adopterons simplement la contrainte que le constructeur de plus haut niveau soit un objet (en bref, document et objet sont synonymes).

{
  "title": "The Social network",
  "summary": "On a fall night in 2003, Harvard undergrad and \n
     programming genius Mark Zuckerberg sits down at his \n
     computer and heatedly begins working on a new idea. (...)",
  "year": 2010,
  "director": {"last_name": "Fincher",
                "first_name": "David"},
  "actors": [
    {"first_name": "Jesse", "last_name": "Eisenberg"},
    {"first_name": "Rooney", "last_name": "Mara"}
  ]
}

Les exercices ci-dessous vous invitent à commencer à récupérer des documents JSON.

Exercices

Exercice Ex-S1-1: validation d’un document JSON

Comment savoir qu’un document JSON est bien formé (c’est-à-dire syntaxiquement correct)? Il existe des validateurs en ligne, bien utiles pour détecter les fautes.

Essayez par exemple http://jsonlint.com/: copiez-collez les documents JSON donnés précédemment dans le validateur et vérifiez qu’ils sont correct (ou pas...).

Savez-vous quel est le jeu de caractères utilisé pour JSON? Cherchez sur le Web. Savez-vous comment on peut représenter de longues chaînes de caractères (comme le résumé du film)? Cherchez (aide: regardez en particulier comment gérer les sauts de ligne).

Le document suivant contient (beaucoup) d’erreurs, à vous de les corriger. Cherchez-les visuellement, puis aidez-vous du validateur.

{
  "title": "Taxi driver",
  "year": 1976,
  "genre": "drama",
  "summary": 'Vétéran de la Guerre du Vietnam, Travis Bickle est chauffeur de
         taxi dans la ville de New York. La violence quotidienne l'affecte peu à peu.',
  "country": "USA",
 "director":     {
   "last_name": "Scorcese",
   first_name: "Martin",
   "birth_date": "1962"
  },
 "actors": [
   {
   first_name: "Jodie",
   "last_name": "Foster",
   "birth_date": null,
   "role": "1962"
   }
   {
   first_name: "Robert",
   "last_name": "De Niro",
   "birth_date": "1943",
   "role": "Travis Bickle ",
   }
}

Exercice Ex-S1-2: nos bases de test

Nous allons récupérer aux formats JSON le contenu d’une base de données relationnelle pour disposer de documents à structure forte. Pour cela, rendez-vous sur le site http://webscope.bdpedia.fr (il s’agit d’un site illustrant le développement MySQL/PHP, rédigé par Philippe Rigaux). Sur ce site vous trouverez un menu Export: vous pouvez sélectionner des films et exporter leur représentation en JSON ou XML.

La fonctionnalité d’export illustre plusieurs possibilités et permet de les combiner, ce qui vous permet d’apprécier concrètement les caractéristiques de la modélisation semi-structurée.

  • export en format XML ou JSON;
  • sous forme de documents totalements indépendants les uns des autres, ou de documents liés par des références;
  • sous forme d’un seul fichier/document représentant l’ensemble de la base, ou avec un fichier/document par entité.

Votre objectif est de créer une hiérarchie de répertoires sur votre ordinateur local, dont la racine est un répertoire movies, avec le contenu suivant

  • un fichier movies.xml contenant tous les films, sans références, en XML;

  • un fichier movies.json contenant tous les films, sans références, en JSON;

  • un répertoire movies-xml, avec un fichier XML pour chaque film;

  • un répertoire movies-json, avec un fichier JSON pour chaque film;

  • un répertoire refs-xml, avec
    • un fichier movies-refs.xml, contenant les films et des références vers les artistes;
    • un fichier artists.xml, contenant des artistes, sans références.
    • un sous-répertoire movies avec un fichier XML pour chaque film, avec références;
    • un sous-répertoire artists avec un fichier XML pour chaque artiste.
  • un répertoire refs-json, comme le précédent, mais en JSON.

Vous n’êtes pas obligés de tout produire du premier coup! Mais nous nous servirons de ces documents pour nos test des bases documentaires XML et JSON ultérieurement. Vous pouvez toujours revenir à la base webscope pour les générer.

Exercice Ex-S1-3: JSON et l’Open Data

L’Open Data désigne le mouvement de mise à disposition des données afin de favoriser leur diffusion et la construction d’applications.

Les données sont fournies au format JSON ! Regardez les sites suivants, récupérez quelques documents, commencez à imaginer quel applications vous pourriez construire.

Exercice Ex-S1-4: produire un jeu de documents JSON volumineux

Pour tester des systèmes avec un jeu de données de taille paramétrable, nous pouvons utiliser des générateurs de données. Voici quelqes possibilités qu’il vous est suggéré d’explorer.

  • le site http://generatedata.com/ est très paramétrable mais ne permet malheureusement pas (aux dernières nouvelles) d’engendrer des documents imbriqués; à étudier quand même pour produire des tableaux volumineux;
  • https://github.com/10gen-labs/ipsum est un générateur de documents JSON spécifiquement conçu pour fournir des jeux de test à MongoDB, un système NoSQL que nous allons étudier.

Ipsum produit des documents JSON conformes à un schéma (http://json-schema.org). Un script Python (vous devez avoir un interpréteur Python installé sur votre machine) prend ce schéma en entrée et produit un nombre paramétrable de documents. Voici un exemple d’utilisation.

python ./pygenipsum.py --count 1000000 schema.jsch > bd.json

Lisez le fichier README pour en savoir plus. Vous êtes invités à vous inspirer des documents JSON produits par le site WebScope pour créer un schéma et engendrer une base de films avec quelques millions de documents. Pour notre base movies, vous pouvez récupérer le schéma JSON des documents. (Suggestion: allez jeter un œil à http://www.jsonschema.net/).

S2. Modélisation

Nous abordons maintenant une question très importante dans le cadre de la mise en œuvre d’une grande base de données constituée de documents: comment modéliser ces documents pour satifsaire les besoins de l’application? Et plus précisément:

  • quelle est la structure de ces documents?
  • quelles sont les contraintes qui portent sur le contenu des documents?

Cette question est bien connue dans le contexte des bases de données relationnelles, et nous allons commencer par rappeler la méthode bien établie. Pour les bases NoSQL, il n’existe pas de méthodologie équivalente. Une bonne (ou mauvaise) raison est d’ailleurs qu’il n’existe pas de modèle normalisé, et que la modélisation doit s’adapter aux caractéristiques de chaque système.

Note

Certains semblent considérer que la question ne se pose pas et qu’on peut entasser les données dans la base, n’importe comment, et voir plus tard ce que l’on peut en faire. C’est un(e absence de) choix porteur de redoutables conséquences pour la suite. La dernière partie de cette section donne mon avis à ce sujet.

Je vais donc extrapoler la méthodologie de conception relationnelle pour étudier ce que l’on peut obtenir avec un modèle de documents structurés.

Conception d’une base relationnelle

Note

Cette partie reprend de manière abrégée le contenu du chapitre “Conception d’une base de données” dans le support de cours Bases de données relationnelles. La lecture complète de ce chapitre est conseillée pour aller plus loin.

Voyons comment on pourrait modéliser notre base de films avec leurs réalisateurs et leurs acteurs. La démarche consiste à:

  • déterminer les “entités” (film, réalisateurs, acteurs) pertinentes pour l’application;
  • définir une méthode d’identification de chaque entité; en pratique on recourt à la définition d’un identifiant artificiel (il n’a aucun rôle descriptif) qui permet d’une part de s’assurer qu’une même “entité” est représentée une seule fois, d’autre part de référencer une entité par son identifiant.
  • préserver le lien entre les entités.

Voici une illustration informelle de la méthode, dans le contexte d’une base relationnelle où l’on suit une démarche fondée sur des règles de normalisation. Nous reprendrons ensuite une approche plus générale basée sur la notation Entité/association.

Méthode informelle

Commençons par les deux premières étapes. On va d’abord distinguer deux types d’entités: les films et les réalisateurs. On en déduit deux tables, celle des films et celle des réalisateurs.

Note

Comment distingue-t-on des entités et modélise-t-on correctement un domaine? Il n’y a pas de méthode magique: c’est du métier, de l’expérience, de la pratique, des erreurs, ...

Ensuite, on va ajouter à chaque table un attribut spécial, l’identifiant, désigné par id, dont la valeur est simplement un compteur auto-incrémenté. On obtient le résultat suivant.

id titre année
1 Alien 1979
2 Vertigo 1958
3 Psychose 1960
4 Kagemusha 1980
5 Volte-face 1997
6 Pulp Fiction 1995
7 Titanic 1997
8 Sacrifice 1986

La table des films.

id nom prénom année
101 Scott Ridley 1943
102 Hitchcock Alfred 1899
103 Kurosawa Akira 1910
104 Woo John 1946
105 Tarantino Quentin 1963
106 Cameron James 1954
107 Tarkovski Andrei 1932

La table des réalisateurs

Un souci constant dans ce type de modélisation est d’éviter toute redondance. Chaque film, et chaque information relative à un film, ne doit être représentée qu’une fois. La redondance dans une base de données est susceptible de soulever de gros problèmes, et notamment des incohérences (on met à jour une des versions et pas les autres, et on se sait plus laquelle est correcte).

Il reste à représenter le lien entre les films et les metteurs en scène, sans introduire de redondance. Maintenant que nous avons défini les identifiants, il existe un moyen simple pour indiquer quel metteur en scène a réalisé un film : associer l’identifiant du metteur en scène au film. L’identifiant sert alors de référence à l’entité. On ajoute un attribut idRéalisateur dans la table Film, et on obtient la représentation suivante.

id titre année idRéalisateur
1 Alien 1979 101
2 Vertigo 1958 102
3 Psychose 1960 102
4 Kagemusha 1980 103
5 Volte-face 1997 104
6 Pulp Fiction 1995 105
7 Titanic 1997 106
8 Sacrifice 1986 107

Cette représentation est correcte. La redondance est réduite au minimum puisque seule l’identifiant du metteur en scène a été déplacé dans une autre table. Pour peu que l’on s’assure que cet identifiant ne change jamais, cette redondance n’induit aucun effet négatif.

Cette représentation normalisée évite des inconvénients qu’il est bon d’avoir en tête:

  • pas de redondance, donc toute mise à jour affecte l’unique représentation, sans risque d’introduction d’incohérences;
  • pas de dépendance forte induisant des anomalies de mise à jour: on peut par exemple détruire un film sans affecter les informations sur le réalisateur, ce qui ne serait pas le cas s’ils étaient associés dans la même table (ou dans un même document: voir plus loin).

Ce gain dans la qualité du schéma n’a pas pour contrepartie une perte d’information. Il est en effet facile de voir qu’elle peut être reconstituée intégralement. En prenant un film, on obtient l’identifiant de son metteur en scène, et cet identifiant permet de trouver l’unique ligne dans la table des réalisateurs qui contient toutes les informations sur ce metteur en scène. Ce processus de reconstruction de l’information, dispersée dans plusieurs tables, peut s’exprimer avec les opérations relationnelles, et notamment la jointure.

Le modèle entité/association

Il reste à appliquer une méthode systématique visant à aboutir au résultat ci-dessus, et ce même dans des cas beaucoup plus complexes. Celle universellement adoptée (avec des variantes) s’appuie sur les notions d’entité et d’association. En voici une présentation très résumée.

La méthode permet de distinguer les entités qui constituent la base de données, et les associations entre ces entités. Un schéma E/A décrit l’application visée, c’est-à-dire une abstraction d’un domaine d’étude, pertinente relativement aux objectifs visés. Rappelons qu’une abstraction consiste à choisir certains aspects de la réalité perçue (et donc à éliminer les autres). Cette sélection se fait en fonction de certains besoins, qui doivent être précisément définis, et rélève d’une démarche d’analyse qui n’est pas abordée ici.

_images/films.png

Fig. 3.3 Le schéma E/A des films

Par exemple, pour notre base de données Films, on n’a pas besoin de stocker dans la base de données l’intégralité des informations relatives à un internaute, ou à un film. Seules comptent celles qui sont importantes pour l’application. Voici le schéma décrivant cette base de données Films (Fig. 3.3). On distingue

  • des entités, représentées par des rectangles, ici Film, Artiste, Internaute et Pays ;
  • des associations entre entités représentées par des liens entre ces rectangles. Ici on a représenté par exemple le fait qu’un artiste joue dans des films, qu’un internaute note des films, etc.

Chaque entité est caractérisée par un ensemble d’attributs, parmi lesquels un ou plusieurs forment l’identifiant unique (en gras). Nous l’avons appelé id pour Film et Artiste, code pour le pays. Le nom de l’attribut-identifiant est peu important, même si la convention id est très répandue.

Les associations sont caractérisées par des cardinalités. La notation 0..* sur le lien Réalise, du côté de l’entité Film, signifie qu’un artiste peut réaliser plusieurs films, ou aucun. La notation 0..1 du côté Artiste signifie en revanche qu’un film ne peut être réalisé que par au plus un artiste. En revanche dans l’association Donne une note, un internaute peut noter plusieurs films, et un film peut être noté par plusieurs internautes, ce qui justifie l’a présence de 0..* aux deux extrêmités de l’association.

Outre les propriétés déjà évoquées (simplicité, clarté de lecture), évidentes sur ce schéma, on peut noter aussi que la modélisation conceptuelle est totalement indépendante de tout choix d’implantation. Le schéma de la Fig. 3.3 ne spécifie aucun système en particulier. Il n’est pas non plus question de type ou de structure de données, d’algorithme, de langage, etc. En principe, il s’agit donc de la partie la plus stable d’une application. Le fait de se débarrasser à ce stade de la plupart des considérations techniques permet de se concentrer sur l’essentiel : que veut-on stocker dans la base ?

Schémas relationnels

La transposition d’une modélisation entité/association s’effectue sous la forme d’un schéma relationnel. Un tel schéma énonce la structure et les contraintes portant sur les données. À partir de la modélisation précédente, par exemple, on obtient les tables Film, Artiste et Role suivantes:

create table Artiste  (idArtiste integer not null,
                       nom varchar (30) not null,
                       prenom varchar (30) not null,
                       anneeNaiss integer,
                       primary key (idArtiste),
                       unique (nom, prenom))

 create table Film  (idFilm integer not null,
                    titre    varchar (50) not null,
                    annee    integer not null,
                    idRealisateur    integer not null,
                    genre varchar (20) not null,
                    resume      varchar(255),
                    codePays    varchar (4),
                    primary key (idFilm),
                    foreign key (idRealisateur) references Artiste);

 create table Role (idFilm  integer not null,
                   idActeur Iinteger not null,
                   nomRole  varchar(30),
                   primary key (idActeur,idFilm),
                   foreign key (idFilm) references Film,
                   foreign key (idActeur) references Artiste);

Le schéma impose des contraintes sur le contenu de la base. On a par exemple spécifié qu’on ne doit pas trouver deux artistes avec la même paire de valeurs (prénom, nom). La contrainte not null indique qu’une valeur doit toujours être présente. Une contrainte très importante est la contrainte d’intégrité référentielle (foreign key): elle garantit par exemple que la valeur de idRéalisateur correspond bien à une clé primaire de la table Artiste. En d’autres termes: un film fait référence, grâce à idRéalisateur, à un artiste qui est représenté dans la base. Le système garantit que ces contraintes sont respectées.

Voici un exemple de contenu pour la table Artiste.

Tableau 3.1 Table des artistes
id nom prénom
11 Travolta John
27 Willis Bruce
37 Tarantino Quentin
167 De Niro Robert
168 Grier Pam

On peut remarquer que le schéma et la base sont représentés séparément, contrairement aux documents structurés où chaque valeur est associé à une clé qui indique sa signification. Ici, le placement d’une valeur dans un colonne spécifique suffit.

Voici un exemple pour la table des films, illustrant la notion de clé étrangère.

Tableau 3.2 Table des films
id titre année idRéal
17 Pulp Fiction 1994 37
57 Jackie Brown 1997 37

Une valeur de la colonne idReal, une clé étrangère, est impérativement la valeur d’une clé primaire existante dans la table Artiste. Cette contrainte forte est vérifiée par le système relationnel et garantit que la base est saine. Il est impossible de faire référence à un metteur en scène qui n’existe pas.

Dans une base relationnelle (bien conçue) les données sont cohérentes et cela apporte une garantie forte aux applications qui les manipulent: pas besoin de vérifier par exemple, quand on lit le film 17, que l’artiste avec l’identifiant 37 existe bien: c’est garanti par le schéma.

En contrepartie, la distribution des données dans plusieurs tables rend le contenu de chacune incomplet. Le système de référencement par clé étrangère en particulier ne donne aucune indication directe sur l’entité référencée, d’où des tables au contenu succinct et non interprétable. Voici la table Role.

Tableau 3.3 Table des rôles
idFilm idArtiste rôle
17 11 Vincent Vega
17 27 Butch Coolidge
17 37 Jimmy Dimmick

En la regardant, on ne sait pas grand chose: il faut aller voir par exemple, pour le premier role, que le film 17 est Pulp Fiction, et l’artiste 11, John Travolta. En d’autres termes, il faut effectuer une opération rapprochant des données réparties dans plusieurs tables. Un système relationnel nous fournit cette opération: c’est la jointure. Voici comment on reconstituerait l’information sur le rôle “Vincent Vega” en SQL.

select titre, nom, prénom, role
from Film, Artiste, Role
where role='Vincent Vega'
and Film.id = Role.idFilm
and Artiste.id = Role.idActeur

La représentation des informations relatives à une même “entité” (un film) dans plusieurs tables a une autre conséquence qui motive (parfois) le recours à une représentation par document structuré. Il faut de fait effectuer plusieurs écritures pour une même entité, et donc appliquer une transaction pour garantir la cohérence des mises à jour. On peut considérer que ces précautions et contrôles divers pénalisent les performances (pour des raisons claires: assurer la cohérence de la base).

Note

Pour la notion de transaction, reportez-vous au chapitre introductif de http://sys.bdpedia.fr qui lui est consacré.

Ce qu’il faut retenir

En résumé, les caractéristiques d’une modélisation relationnelle sont

  • Un objectif de normalisation qui vise à éviter à la fois toute redondance et toute perte d’information;

    1. la redondance est évitée en découpant les données avec une granularité fine, et en les stockant indépendamment les unes des autres;
    2. la perte d’information est évitée en utilisant un système de référencement basé sur les clés primaires et clés étrangères.
  • Les données sont contraintes par un schéma qui impose des règles sur le contenu de la base

  • Il n’y a aucune hiérarchie dans la représentation des entités; une entité comme Pays, qui peut être considérée comme secondaire, a droit à sa table dédiée, tout comme l’entité Film qui peut être considérée comme essentielle; on ne pré-suppose pas en relationnel, l’importance respective des entités représentées;

  • La distribution des données dans plusieurs tables est compensée par la capacité de SQL à effectuer des jointures qui exploitent le plus souvent le système de référencement (clé primaire, clé étrangère) pour associer des lignes stockées séparément.

  • Plusieurs écritures transactionnelles peuvent être nécessaires pour créer une seule entité.

Ce modèle est cohérent. Il fonctionne très bien, depuis très longtemps, au moins pour des données fortement structurées comme celles que nous étudions ici. Il permet de construire des bases pérennes, conçues en grand partie indépendamment des besoins ponctuels d’une application, représentant un domaine d’une manière suffisament générique pour satisfaire tous les types d’accès, mêmes s’ils n’étaient pas envisagés au départ.

Voyons maintenant ce qu’il en est avec un modèle de document structuré.

Conception NoSQL avec documents structurés

En relationnel, on a des lignes (des nuplets pour être précis) et des tables (des relations). Dans le contexte du NoSQL, on va parler de documents et de collections (de documents).

Documents et collections

Notons pour commencer que la représentation arborescente est très puissante, plus puissante que la représentation offerte par la structure tabulaire du relationnel. Dans un nuplet relationnel, on ne trouve que des valeurs dites atomiques, non décomposables. Il ne peut y avoir qu’un seul genre pour un film. Si ce n’est pas le cas, il faut (processus de normalisation) créer une table des genres et la lier à la table des films (je vous laisse trouver le schéma correspondant, à titre d’exercice). Cette nécessité de distribuer les données dans plusieurs tables est une lourdeur souvent reprochée à la modélisation relationnelle.

Avec un document structuré, il est très facile de représenter les genres comme un tableau de valeurs, ce qui rompt la première règle de normalisation.

{
  "title": "Pulp fiction",
  "year": "1994",
  "genre": ["Action", "Policier", "Comédie"]
  "country": "USA"
}

Par ailleurs, il est également facile de représenter une table par une collection de documents structurés. Voici la table des artistes en notation JSON.

 [
  artiste:  {"id": 11, "nom": "Travolta", "prenom": "John"},
  artiste:  {"id": 27, "nom": "Willis", "prenom": "Bruce"},
  artiste:  {"id": 37, "nom": "Tarantino", "prenom": "Quentin"},
  artiste:  {"id": 167, "nom": "De Niro", "prenom": "Robert"},
  artiste:  {"id": 168, "nom": "Grier", "prenom": "Pam"}
]

On pourrait donc “encoder” une base relationnelle sous la forme de documents structurés, et chaque document pourrait être plus complexe structurellement qu’une ligne dans une table relationnelle.

D’un autre côté, une telle représentation, pour des données régulières, n’est pas du tout efficace à cause de la redondance de l’auto-description: à chaque fois on répète le nom des clés, alors qu’on pourrait les factoriser sous forme de schéma et les représenter indépendamment (ce que fait un système relationnel, voir ci-dessus).

L’auto-description n’est valable qu’en cas de variation dans la structure, ou éventuellement pour coder l’information de manière autonome en vue d’un échange. Une représentation arborescente XML / JSON est donc plus appropriée pour des données de structure complexe et surtout flexible.

Le pouvoir de l’imbrication des structures

Dans une modélisation relationnelle, nous avons dû séparer les films et les artistes dans deux tables distinctes, et lier chaque film à son metteur en scène par une clé étrangère. Grâce à l’imbrication des structures, il est possible avec un document structuré de représenter l’information de la manière suivante:

{
  "title": "Pulp fiction",
  "year": "1994",
  "genre": "Action",
  "country": "USA",
  "director": {
    "last_name": "Tarantino",
    "first_name": "Quentin",
    "birth_date": "1963"
  }
}

On a imbriqué un objet dans un autre, ce qui ouvre la voie à la représentation d’une entité par un unique document complet.

Important

Notez que nous n’avons plus besoin du système de référencement par clés primaires / clés étrangères, remplacé par l’imbrication qui associe physiquement les entités film et artiste.

Prenons l’exemple du film “Pulp Fiction” et son metteur en scène et ses acteurs. En relationnel, pour reconstituer l’ensemble du film “Pulp Fiction”, il faut suivre les références entre clés primaires et clés étrangères. C’est ce qui permet de voir que Tarantino (clé = 37) est réalisateur de Pulp Fiction (clé étrangère idRéal dans la table Film, avec la valeur 37) et joue également un rôle (clé étrangère idArtiste dans la table Rôle).

Tout peut être représenté par un unique document structuré, en tirant parti de l’imbrication d’objets dans des tableaux.

{
  "title": "Pulp fiction",
  "year": "1994",
  "genre": "Action",
  "country": "USA",
  "director": {
    "last_name": "Tarantino",
    "first_name": "Quentin",
    "birth_date": "1963" },
  "actors": [
    {"first_name": "John",
     "last_name": "Travolta",
     "birth_date": "1954",
     "role": "Vincent Vega" },
    {"first_name": "Bruce",
     "last_name": "Willis",
     "birth_date": "1955",
     "role": "Butch Coolidge" },
    {"first_name": "Quentin",
     "last_name": "Tarantino",
     "birth_date": "1963",
     "role": "Jimmy Dimmick"}
   ]
  }

Nous obtenons une unité d’information autonome représentant l’ensemble des informations relatives à un film (on pourrait bien entendu en ajouter encore d’autres, sur le même principe). Ce rassemblement offre des avantages forts dans une perspective de performance pour des collections à très grande échelle.

  • Plus besoin de jointure: il est inutile de faire des jointures pour reconstituer l’information puisqu’elle n’est plus dispersée, comme en relationnel, dans plusieurs tables.

  • Plus besoin de transaction (?): une écriture (du document) suffit; pour créer toutes les données du film “Pulp fiction” ci-dessus, il faudrait écrire 1 fois dans la table Film, 3 fois dans la table Artiste; 3 fois dans la table Role.

    De même, une lecture suffit pour récupérer l’ensemble des informations.

  • Adaptation à la distribution. Si les documents sont autonomes, il est très facile des les déplacer pour les répartir au mieux dans un système distribué; l’absence de lien avec d’autres documents donne la possibilité d’organiser librement la collection.

Cela semble séduisant... De plus, les transactions et les jointures sont deux mécanismes assez compliqués à mettre en œuvre dans un environnement distribué. Ne pas avoir à les implanter simplifie considérablement la création de systèmes NoSQL, d’où la prolifération à laquelle nous assistons. Tout système sachant faire des put() et des get() peut prétendre à l’appellation !

Mais il y a bien entendu des inconvénients.

Les inconvénients

En observant bien le document ci-dessus, on réalise rapidement qu’il introduit cependant deux problèmes importants.

  • Hiérarchisation des accès: la représentation des films et des artistes n’est pas symétrique; les films apparaissent près de la racine des documents, les artistes sont enfouis dans les profondeurs; l’accès aux films est donc privilégié (on ne peut pas accéder aux artistes sans passer par eux) ce qui peut ou non convenir à l’application.
  • Perte d’autonomie des entités. Il n’est plus possible de représenter les informations sur un metteur en scène si on ne connaît pas au moins un film; inversement, en supprimant un film (e.g., Pulp Fiction), on risque de supprimer définitivement les données sur un artiste (e.g., Tarantino).
  • Redondance: la même information doit être représentée plusieurs fois, ce qui est tout à fait fâcheux. Quentin Tarantino est représenté deux fois, et en fait il sera représenté autant de fois qu’il a tourné de films (ou fait l’acteur quelque part).

En extrapolant un peu, il est clair que la contrepartie d’un document autonome contenant toutes les informations qui lui sont liées est l’absence de partage de sous-parties potentiellement communes à plusieurs documents (ici, les artistes). On aboutit donc à une redondance qui mène immanquablement à des incohérences diverses.

Par ailleurs, on privilégie, en modélisant les données comme des documents, une certaine perspective de la base de données (ici, les films), ce qui n’est pas le cas en relationnel où toutes les informations sont au même niveau. Avec la représentation ci-dessus par exemple, comment connaître tous les films tournés par Tarantino? Il n’y a pas vraiment d’autre solution que de lire tous les documents, c’est compliqué et surtout coûteux.

Ce sont des inconvénients majeurs, qui risquent à terme de rendre la base de données inexploitable. Il faut bien les prendre en compte avant de se lancer dans l’aventure du NoSQL. D’autant que ...

Et le schéma?

Les systèmes NoSQL (à quelques exceptions près, cf. Cassandra) ne proposent pas de schéma, ou en tout cas rien d’équivalent aux schémas relationnels. Il existe un gain apparent: on peut tout de suite, sans effectuer la moindre démarche de modélisation, commencer à insérer des documents. Rapidement la structure de ces documents change, et on ne sait plus trop ce qu’on a mis dans la base qui devient une véritable poubelle de données.

Si on veut éviter cela, c’est au niveau de l’application effectuant des insertions qu’il faut effectuer la vérification des contraintes qu’un système relationnel peut nativement prendre en charge. Il faut également, pour toute application exploitant les données, effectuer des contrôles puisqu’il n’y a pas de garantie de cohérence ou de complétude.

L’absence de schéma est (à mon avis) un autre inconvénient fort des systèmes NoSQL.

Ma conclusion: Relationnel ou NoSQL?

Note

Ce qui suit constitue un ensemble de conclusions que je tire personnellement des arguments qui précèdent. Je ne cherche pas à polémiquer, mais à éviter de gros soucis à beaucoup d’enthousiastes qui penseraient découvrir une innovation mirifique dans le NoSQL. Contre-arguments et débats sont les bienvenus!

La (ma) conclusion de ce qui précède est que les systèmes NoSQL sont beaucoup moins puissants, fonctionnellement parlant, qu’un système relationnel. Ils présentent quelques caractéristiques potentiellement avantageuses dans certaines situations, essentiellement liés à leur capacité à passer à l’échelle comme système distribué. Ils ne devraient donc être utilisés que dans des situations très précises, et rarement rencontrées. Résumons les inconvénients:

  • Un modèle de données puissant, mais menant à des représentations asymétriques des informations.

    Certaines applications seront privilégiées, et d’autres pénalisées. Une base de données est (de mon point de vue) beaucoup plus pérenne que les applications qui l’exploitent, et il est dangereux de concevoir une base pour une application initiale, et de s’apercevoir qu’elle est inadaptée ensuite.

  • Pas de jointure, pas de langage de requêtes et en tout cas non normalisé.

    Cela implique une chute potentielle extrêmement forte de la productivité. Êtes-vous prêts à écrire un programme à chaque fois qu’il faut effectuer une mise à jour, même minime?

  • Pas de schéma, pas de contrôle sur les données.

    Ne transformez pas votre base en déchèterie de documents! La garantie de ce que l’on va trouver dans la base évite d’avoir à multiplier les tests dans les applications.

  • Pas de transactions.

    Une transaction assure la cohérence des données (cf. le support en ligne http://sys.bdpedia.fr). Êtes-vous prêts à baser un siste de commerce électronique sur un système NoSQL qui permettra de livrer des produits sans garantir que vous avez été payé?

D’une manière générale, ce qu’un système NoSQL ne fait pas par rapport à un système relationnel doit être pris en charge par les applications (contrôle de cohérence, opérations de recherche complexes, vérification du format des documents). C’est potentiellement une grosse surcharge de travail et un risque (comment garantir que les contrôles ou tests sont correctement implantés?).

Alors, quand peut-on recourir un système NoSQL? Il existe des niches, celles qui présentent une ou plusieurs des caractéristiques suivantes:

  • Des données très spécifiques, peu ou faiblement structurées. graphes, séries temporelles, données textuelles et multimédia. Les systèmes relationnels se veulent généralistes, et peuvent donc être moins adaptés à des données d’un type très particulier.
  • Peu de mises à jour, beaucoup de lectures. C’est le cas des applications de type analytique par exemple: on écrit une fois, et ensuite on lit et relit pour analyser. Dans ce cas, la plupart des inconvénients ci-dessus disparaissent ou sont minorés.
  • De très gros volumes. Un système relationnel peut souffrir pour calculer efficament des jointures pour de très gros volumes (ordre de grandeur: des données dépassant les capacités d’un unique ordinateur, soit quelques TéraOctets à ce jour). Dans ce cas on peut vouloir dénormaliser, recourir à un système NoSQL, et assumer les dangers qui en résultent.
  • De forts besoins en temps réel. Si on veut obtenir des informations en quelques ms, même sur de très grandes bases, certains systèmes NoSQL peuvent être mieux adaptés.

Voilà ! Un cas typique et justifié d’application est celui de l’accumulation de données dans l’optique de construire des modèles statistiques. On accumule des données sur le comportement des utilisateurs pour construire un modèle de recommandation par exemple. La base est alors une sorte d’entrepôt de données, avec des insertions constantes et aucune mise à jour des données existantes.

NoSQL = Not Only SQL. En dehors de ces niches, je pense très sincèrement que dans la plupart des cas le relationnel reste un meilleur choix et fournit des fonctionnalités beaucoup plus riches pour construire des applications. Le reste du cours vous permettra d’apprécier plus en profondeur la technicité de certains arguments. Après ce sera à vous de juger.

Exercices

Exercice Ex-S2-1: document = graphe

Représenter sous forme de graphe le film complet “Pulp Fiction” donné précédemment.

Exercice Ex-S2-2: Privilégions les artistes

Reprendre la petit base des films (les 3 tables données ci-dessus) et donner un document structuré donnant toutes les informations disponibles sur Quentin Tarantino. On veut donc représenter un document centré sur les artistes et pas sur les films.

Exercice Ex-S2-3: Comprendre la notion de document structuré

Vous gérez un site de commerce électronique et vous attendez des dizaines de millions d’utilisateurs (ou plus). Vous vous demandez quelle base de données utiliser: relationnel ou NoSQL?

Les deux tables suivantes représentent la modélisation relationnelle pour les utilisateurs et les visites de pages (que vous enregistrez bien sûr pour analyser le comportement de vos utilisateurs).

Tableau 3.4 Table des utilisateurs
id email nom
1 s@cnam.fr Serge
2 b@cnam.fr Benoît
Tableau 3.5 Table des visites
idUtil page nbVisites
1 http://cnam.fr/A 2
2 http://cnam.fr/A 1
1 http://cnam.fr/B 1

Proposez une représentation de ces informations sous forme de document structuré

  • en privilégiant l’accès par les utilisateurs;
  • en privilégiant l’accès par les pages visitées.

Puis proposez une modélisation relationnelle, et une sous forme de document structuré, pour représenter dans la base la liste des produits achetés par un utilisateur.

Exercice Ex-S2-4: extrait de l’examen du 16 juin 2016

Le service informatique du Cnam a décidé de représenter ses données sous forme de documents structurés pour faciliter les processus analytiques. Voici un exemple de documents centrés sur les étudiant.e.s et incluant les Unités d’Enseignement (UE) suivies par chacun.e.

{
  "_id": 978,
  "nom": "Jean Dujardin",
  "UE": [{"id": "ue:11", "titre": "Java", "note": 12},
        {"id": "ue:27", "titre": "Bases de données", "note": 17},
        {"id": "ue:37",  "titre": "Réseaux", "note": 14}
        ]
}
{
  "_id": 476,
   "nom": "Vanessa Paradis",
   "UE": [{"id":  "ue:13",  "titre": "Méthodologie", "note": 17,
          {"id": "ue:27",  "titre": "Bases de données", "note": 10},
          {"id":  "ue:76",   "titre": "Conduite projet", "note": 11}
         ]
}

Question 1: documents structurés et relationnel

Sachant que ces documents sont produits à partir d’une base relationnelle, reconstituez le schéma de cette base et indiquez le contenu des tables correspondant aux documents ci-dessus.

Question 2: hiérarchie des documents structurés

Proposez une autre représentation des mêmes données, centrée cette fois, non plus sur les étudiants, mais sur les UEs.

Avec les documents semi-structurés, on choisit de privilégier certaines entités, celles qui sont proches de la racine de l’arbre. En centrant sur les UEs, on obtient le même contenu, mais avec une représentation très différente.

S3: Cassandra, une base relationelle étendue

Note

Ce chapitre a été rédigé en 2016 en prenant comme point de départ le rapport de Guillaume Payen pour NFE204. Merci à lui.

Cassandra est un système de gestion de données à grande échelle conçu à l’origine (2007) par les ingénieurs de Facebook pour répondre à des problématiques liées au stockage et à l’utilisation de gros volumes de données. En 2008, ils essayèrent de le démocratiser en founissant une version stable, documentée, disponible sur Google Code. Cependant, Cassandra ne reçut pas un accueil particulièrement enthousiaste. Les ingénieurs de Facebook décidèrent donc en 2009 de faire porter Cassandra par l’Apache Incubator. En 2010, Cassandra était promu au rang de top-level Apache Project.

Apache a joué un rôle de premier plan dans l’attraction qu’a su créer Cassandra. La communauté s’est tellement investie dans le projet Cassandra que, au final, ce dernier a complètement divergé de sa version originale. Facebook s’est alors résolu à accepter que le projet - en l’état - ne correspondait plus précisément à leurs besoins, et que reprendre le développement à leur compte ne rimerait à rien tant l’architecture avait évolué. Cassandra est donc resté porté par l’Apache Incubator.

Aujourd’hui, c’est la société Datastax qui assure la distribution et le support de Cassandra qui reste un projet Open Source de la fondation Apache.

Cassandra a beaucoup évolué depuis l’origine, ce qui explique une terminologie assez erratique qui peut prêter à confusion. L’inspiration initiale est le système BigTable de Google, et l’évolution a ensuite plutôt porté Cassandra vers un modèle proche du relationnel, avec quelques différences significatives, notamment sur les aspects internes. C’est un système NoSQL très utilisé, et sans doute un bon point de départ pour passer du relationnel à un système distribué.

Installation

Avec Docker, il vous sera possible d’utiliser Cassandra dans un environnement virtuel. C’est de loin le mode d’installation le plus simple, il est rapide et ne pollue pas la machine avec des services qui tournent en tâche de fond et dont on ne se sert pas.

Le serveur

Reportez-vous au chapitre Préliminaires: Docker pour l’introduction à Docker. Vous devriez avoir une machine Docker disponibe, et accéder à un terminal Docker configuré pour dialoguer avec cette machine (ou utiliser Kitematic pour une simplicité maximale). En ligne de commande, entrez:

docker run --name mon-serveur-cassandra -p 3000:9042 \
    -e "CASSANDRA_TOKEN=1" -d spotify/cassandra:cluster

Pour l’interface CQL (que nous allons utiliser), c’est le port 9042 du conteneur qui doit être renvoyé vers un port de la machine Docker. Normalement, vous savez faire, sinon relisez encore et encore le chapitre sur Docker.

Note

Nous avons récupéré un tag bien spécifique de Cassandra, il s’agit du tag cluster. Il nous sera utile par la suite lorsque l’on voudra faire des essais sur la réplication. C’est aussi la raison pour laquelle il nous a été demandé de renseigner le token de placement dans le Hash Ring, dont la valeur ici a été fixée à 1. Des explications? Elles viendront dans les chapitres ultérieurs.

L’image Docker de cassandra est alors téléchargée et instanciée. Vérifiez-le en listant vos conteneurs:

$ docker ps

Notez l’adresse IP de la machine Docker. Nous sommes prêts à nous connecter au serveur Cassandra et à interagir avec la base de données.

Le client

Il vous faut un client sur la machine hôte. L’application cliente de base est l’interpréteur de commandes cqlsh, ce qui nécessite une installation des binaires Cassandra.

Des clients graphiques existent. Le plus complet (à ce jour) semble le Datastax DevCenter, qui impose malheureusement la création d’un compte chez Datastax (merci à eux quand même) et des sollicitations par la suite pour essayer de vous vendre des services Cassandra. C’est le client que j’utilise par la suite. La Fig. 3.5 montre l’interface, avec les fenêtres permettant d’explorer le schéma de la base et d’interroger cette dernière grâce au langage dédié CQL.

_images/DevCenter.png

Fig. 3.5 Le client DevCenter fourni par la société Datastax.

Le modèle de données

Cassandra est un système qui s’est progressivement orienté vers un modèle relationnel étendu, avec typage fort et schéma contraint. Initialement, Cassandra était beaucoup plus permissif et permettait d’insérer à peu près n’importe quoi.

Note

Méfiez-vous des “informations” qui trainent encore sur le Web, où Cassandra est par exemple qualifié de “column-store, avec une confusion assez générale due en partie aux évolutions du système, et en partie au fait que certains se contentent de répéter ce qu’ils ont lu quelque part sans se donner la peine de vérifier ou même de comprendre.

Comme dans un système relationnel, une base de données Cassandra est constituée de tables. Chaque table a un nom et est constituée de colonnes. Toute ligne (row) de la table doit respecter le schéma de cette dernière. Si une table a 5 colonnes, alors à l’insertion d’une entrée, la donnée devra être composée de 5 valeurs respectant le typage. Une colonne peut avoir différents types,

  • des types atomiques, comme par exemple entier, texte, date;
  • des types complexes (ensembles, listes, dictionnaires);
  • des types construits et nommés.

Cela vous rappelle quelque chose? Nous sommes effectivement proche d’un modèle de documents structurés de type JSON, avec imbrication de structures, mais avec un schéma qui assure le contrôle des données insérées. La gestion de la base est donc très contrainte et doit se faire en cohérence avec la structure de chaque table (son schéma). C’est une différence notable avec de nombreux systèmes NoSQL.

Important

Le vocabulaire encore utilisé par Cassandra est hérité d’un historique complexe et s’avère source de confusion. Ce manque d’uniformité et de cohérence dans la terminologie est malheureusement une conséquence de l’absence de normalisation des systèmes dits “No-SQL”. Dans tout ce qui suit, nous essayons de rester en phase avec les concepts (et leur nommage) présentés dans ce cours, d’établir le lien avec le vocabulaire Cassandra et si possible d’expliquer les raisons des écarts terminologiques. En particulier, nous allons utiliser document comme synonyme de row Cassandra, pour des raisons d’homogénéïté avec le reste de ce cours.

Paires clé/valeur (columns) et documents (rows)

La structure de base d’un document dans Cassandra est la paire (clé, valeur), autrement dit la structure atomique de représentation des informations semi-structurées, à la base de XML ou JSON par exemple. Une valeur peut être atomique (entier, chaîne de caractères) ou complexe (dictionnaire, liste).

Vocabulaire

Dans Cassandra, cette structure est parfois appelée colonne, ce qui est difficilement explicable au premier abord (vous êtes d’accord qu’une paire-clé/valeur n’est pas une colonne?). Il s’agit en fait d’un héritage de l’inspiration initiale de Cassandra, le système BigTable de Google dans lequel les données sont stockées en colonnes. Même si l’organisation finale de Cassandra a évolué, le vocabulaire est resté. Bilan: chaque fois que vous lisez “colonne” dans le contexte Cassandra, comprenez “paire clé-valeur” et tout s’éclaircira.

Versions

Il existe une deuxième subtilité que nous allons laisser de côté pour l’instant: les valeurs dans une paire clé-valeur Cassandra sont associées à des versions. Au moment où l’on affecte une valeur à une clé, cette valeur est étiquetée par l’estampille temporelle courante, et il est possible de conserver, pour une même clé, la série temporelle des valeurs successives. Cassandra, à strictement parler, gère donc des triplets (clé, estampille, valeur)*. C’est un héritage de BigTable, que l’on retrouve encore dans HBase par exemple.

L’estampille a une utilité dans le fonctionnement interne de Cassandra, notamment lors des phases de réconciliation lorsque des fichiers ne sont plus synchronisés suite à la panne d’un nœud. Nous y reviendrons.

Un document dans Cassandra est un identifiant unique associé à un ensemble de paires (clé, valeur). Il s’agit ni plus ni moins de la notion traditionnelle de dictionnaire que nous avons rencontrée dès le premier chapitre de ce cours et qu’il est très facile de représenter en JSON par exemple (ou en XML bien entendu).

Vocabulaire

Cassandra appelle row les documents, et row key l’identifiant unique. La notion de ligne (row) vient également de BigTable. Conceptuellement, il n’y a pas de différence avec les documents semi-structurés que nous étudions depuis le début de ce cours.

Dans les versions initiales de Cassandra, le nombre de paires clé-valeur (“colonnes”) constituant un document (ligne) n’était pas limité. On pouvait donc imaginer avoir des documents contenant des milliers de paires, tous différents les uns des autres. Ce n’est plus possible dans les versions récentes, chaque document devant être conforme au schéma de la table dans laquelle il est inséré. Les concepteurs de Cassandra ont sans doute considéré qu’il était malsain de produire des fourre-tout de données, difficilement gérables. La Fig. 3.6 montre un document Cassandra sous la forme de ses paires clés-valeurs

_images/cass-row.png

Fig. 3.6 Structure d’un document dans Cassandra

Les tables (column families)

Les documents sont groupés dans des tables qui, sous Cassandra, sont parfois appelées des column families pour des raisons historiques.

Vocabulaire

La notion de column family vient là encore de Bigtable, où elle avait un sens précis qui a disparu ici (pourquoi appeler une collection une “famille de colonnes?”). Transposez column family en collection et vous serez en territoire connu. Pour retrouver un modèle encore très proche de celui de BigTable, vous pouvez regarder le système HBase où les termes column family et column ont encore un sens fort.

Note

Il existe aussi des super columns, ainsi que des super column families. Ces structures apportent un réel niveau de complexité dans le modèle de données, et il n’est pas vraiment nécessaire d’en parler ici. Il se peut d’ailleurs ques ces notions peu utiles disparaissent à l’avenir.

La Fig. 3.7 illustre une table et 3 documents avec leur identifiant.

_images/cass-column-family.png

Fig. 3.7 Une table (column family) contenant 3 documents (rows) dans Cassandra

Bases (Keyspaces)

Enfin le troisième niveau d’organisation dans Cassandra est le keyspace, qui contient un ensemble de tables (column families). C’est l’équivalent de la notion de base de données, ensemble de tables dans le modèle relationnel, ou ensemble de collections dans des systèmes comme MongoDB.

Conception d’un schéma

Le modèle de données sur Cassandra est très influencé à l’origine par le système BigTable dont le plus proche héritier à ce jour est HBase. Cassandra en hérite principalement une terminologie assez dérourante et peu représentative d’une organisation assez classique structurée selon les niveaux base, table et document. Une fois dépassée ce petit obstacle, on constate une adoption des principes fondamentaux des systèmes documentaires distribués: des documents à la structure flexible construits sur la cellule (clé, valeur), entités d’information autonomes conçus pour le partitionnement dans un système distribué. Cassandra conserve quelques particularités provenant de BigTable (comme le versionnement des valeurs ou l’ajout de niveaux intermédiaires).

De nombreux conseils sont disponibles pour la conception d’un schéma Cassandra. Cette conception est nécessairement différente de celle d’un schéma relationnel à cause de l’absence du système de clé étrangère et de l’opération de jointure. C’est la raison pour laquelle de nombreux design patterns sont proposés pour guider la mise en place d’une architecture de données dans Cassandra qui soit cohérente avec les besoins métiers, et la performance que peut offrir la base de données.

Cassandra oblige à réfléchir en priorité à la façon dont le modèle de données va être utilisé. Quelles requêtes vont être exécutées? Dans quel sens mes données seront-elles traitées? C’est à partir de ces questions que pourra s’élaborer un modèle optimisé, dénormalisé et donc performant. L’inconvénient d’une démarche basée sur les besoins est que si ces derniers évoluent (ou si une application différente veut accéder à une base existante), l’organisation de la base devient inadaptée. Avec un système relationnel comme MySQL, le raisonnement est opposé: la disponibilité des jointures permet de se fixer comme but la normalisation du modèle de données afin de répondre à tous les cas d’usage possibles, éventuellement de manière non optimale.

En résumé:

  • Cassandra permet de stocker des tables dénormalisées dans lesquelles les valeurs ne sont pas nécessairement atomiques; il s’appuie sur une plus grande diversité de types (pas uniquement des entiers et des chaînes de caractères, mais des types construits comme les listes ou les dictionnaires).
  • La modélisation d’une architecture de données dans Cassandra est beaucoup plus ouverte qu’en relationnel ce qui rend notamment la modélisation plus difficile à évaluer, surtout à long terme.
  • La dénormalisation (souvent considérée comme la bête noire à pourchasser dans un modèle relationnel) devient recommandée avec Cassandra, en restant conscient que ses inconvénients (notamment la duplication de l’information, et les incohérences possibles) doivent être envisagés sérieusement.
  • En contrepartie des difficultés accrues de la modélisation, et surtout de l’impossibilté de garantir formellement la qualité d’un schéma grâce à des méthodes adaptées, Cassandra assure un passage à l’échelle par distribution basé sur des techniques de partitionnement et de réplication que nous détaillerons ultérieurement. C’est un système qui offre des performances jugées très satisfaisantes dans un environnement Big Data.

Créons notre base

À vous de vous retrousser les manches pour créer votre base Cassandra et y insérer nos films (ou toute autre jeu de données de votre choix). Les commandes de base sont données ci-dessous; elles peuvent être entrées directement dans l’interpéteur de commande, ou par l’intermédiaire d’un client graphique comme DevCenter.

Le keyspace

Note

L’éditeur DevCenter propose une interface de définition des keyspaces qui semble mieux fonctionner que l’exécution directe de la commande, d’après certains retours.

Rappelons que keyspace est le nom que Cassandra donne à une base de données. Cassandra est fait pour fonctionner dans un environnement distribué. Pour créer un keyspace, il faut donc préciser la stratégie de réplication à adopter. Nous verrons plus en détail après comment tout ceci fonctionne. Voici la commande:

CREATE KEYSPACE IF NOT EXISTS Movies
       WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor': 3 };

Une fois le keyspace créé, essayez les commandes suivantes (sous cqlsh uniquement).

cqlsh > DESCRIBE keyspaces;
cqlsh > DESCRIBE KEYSPACE Movies;

Avec un client graphique, il est facile d’explorer un keyspace.

Données relationnelles (à plat)

On peut traiter Cassandra comme une base relationnelle (en se plaçant du point de vue de la modélisation en tout cas). On crée alors des tables destinées à contenir des données “à plat”, avec des types atomiques. Commençons par créer une table pour nos artistes.

create table artists (id text,
               last_name text, first_name text,
               birth_date int, primary key (id)
              );

Je vous renvoie à la documentation Cassandra pour la liste des types atomiques disponibles. Ce sont, à peu de chose près, ceux de SQL. On peut noter que Cassandra fournit maintenant des commandes create table et describe table pour parler de ce qui s’appelait encore récemment column family.

L’insertion de données suit elle aussi la syntaxe SQL.

insert into artists (id, last_name, first_name, birth_date)
            values ('artist1', 'Depardieu', 'Gérard', 1948);
insert into artists (id, last_name, first_name, birth_date)
            values ('artist2', 'Baye', 'Nathalie', 1948);
insert into artists (id, last_name, first_name)
            values ('artist3', 'Marceau', 'Sophie');

On peut vérifier que l’insertion a bien fonctionné en sélectionnant les données.

select * from artists;

 id         | last_name   | first_name     | birth_date
------------+-------------+-----------------------------
  'artist1' | Depardieu   | Gérard         | 1948
  'artist2' | Baye        | Nathalie       | 1948
  'artist3' | Marceau     | Sophie         | null

À la dernière insertion, nous avons délibérément omis de renseigner la colonne birth_date, et Cassandra accepte la commande sans retourner d’erreur. Cette flexibilité est l’un des aspects communs à tous les modèles s’appuyant sur une représentation semi-structurée.

Il est également possible d’insérer à partir d’un document JSON en ajoutant le mot-clé JSON.

insert into artists JSON '{
     "id": "a1",
     "last_name": "Coppola",
     "first_name": "Sofia",
     "birth_date": "1971"
 }';

La structure du document doit correspondre très précisément (types compris) au schéma de la table, sinon Cassandra rejette l’insertion.

Note

Vous pouvez récupérer sur le site http://webscope.bdpedia.fr des commandes d’insertion Cassandra pour notre base de films.

Documents structurés (avec imbrication)

Cassandra va au-delà de la norme relationnelle en permettant des données dénormalisées dans lesquelles certaines valeurs sont complexes (dictionnaires, ensembles, etc.). C’est le principe de base que nous avons étudié pour la modélisation de document: en permettant l’imbrication on s’autorise la création de structures beaucoup plus riches, et potentiellement suffisantes pour représenter intégralement les informations relatives à une entité.

Note

Le concept de relationnel “étendu” à des types complexes est très ancien, et existe déjà dans des systèmes comme Postgres depuis longtemps.

Prenons le cas des films. En relationnel, on aurait la commande suivante:

create table movies (id text,
            title text,
            year int,
            genre text,
            country text,
         primary key (id) );

Tous les champs sont de type atomique. Pour représenter le metteur en scène, objet complexe avec un nom, un prénom, etc., il faudrait associer (en relationnel) chaque ligne de la table movies à une ligne d’une autre table représentant les artistes.

Cassandra permet l’imbrication de la représentation d’un artiste dans la représentation d’un film; une seule table suffit donc. Il nous faut au préalable définir le type artist de la manière suivante:

create type artist (id text,
                    last_name text,
                    first_name text,
                    birth_date int,
                    role text);

Et on peut alors créer la table movies en spécifiant que l’un des champs a pour type artist.

create table movies (id text,
                     title text,
                     year int,
                     genre text,
                     country text,
                     director frozen<artist>,
                     primary key (id) );

Notez le champ director, avec pour type frozen<artist> indiquant l’utilisation d’un type défini dans le schéma.

Note

L’utilisation de frozen semble obligatoire pour les types imbriqués. Les raisons sont peu claires pour moi. Il semble que frozen implique que toute modification de la valeur imbriquée doive se faire par remplacement complet, par opposition à une modification à une granularité plus fine affectant l’un des champs. Vous êtes invités à creuser la question si vous utilisez Cassandra.

Il devient alors possible d’insérer des documents structurés, comme celui de l’exemple ci-dessous. Ce qui montre l’équivalence entre le modèle Cassandra et les modèles des documents structurés que nous avons étudiés. Il est important de noter que les concepteurs de Cassandra ont décidé de se tourner vers un typage fort: tout document non conforme au schéma précédent est rejeté, ce qui garantit que la base de données est saine et respecte les contraintes.

INSERT INTO movies JSON '{
    "id": "movie:1",
    "title": "Vertigo",
    "year": 1958,
    "genre": "drama",
    "country": "USA",
    "director": {
        "id": "artist:3",
        "last_name": "Hitchcock",
        "first_name": "Alfred",
    "   birth_date": "1899"
    },
}';

Sur le même principe, on peut ajouter un niveau d’imbrication pour représenter l’ensemble des acteurs d’un film. Le constructeur set<...> déclare un type ensemble. Voici un exemple parlant:

create table movies (id text,
              title text,
              year int,
              genre text,
              country text,
              director frozen<artist>,
              actors set< frozen<artist>>,
           primary key (id) );

Je vous laisse tester l’insertion des documents tels qu’ils sont fournis par le site http://webscope.bdpedia.fr, avec tous les acteurs d’un film.

En résumé:

  • Cassandra propose un modèle relationnel étendu, basé sur la capacité à imbriquer des types complexes dans la définition d’un schéma, et à sortir en conséquence de la première règle de normalisation (ce type de modèle est d’ailleurs appelé depuis longtemps N1NF pour Non First Normal Form);
  • Cassandra a choisi d’imposer un typage fort: toute insertion doit être conforme au schéma;
  • L’imbrication des constructeurs de type, notamment les dictionnaires (nuplets) et les ensembles (set) rend le modèle comparable aux documents structurés JSON ou XML.

La suite du cours complètera progressivement la présentation de Cassandra.

Exercices

Exercice Ex-S3-1: mise en route de Cassandra

Votre tâche est simple: installer Cassandra, un client de votre choix (DevCenter recommandé), reproduire les commandes ci-dessus et créer une base movies avec nos films. Profitez-en pour vous familiariser avec l’interface graphique.

S4: MongoDB, une base JSON

Supports complémentaires

Voyons maintenant une base purement “documentaire” qui représente les données au format JSON. Il s’agit de MongoDB, un des systèmes NoSQL les plus populaires du moment. MongoDB est particulièrement apprécié pour sa capacité à passer en mode distribué pour répartir le stockage et les traitements: nous verrons cela ultérieurement. Ce chapitre se concentre sur MongoDB vu comme une base centralisée pour le stockage de documents JSON.

L’objectif est d’apprécier les capacités d’un système de ce type (donc, non relationnel) pour les fonctionnalités standard attendues d’un système de gestion de bases de données. Comme nous le verrons, MongoDB n’impose pas de schéma, ce qui peut être vu comme un avantage initialement, mais s’avère rapidement pénalisant puisque la charge du contrôle des données est reportée du côté de l’application; MongoDB propose un langage d’interrogation qui lui est propre (donc, non standardisé), pratique mais limité; enfin MongoDB n’offre aucun support transactionnel.

Les données utilisées en exemple ici sont celles de notre base de films, que vous pouvez récupérer sur le site du Webscope, http://webscope.bdpedia.fr/index.php?ctrl=xml. Choisissez bien entendu les exports en JSON. Si vous disposez de documents JSON plus proches de vos intérêts, vous êtes bien entendu invités à les prendre comme base d’essai.

MongoDB est un système libre de droits (pour sa version de base), téléchargeable à http://www.mongodb.org/downloads.

Installation de MongoDB

L’installation Docker se fait en 2 clics. MongoDB fonctionne en mode classique client/serveur. Le serveur mongod est en attente sur le port 27017 dans son conteneur, et peut être redirigé vers un port de la machine Docker.

Note

Dans ce qui suit, l’IP de la machine Docker est 192.168.99.100, et le port 32769. Remplacez évidemment par vos propres valeurs.

En ce qui concerne les applications clientes, nous avons en gros deux possibilités: l’interpréteur de commande mongo (qui suppose d’avoir installé MongoDB sur la machine hôte) ou une application graphique plus agréable à utiliser. Parmi ces dernières, des choix recommandables sont

  • RockMongo (http://rockmongo.com), une application Web d’administration de MongoDB à peu près équivalente à phpMyAdmin,
  • RoboMongo (http://robomongo.org), une interface graphique plus facile d’installation, mais assez limitée,
  • et enfin MongoChef (http://3t.io/mongochef/) qui me semble le meilleur client graphique du moment; il existe une version gratuite, qui ne vous expose qu’à quelques courriels de relance de la part des auteurs du système (vous pouvez en profiter pour les remercier gentiment).

Vous avez le choix. Dans ce qui suit je présente les commandes soit avec l’interpréteur mongo, soit avec MongoChef.

L’interpréteur de commandes mongo

L’interpréteur de commande suppose l’installation des binaires de MongoDB sur votre machine, ce qui se fait très facilement après les avoir téléchargé depuis http://www.mongodb.com. Il se lance comme suit:

$ mongo --host 192.168.99.100 --port 32769
  MongoDB shell version: 2.4.7
  connecting to: test
  >

La base par défaut est test. Cet outil est en fait un interpréteur javascript (ce qui est cohérent avec la représentation JSON) et on peut donc lui soumettre des instructions en Javascript, ainsi que des commandes propres à MongoDB. Voici quelques instructions de base.

  • Pour se placer dans une base:

    use <nombase>
    
  • Une base est constituée d’un ensemble de collections, l’équivalent d’une table en relationnel. Pour créer une collection:

    db.createCollection("movies")
    
  • La liste des collections est obtenue par:

    show collections
    
  • Pour insérer un document JSON dans une collection (ici, movies):

    db.movies.insert ({"nom": "nfe024"})
    

    Il existe donc un objet (javascript) implicite, db, auquel on soumet des demandes d’exécution de certaines méthodes.

  • Pour afficher le contenu d’une collection:

    db.movies.find()
    

    C’est un premier exemple d’une fonction de recherche avec MongoDB. On obtient des objets (javascript, encodés en JSON)

    { "_id" : ObjectId("5422d9095ae45806a0e66474"), "nom" : "nfe024" }
    

    MongoDB associe un identifiant unique à chaque document, de nom conventionnel _id, et lui attribue une valeur si elle n’est pas indiquée explicitement.

  • Pour insérer un autre document:

    db.movies.insert ({"produit": "Grulband", prix: 230, enStock: true})
    

    Vous remarquerez que la structure de ce document n’a rien à voir avec le précédent: il n’y a pas de schéma (et donc pas de contrainte) dans MongoDB. On est libre de tout faire (et même de faire n’importe quoi). Nous sommes partis pour mettre n’importe quel objet dans notre collection movies, ce qui revient à reporter les problèmes (contrôles, contraintes, tests sur la structure) vers l’application.

  • On peut affecter un identifiant explicitement:

    db.movies.insert ({_id: "1", "produit": "Kramölk", prix: 10, enStock: true})
    
  • On peut compter le nombre de documents dans la collection:

    db.movies.count()
    
  • Et finalement, on peut supprimer une collection:

    db.movies.drop()
    

C’est un bref aperçu des commandes. On peut se lasser au bout d’un certain temps d’entrer des commandes à la main, et préférer utiliser une interface graphique. Il n’existe pas d’interface native fournie par 10gen, l’entreprise qui développe MongoDB, mais on trouve beaucoup d’outils en Open Source (cf. la liste http://docs.mongodb.org/ecosystem/tools/administration-interfaces/).

Le client RoboMongo

RoboMongo est un client graphique disponible pour toutes les plate-formes à http://robomongo.org. L’installation est très simple, l’outil n’offre pas beaucoup plus (au moment où ces lignes sont écrites en tout cas) que l’interpréteur de commande, si ce n’est bien sûr l’ergonomie d’une interface graphique. La figure L’interface RoboMongo montre l’interface en action.

_images/RoboMongo.png

Fig. 3.8 L’interface RoboMongo

Le client MongoChef

Egalement un client graphique, mais avec des fonctionnalités beaucoup plus riches que RoboMongo, un interpréteur de commande intelligent (autocomplétion, exécution de scripts placés dans des fichiers), des fonctionnalités d’import et d’export. C’est le choix recommandé. Installation en quelques clics, là encore, sur toutes les plateformes. La figure L’interface de MongoChef montre l’interface en action.

_images/mongochef.png

Fig. 3.9 L’interface de MongoChef

Le client RockMongo

C’est une application PHP disponible ici: http://www.rockmongo.com. Il vous faut donc un serveur Apache/PHP, probablement déjà disponible sur votre machine. Si ce n’est pas le cas, c’est tellement standard que vous devriez pouvoir l’installer en 2 clics.

Il est nécessaire d’installer l’extension PHP de connexion à MongoDB. La méthode varie selon votre système: la page http://rockmongo.com/wiki/installation donne les indications nécessaires.

En ce qui concerne RockMongo, récupérez la dernière version sur le site ci-dessus, installez-la dans votre répertoire htdocs, et laissez la configuration par défaut. En accédant à http://localhost/rockmongo (en supposant que rockmongo soit le nom du répertoire dans htdocs), vous devriez accéder à une fenêtre de connexion. Le compte par défaut est admin / admin. Une fois connecté, l’affichage devrait ressembler à celui de la figure L’interface RockMongo.

_images/rockmongo.png

Fig. 3.10 L’interface RockMongo

Important

Ce qui précède est valable pour une installation sur une machine locale, avec un serveur mongod en local également.

L’interface ressemble à celle de phpMyAdmin, pour ceux qui connaissent. En particulier, la barre à gauche montre la liste des bases de données existantes,

Création de notre base

Nous allons insérer des documents plus sérieux pour découvrir les fonctionnalités de MongoDB. Notre base de films nous fournit des documents JSON, comme celui-ci par exemple:

{
  "_id": "movie:100",
  "title": "The Social network",
  "summary": "On a fall night in 2003, Harvard undergrad and
     programming genius Mark Zuckerberg sits down at his
     computer and heatedly begins working on a new idea. (...)",
  "year": 2010,
  "director": {"last_name": "Fincher",
                "first_name": "David"},
  "actors": [
    {"first_name": "Jesse", "last_name": "Eisenberg"},
    {"first_name": "Rooney", "last_name": "Mara"}
   ]
}

Comme il serait fastidieux de les insérer un par un, nous allons utiliser un utilitaire de chargement. Voici deux possibilités: l’utilitaire d’import de MongoDB, ou MongoChef.

L’utilitaire d’import de MongoDB prend en entrée un tableau JSON contenant la liste des objets à insérer. Dans notre cas, nous allons utiliser l’export JSON de la base Webscope dont le format est le suivant.

  [
   {
    "_id": "movie:1",
    "title": "Vertigo",
    "year": "1958",
    "director":   {
      "_id": "artist:3",
      "last_name": "Hitchcock",
      "first_name": "Alfred",
      "birth_date": "1899"
    },
    "actors": [
     {
      "_id": "artist:15",
      "first_name": "James",
      "last_name": "Stewart",
     },
     {
      "_id": "artist:16",
      "first_name": "Kim",
      "last_name": "Novak",
     }
    ]
  },
  {
    "_id": "movie:2",
    "title": "Alien",
    ...
  }
]

En supposant que ce tableau est sauvegardé dans movies.json, on peut l’importer dans la collection movies de la base nfe204 avec le programme utilitaire mongoimport (c’est un programme, pas une commande du client mongo) :

mongoimport -d nfe204 -c movies --file movies.json --jsonArray

Ne pas oublier l’argument jsonArray qui indique à l’utilitaire d’import qu’il s’agit d’un tableau d’objets à créer individuellement, et pas d’un unique document JSON.

Si vous utilisez MongoChef, il existe une option d’import de collection qui accepte un format légèrement différent de celui ci-dessus. Un fichier conforme à ce format est disponible à http://b3d.bdpedia.fr/files/movies-mongochef.json. Vous pouvez le télécharger et l’utiliser pour insérer directement les films dans la base avec MongoChef.

Exercices

Exercice Ex-S4-1: mise en route de MongoDB

Votre tâche est simple: installer MongoDB, le client RockMongo ou RoboMongo (ou un autre de votre choix), reproduire les commandes ci-dessus et créer une base movies avec nos films. Profitez-en pour vous familiariser avec l’interface graphique.

Vous pouvez également créer une base moviesref avec deux collections: une pour les films et l’autre pour les artistes, les premiers référençant les seconds. Les commandes d’import devraient être les suivantes:

mongoimport -d moviesref -c movies --file movies-refs.json --jsonArray
mongoimport -d moviesref -c artists --file artists.json --jsonArray

Quiz

  • Pourquoi peut-on dire que les documents structurés sont “auto-décrits”? Quel sont les avantages, quels sont les inconvénients?
  • Quel est l’intérêt (dans le cadre d’un système NoSQL à grande échelle) des structures riches et imbriquées qui permettent de construire des documents complexes?
  • Qu’appelle-t-on “codage” d’un document textuel? Quel est le codage d’un document JSON? d’un document XML (cherchez sur le Web)? Doit-on connaître le codage pour échanger des documents sur le Web?
  • La modélisation entité-association reste-elle utile pour des bases NoSQL? Comment pourrait-on déduire la structure de nos documents à partir du schéma de la Fig. 3.3?
  • Pourrait-on envisager de créer autant de documents qu’il existe de chemins d’accès aux données? Par exemple, notre base Films comprendrait des documents centrés sur les films (Pulp Fiction et tous ses acteurs), et d’autres centrés sur les réalisateurs (Tarantino et tous les films qu’il a tournés). Que diriez-vous des avantages et inconvénients?