MongoDB: Retour d'expérience

Comme avec toute technologie « nouvelle », j'étais extrêmement réservé quand à l'essor du NoSQL et son intérêt dans le cadre de mon quotidien de développeur fullstack. J'ai fini par m'y mettre par obligation professionnelle. Je ne pense pas en avoir fait le tour pour le moment, mon avis est donc à prendre avec précaution, mais je le livre comme un point de départ d'un débat qui sera je l'espère contructif.

MongoDB vs MySQL

La grande partie de mon expérience avec les bases de données s'est construite autour de MySQL. Je pense ne pas être le seul dans ce cas. C'est donc via le prisme d'un utilisateur avancé de MySQL que j'orienterai ce billet.

Pour illustrer les différences entre bases de données relationnelles et MongoDB, je vais partir d'un schéma de base de données simple et montrer comment selon moi, il faudrait le modèliser dans chacunes d'entre elles.

Cas concret: Un marketplace

J'ai choisi le cas concret d'une place de marché car c'est selon moi celui qui mettra le plus en exergue les différences de conception SQL et NoSQL.

Une place de marché est finalement assez simple. Nous avons des concepts que l'on retrouve dans à peu près toutes les applications :

On retrouve également des concepts propres aux places de marché :

Je simplifie, ici, volontairement la chose pour complexifier par itérations successives.

Modèle pour bases de données relationnelles

Typiquement, ce type d'architecture sera modélisée comme suit :

User(id, name, organisations[]:Organisation, home:Place, phone:Contact, mail:Contact, country:)
Organisation(id, name, owner:User.id, place:Place, country:)
Place(id, address, lat, lng)
Contact(id, type, value)
Country(id, name)

Product(id, reference, name, organisation:Organisation)
Price(id, amount, product:Product, country:Country)
Comment(id, content, parent:Comment, author:User, product:Product)
Delivery(id, date, place:Place, recipient:User, bill:Bill)
DeliveryRow(id, quantity, product:Product)
Bill(id, date, seller:Organisation, buyer:Organisation)
BillRow(id, quantity, product:Product, price)

Comme vous pouvez le constater, nous avons créé bien plus de concepts que ceux énumérés au départ. La raison est simple : la déduplication et la linéarisation. Quand on modélise une base de données relationnelle, on met un point d'honneur à éviter les doublons. On essaie également de faire entrer une structure de donnée arborescente dans un ensemble de tables interconnectées entre elles.

On essaie donc d'isoler un maximum de concepts enfants et on crée autant de cases que nécessaire pour les englober tout en décrivant leurs relations à l'aide de clés étrangères.

Autre avantage des bases de données relationnelles, les clés étrangères et le typage fort des divers champs de la base. La déclaration explicite des relations et des types permet à la base de données d'assurer un forte intégrité des données qu'elle stocke. Les transactions permettent également d'effectuer plusieurs opérations jusqu'à l'obtention d'une état intègre. Ceci permet de conserver encore une fois l'intégrité des données en cas d'échec d'une de ces modifications.

Ces avantages ont cependant un coût. En effet, il est nécessaire de maintenir un état global et de le propager entre toutes les instances du serveur de base de données. C'est pourquoi les bases de données relationnelles fonctionnent sur un schéma maître/esclave.

Ainsi, dès lors que l'on est confronté à une utilisation intense de la base de données, on constate une dégradation des performances et la nécessité de faire appel à un DBA et/ou à un Administrateur Système pour maintenir tant bien que mal ces performances et la disponibilité du système.

En revanche, un atout fort de la modélisation relationnelle est qu'une modélisation bien réalisée n'est pas censée évoluer au cours du temps sauf élargissement du domaine de l'application. Avec les bases de données relationnelles, on décrit une structure représentant l'essence même des données et on l'exploite ensuite sans même se soucier (ou presque) de son intégrité.

Ceci rend l'évolution du produit simple et fluide puisqu'à chaque nouvelle demande de fonctionnalité, on n'intervient que très peu sur le schéma de la base de données. Ceci, au prix d'une concession assumée : l'overhead des calculs et vérifications qui permettent à la base de données de préserver son intégrité et de la difficulté de rendre une telle architecture scalable.

Modélisation NoSQL avec MongoDB

C'est justement pour répondre à ces problèmes de scalabilité que MongoDB a été conçu. En effet, pour permettre une scalabilité horizontale, de nombreux principes propres aux bases de données relationnelles ont été volontairement écartés, nous y reviendrons.

Les transactions, les clés étrangères, les clés uniques auto-incrémentées sont donc absentes. En sus, le mode de stockage des données est également revu. Là où les bases de données relationnelles utilisent un mode de stockage par lignes et colonnes de taille généralement fixe, les bases de données telles que MongoDB proposent un stockage dans un document sans contraintes (sauf sa taille, nous le verrons après) bien souvent au format JSON autorisant un contenu arborescent (avec en théorie avec une infinité de niveaux).

L'utilisation d'une base de donnée NoSQL mène généralement à une conception bien différente :

Organisation {
  name: String,
  place: {
    address: String,
    latitude: Number,
    longitude: Number
  },
  users: [{
    id: ObjectId,
    name: String,
    home: {
      address: String,
      latitude: Number,
      longitude: Number
    },
    phone: String,
    mail: String,
    country: String,
    owner: Boolean
  }]
}

Product {
  id: ObjectId,
  reference: String,
  name: String,
  organisation_id: ObjectId,
  prices: [{
    id: ObjectId,
    amount: Number,
    country: String
  }],
  comments: [{
    id: ObjectId,
    content: String,
    author: ObjectId,
    parents_ids: [ObjectId]
  }]
}

Trade {
  id: ObjectId,
  buyer_id: ObjectId,
  buyer_name: String,
  seller_id: ObjectId,
  seller_name: String,
  items: {
    product_id: ObjectId,
    product_name: String,
    product_price: Number,
    product_reference: String,
    quantity: Number
  },
  deliveries: [{
    id: ObjectId,
    date: Date,
    place: {
      address: String,
      lat: Number,
      lng: Number
    }
  }]
}

Comme vous pouvez le voir, notre schéma est complètement différent. En effet, bien que la plupart de nos concepts aient été conservés, nous n'avons plus que trois collections au lieu d'une dizaine de tables.

Plusieurs choses peuvent choquer les afficionados des bases de données relationnelles :

< Blog