MongoDB sort en version 2.2

Posté par  (site web personnel) . Édité par Nÿco, Nils Ratusznik, Bruno Michel, jcr83, Cyprien, passant·e et NeoX. Modéré par baud123. Licence CC By‑SA.
Étiquettes :
31
29
août
2012
Base de données

Une nouvelle version de la base de données MongoDB est sortie ce mercredi. Il s'agit de la version 2.2 qui fait suite à la version 2.0 (le versionnement adopte un système pair/impair : pair pour les versions stables, impair pour les versions en développement).

Pour rappel, MongoDB (humongous database) est une base de données développée par la société 10gen. Elle s'inscrit dans le mouvement NoSQL (Not Only SQL), très à la mode. Les forces de MongoDB viennent avant tout de sa simplicité : aucun schéma, installation simplissime, peu de concept complexes à maîtriser avant de l'utiliser. C'est un système de gestion de base de données orientée documents, extensible, écrit en langage C++ et distribué sous licence AGPL. Plus d'information sur la base de données sur Wikipedia ou sur le site de MongoDB.

Dans cette dépêche, nous étudierons les nouveautés proposées par cette nouvelle version. Les plus pressés iront consulter les notes de version tandis que les pointilleux pourront s'intéresser à la liste des bugs corrigés par cette version.

Les avancées importantes proposées par MongoDB 2.2 sont :

  • le framework d’agrégation ;
  • l'amélioration de la gestion de la concomitance ;
  • l'ajout de collection TTL ;
  • le raffinement du routage des requêtes.

Framework d'agrégation

L'utilisation de MapReduce rebutait probablement les utilisateurs de MongoDB (NDLR : à tort probablement). La fonctionnalité était sujette à quelques bugs. Les développeurs de MongoDB ont donc décidé de publier un framework d'agrégation permettant de réaliser des requêtes plus complexes que les habituels find et count, permettant ainsi aux utilisateurs venant du monde SQL d'effectuer des calculs plus complexes côté base de données.

La syntaxe rappelle facilement cascading qui simplifie les requêtes sur les clusters Hadoop et s'en inspire probablement. Il permet, par exemple, de réaliser un équivalent de requête SQL group by et having facilement.

Ce framework fonctionne y compris sur les clusters utilisant le sharding. Aucune information en revanche n'est donnée pour faire fonctionner les requêtes d'agrégation sur les secondaries (NDLR : les esclaves).

Amélioration de la gestion de la concomitance

La gestion de la concomitance était un problème revenant régulièrement sur la liste de diffusion MongoDB ainsi que dans le bugtracker. Un des plus gros reproches fait à MongoDB est son système de verrou global au serveur (i.e. une seule opération d'écriture peut s’exécuter à un instant donné sur le serveur). L'approche a été revue et le verrou est désormais descendu au niveau de la database, ce qui devrait augmenter les performances générales de MongoDB en cas d'écritures concomitantes dans plusieurs bases. L'étape suivante sera de descendre le verrou au niveau de la collection puis au niveau du document mais ce n'est pas encore prévu (et c'est bien dommage !).

Les verrous ont donc été rendus plus fin, c'est à dire qu'il existe désormais plusieurs types de verrous selon la portée des opérations. On pourra trouver le détail sur la page dédiée.

L'ajout de collection TTL

Les collections TTL (time to live) permettent de stocker des documents ayant une certaine durée de vie, c'est à dire qu'ils disparaissent de la collection après un certain temps. Ce temps est un réglage global à la collection.
On peut facilement imaginer l'usage de telles collections : logs d'erreur, journaux d'évènements ou tout autre donnée volatile. Cette fonctionnalité vient en complément des collections à taille fixe (capped) agissant comme un buffer circulaire.
La fonctionnalité fonctionne dans les environnement répliqués et partition-nés.

Le raffinement du routage des requêtes

Deux améliorations sont au menu :

  • il est désormais possible de spécifier le type d'instance sur lesquelles les requêtes sont faites de manière plus fine. Dans les versions précédentes de MongoDB, on pouvait spécifier si on souhaitait lire sur le PRIMARY ou bien sur un SECONDARY. Grâce aux read preferences, on peut désormais demander, sur le PRIMARY à lire de préférence sur le PRIMARY mais en acceptant un SECONDARY, mais aussi sur un SECONDARY, de lire sur le SECONDARY ou bien sur un PRIMARY. On peut également étiqueter les instances et demander à lire de préférence sur une instance portant une certaine étiquette.
  • il est également possible de configurer plus finement le partionnement des données (sharding) en spécifiant une relation entre la clé de partionnement et la partition (shard) sur laquelle elle va aller. Dans les versions précédentes, cette répartition était décidée par MongoDB, on peut donc désormais influer et tenir compte de la topologie de son cluster. Par exemple, on pourra insérer les utilisateurs provenant d'un pays A dans les shards présent dans les datacenters du pays A.

Autres avancées

On notera également quelques améliorations moins importantes :

  • l'optimisation de certains types de requêtes ;
  • l'amélioration de la plupart des outils de diagnostics ;
  • la possibilité d'envoyer les logs via syslog ;
  • l'utilitaire mongoimport permet d'importer des documents d'une taille supérieure à 16 Mo ;
  • shell amélioré :

    • Unicode mieux pris en charge ;
    • fonctionnalités d'édition comme dans Bash en emacs-mode ;
    • prise en charge des commands multi-lignes dans l'historique ;
  • une amélioration de la propagation lors de la mise à jour de la configuration lors d'ajout ou de la suppression de serveurs.

Aller plus loin

  • # De la simplicité de MongoDB

    Posté par  (site web personnel, Mastodon) . Évalué à 10.

    C'est un mensonge. Déployer sérieusement MongoDB requiert beaucoup de rigueur, et même comme ça c'est loin d'être simple. J'avais dû réaliser les scripts de démarrage, de maintenance et de sauvegarde pour un jeu (qui devait scaler, mais qui n'a jamais dépassé les 16k users :o) ). Ben c'est franchement la croix et la bannière pour assurer un service correct, savoir ce qui se passe, comprendre pourquoi la réplication ne s'opère pas, comprendre pourquoi une collection a été créée avec le même sur deux serveurs d'un même shard, etc.

    Bref, pas si simple.

    • [^] # Re: De la simplicité de MongoDB

      Posté par  (site web personnel) . Évalué à 4.

      Effectivement, mongodb peut se révéler une technologie étonnante lorsqu'on l'utilise à grande échelle et dans le but d'en tirer une grande performance.
      En revanche, l'administration est simplissime tant qu'on reste dans des petits clusters (2 shards, 3 serveurs par shards) ce qui est le cas de la plupart des utilisateurs de mongodb.

      Un des points faibles de mongodb reste son design (lock global puis par database, un thread par connection entre autres). Mais son objectif de simplicité est, je pense, atteint.

      • [^] # Re: De la simplicité de MongoDB

        Posté par  . Évalué à 2.

        Le principe du NoSQL c'est justement de relâcher cette contrainte habituelle des bases de données relationnelles qu'est le contrôle absolu des données.
        La scalabilité ce n'est pas juste répartir ce qui se calculait sur un serveur. C'est beaucoup plus compliqué que cela. Déjà, quand on développe une appli parallèle, il est souvent assez complexe de savoir précisément ce qui se passe sur chacun des thread/process. Donc quand on applique ce principe à une appli, ça devient d'autant plus compliqué…
        Donc, quand on veut scaler réellement, effectivement, il y a une complexité, mais qui pour le moment existe dans tous les logiciels que j'ai pu tester :s

  • # Première appréciation

    Posté par  . Évalué à 1.

    Je suis arrivé à la leçon 13 de MongoDB et je ne suis déjà pas d'accord avec au moins deux principes. D'abord les opérateurs de comparaison: le style find( … {name: {$gt: …}} ne me plaît pas du tout. Puisqu'il s'agit de Javascript, je me serais attendu à passer un argument de type fonction ou callback.

    Je ne suis pas non plus d'accord avec le principe de fonctionnement de la méthode update (ou avec son nom), qui en réalité remplace un document par l'argument. Le nom de la fonction ne correspond donc pas à sa mise en oeuvre. Ça éviterait le loin-d'être-intuitif opérateur $set, un peu trop pour moi. Intuitivement, d'ailleurs la démo le montre, on s'attend à ce que les arguments de update ne mettent à jour que les propriétés spécifiées par l'argument. Donc soit le nom "update" est très mal choisi (auquel cas "replace" conviendrait bien mieux), soit sa mise en oeuvre devrait ne mettre à jour que les propriétés spécifiées.

    Sinon MongoDB est en effet très intuitif, en tous cas pour ce que j'en ai essayé.

    • [^] # Re: Première appréciation

      Posté par  . Évalué à 3.

      Hello, le problème de base, et la raison pour laquelle, à mon avis, toutes les requêtes ne fonctionnent pas avec des callbacks écrites en js, c'est que le js est à priori lent comparé au reste du code mongo en c++. Donc, si on veut faire de bonnes requêtes, il faut juste utiliser les structures de données descriptives pour exprimer la teneur du dataset, et laisser le js là ou la performance n'est plus critique, ou sur de plus petits jeux de données.

      • [^] # Re: Première appréciation

        Posté par  . Évalué à 2.

        […] la raison pour laquelle, à mon avis, toutes les requêtes ne fonctionnent pas avec des callbacks écrites en js, c'est que le js est à priori lent comparé au reste du code mongo en c++.

        Je comprends. Mais n'est-il pas possible d'optimiser l'exécution de telles fonctions, d'une manière ou d'une autre, de faire en sorte qu'elles n'aient qu'un impact limité sur les performances? C'est juste une question de curiosité.

    • [^] # Re: Première appréciation

      Posté par  (site web personnel) . Évalué à 5.

      Je pense que ton problème avec "update" se résout de lui même une fois que tu intègre bien que MongoDB est une DB orientée document . Quand tu update quelque chose, c'est forcement le document dans son ensemble. Penser que tu ne mets a jour que les champs spécifiés montre que tu penses encore en termes de tables relationnelles…

      • [^] # Re: Première appréciation

        Posté par  . Évalué à 5.

        Quand tu update quelque chose, c'est forcement le document dans son ensemble.

        Ce n'est qu'une manière trop hâtive AMHA de modéliser ce concept. Si je mets un document à jour, je le remplace rarement (jamais?) dans son intégralité! Ça, c'est le logiciel qui le fait. Quand je mets un document à jour, je modifie quelques phrases ou paragraphes mais jamais tout d'un coup. Jamais, dans aucun système informatique que j'aie eu l'occasion de [bien] connaître le concept de "mise à jour" ne correspond au remplacement intégral du système, ce qui s'apparente à une réinstallation. Pourquoi ce concept devrait-il être modifié dès lors qu'on s'adresse à des documents.

        D'ailleurs si la démo en ligne ajoute cet indice quant à l'utilisation de la commande update, c'est justement que ses concepteurs se sont préparés à ce qu'il y ait confusion, ce qui confirme en substance mon argumentation! Si c'était limpide dans leur réflexion, alors pourquoi persister dans cette direction, qu'ils savaient pertinemment source de confusion? (poil aux arpions.)

        Penser que tu ne mets a jour que les champs spécifiés montre que tu penses encore en termes de tables relationnelles…

        (Faudrait vraiment être dans ma tête pour affirmer ça! Allez, encore un petit effort…)

        Que nenni, je me réfère à tout ce que je connais, où le concept "mise à jour" est le remplacement d'une partie du système ou de l'objet auquel s'applique la mise à jour. Pas l'objet dans son intégralité, auquel cas un autre terme que "mise à jour" doit être envisagé.

        Et de toutes façons, "update" est un terme beaucoup trop généraliste pour être compris de manière irréfutable comme les concepteurs l'entendent. Plus évident à appréhender: "update_properties()", "update_metadata()", "update_document_descriptor()" ou encore "replace_document()". Ce sont juste des suggestions à brûle-pourpoint, pas nécessairement appropriées, mais qui veulent illustrer à quel point la précision des termes utilisés réduit le doute quant à la compréhension de leur nature et de leur mise en oeuvre.

        Un système est intuitif s'il se base sur des concepts connus et maîtrisés, par exemple le langage. Demander à ce qu'on considère qu'une mise à jour est un remplacement est une erreur. Demander aux utilisateurs et aux développeurs de donner une définition intuitivement contradictoire à des termes bien connus et dont la signification est bien maîtrisée, bien assimilée est une erreur.

        Je pense que ton problème avec "update" se résout de lui même une fois que tu intègre bien que MongoDB est une DB orientée document .

        (La bonne vieille rengaine du "changement de paradigme", si j'ai bien compris?)

        Je répète: faudrait vraiment être dans ma tête pour affirmer ça. Que je ne sois pas d'accord avec un concept ou une mise en œuvre ne signifie absolument pas que je ne l'ai pas… intégré, compris ou quelqu'autre forme de limitation intellectuelle dont je pourrais être affublé.

        • [^] # Re: Première appréciation

          Posté par  . Évalué à 2.

          Je ne souscris pas à ta distinction entre update et replace.

          Je tente une transposition dans le langage de programmation C. Selon tes critères, le code a = a + 1; serait un replace du document-variable a. Idem pour a = f(a);. Avec le vocabulaire MongoDB, ce serait un update de a, alors que selon toi un update correspond à a[0] = 0;. Mais cette dernière opération n'est pas prévue dans MongoDB, puisque le document est un élément atomique du stockage. À partir du moment où on intègre ce principe, la dénomination update me semble justifiée : on modifie un document de la même façon qu'on modifie une variable atomique.

          C'est peut-être plus clair quand on connait le principe sous-jacent. On présuppose que l'application cliente fonctionne à la façon d'un ORM classique : on interroge la base pour récupérer des données structurées en "modèles", on les traite côté client, on sauvegarde chaque modèle modifié. En pseudo-code :

          post = Post.find(postid) # fetch from DB
          post.addcomment(...)
          post.save()              # write back to DB (update)
          
          

          AMHA, MongoDB est particulièrement adapté à ce mode de fonctionnement basé sur un ORM à la Active Record.

          • [^] # Re: Première appréciation

            Posté par  . Évalué à 0.

            Je tente une transposition dans le langage de programmation C.

            Cette transposition est inappropriée. Je parle du langage textuel utilisé par la syntaxe. Tu ne peux comparer des symboles (+, -, *, /, =) avec du langage textuel (insert, update, set) car la compréhension et l'appréhension des deux ne repose pas sur les mêmes bases. On n'aborde pas un symbole et un mot de la même manière.

            C'est peut-être plus clair quand on connait le principe sous-jacent.

            Et c'est précisément là mon opposition: la syntaxe est intuitive si elle se suffit à comprendre les effets d'une opération (au sens général). Si "c'est plus clair en connaissant les principes sous-jacents" c'est précisément que la syntaxe n'est pas intuitive.

            Toute mon argumentation repose sur le simple fait que l'auteur de la démo s'attendait à ce qu'il y ait confusion, c-à-d à ce que la fonction update() n'est pas intuitive. Et je ne fais qu'expliquer en quoi elle ne l'est pas.

            Je te suggère de lire (si ce n'est déjà fait) mes commentaires plus bas et celui de lothiraldan.

      • [^] # Re: Première appréciation

        Posté par  . Évalué à 0.

        Une petite analogie, pour se détendre?

        MongoDB: J'ai mis à jour le verre de Canada Dry par du Whisky.
        FantastIX: Non, tu as remplacé le Canada Dry par du Whisky, je me fous du verre!
        MongoDB: Mais non! J'ai mis à jour le Canada Dry par du Whisky.
        Le client: Sont devenus zinzins, m'en vais voir au café d'en face!

    • [^] # Re: Première appréciation

      Posté par  . Évalué à 4.

      Tout d'abord, pour les opérateurs de comparaison, il faut se rappeler que MongoDB, bien qu'ayant un client Javascript, est une base de donnée C++ et surtout qu'il est utilisé depuis de nombreux languages différents. C'est déjà assez le bordel quand on veut faire un map/reduce depuis python (on doit passer des fonctions js sous forme de string), encore heureux que ça ne soit pas du js pour juste les comparaisons.

      Ensuite concernant la méthode update, je vois pas trop de quoi tu parle. Ex:
      > db.test.find()
      > db.test.save({'a': 'a', 'b': 'b', 'c': 'c'})
      > db.test.find()
      { "_id" : ObjectId("503f653894950c048f876c37"), "a" : "a", "b" : "b", "c" : "c" }
      > db.test.update({'a': 'a'}, {$set: {'b': 'd'}})
      > db.test.find()
      { "_id" : ObjectId("503f653894950c048f876c37"), "a" : "a", "b" : "d", "c" : "c" }

      J'ai plutôt l'impression que quand tu fais fais ton update, le critère ne match rien et dans ce cas crée un nouveau document. (cf http://www.mongodb.org/display/DOCS/Updating#Updating-%7B%7Bupserts%7D%7D)

      Sinon, ayant déjà utilise l'Aggregation Framework, c'est un outil très puissant et son arrivée fait du bien (plus besoin d'écrire du js depuis un autre language (dsl pour le troll, j'ai pas put me retenir)).

      • [^] # Re: Première appréciation

        Posté par  . Évalué à 4.

        Ensuite concernant la méthode update, je vois pas trop de quoi tu parle.

        db.test.update({'a': 'a'}, {$set: {'b': 'd'}})
        
        

        J'ai omis de préciser que c'est justement cet argument "$set" qui me pose ce problème de sémantique.

        Update a besoin d'un argument supplémentaire, $set, pour qu'on se ramène à ce que je conçois intuitivement d'une mise à jour, c-à-d la modification partielle des propriétés du document. Pour dire comme Coluche: «c'est plus long, faut faire le nœud!»

        J'irais même jusqu'à dire que l'usage de "update()" et "set$" conjointement remplissent des fonctions dont la signification intuitive de l'une est la mise en oeuvre de l'autre, à savoir update() = mise à jour partielle et $set = remplacement. De plus update( …. {property1: value} ) — donc sans l'argument $set — est, dans les faits, un remplacement du document. Bref…

        Considérons la situation suivante:

        • update( doc_id, array_of_properties ) met à jour les propriétés correspondantes du document. Si certaines propriétés de array n'existent pas dans le document, elles sont ajoutées. C'est l'équivalent d'une fusion entre les deux collections de propriétés, celles du document et l'argument.

        • replace( doc_id, array_of_properties ) remplace le[s propriétés du] document par les propriétés indiquées dans l'argument.

        Ces deux fonctions sont intuitives, parlent d'elles-mêmes, sans ambiguïté et n'ont pas besoin du degré de complexité (support de la fonction $set) pour correspondre à l'idée dont on s'en fait. Et là, je suis satisfait car je ne découvre pas de comportement caché ou implicite. (Et, surtout, j'ai pas besoin de lire la doc pour me rendre compte qu'on ne m'a pas tout dit à propos d'Henry^Wupdate()!)

        D'après la démo:

        db.unicorns.update( {_id: ObjectId('503f73fb563d8a0d850007b3')}, {weight: 55} )
        Well, something's not right, we lost the name, gender and vampire kill counter
        Ah, right, it turns out that update updates the entire document with the new value

        D'abord, il conviendrait de dire « it turns out that update replaces the entire document with the new value ». Le comportement décrit n'est pas une mise à jour mais un remplacement!

        Mon point de vue est que update, avec ses deux arguments, devrait se limiter à mettre à jour les propriétés qu'on lui demande. C'est d'ailleurs la définition d'une mise à jour.

        Allez, honnêtement, que signifie db.unicorns.update( {_id: ObjectId('503f73fb563d8a0d850007b3')}, {weight: 55} ) de manière intuitive, si l'on s'en tient uniquement à ce qu'on lit? «Mets-moi à jour ces propriétés-là dans le document dont l'ID est 503f73fb563d8a0d850007b3 avec ces valeurs-là.» L'élimination des autres propriétés est un comportement auquel on ne s'attend pas de manière naturelle. La preuve, puisque la démo l'affirme: « Ah, right, it turns out that update updates (replaces) the entire document with the new value »!

        Je pense pouvoir être difficilement plus explicite.

        • [^] # Re: Première appréciation

          Posté par  . Évalué à 3.

          Ok au temps pour moi, je suis d'accord que ça serait plus intuitif. Mais il ne faut pas oublier qu'il n'y a pas que $set qu'on peut utiliser avec update, ya aussi tout celà : http://www.mongodb.org/display/DOCS/Updating#Updating-ModifierOperations.

          Et comme on peut les combiner entre eut (on peut mettre un $inc et un $set dans la même requête), ça permet d'avoir une api cohérente je trouve.

          Par contre, le fait qu'on remplace le document quand on ne met de modifier n'est pas des plus intuitif, on est d'accord (même si c'est clairement documenté (http://www.mongodb.org/display/DOCS/Updating#Updating-update%28%29). Je suis pas sûr que l'avoir exposé à des débutants dans la démo soit une bonne idée, je connaissais pas cette fonctionnalité avant de te lire perso.

          • [^] # Re: Première appréciation

            Posté par  . Évalué à 4.

            Par contre, le fait qu'on remplace le document quand on ne met de modifier n'est pas des plus intuitif, on est d'accord

            Bien :-) .

            Mais il ne faut pas oublier qu'il n'y a pas que $set qu'on peut utiliser avec update, ya aussi tout celà : http://www.mongodb.org/display/DOCS/Updating#Updating-ModifierOperations.

            Je me doutais effectivement qu'il ne devait pas y avoir que $set mais, si on y réfléchit, il aurait été tout aussi simple, peut-être même plus, de définir inc(), set(), unset(), push(), etc en tant que fonctions, non? genre db.unicorns( {name: 'mathusalem'} ).set( {age: 'unknown'} ) . N'est-ce pas plus simple?

            Et comme on peut les combiner entre eut (on peut mettre un $inc et un $set dans la même requête), ça permet d'avoir une api cohérente je trouve.

            L'API est sans doute cohérente avec elle-même mais pas nécessairement intuitive. Si on sait que les modificateurs renvoient tous le document sur lequel ils agissent, pourquoi pas

                db.unicorns( {name: 'mathusalem'} ).set( {weight: 'unknown'} ).inc( {age: 900} )
            
            

            L'API actuelle apporte du bruit inutile; pourquoi imposer l'utilisation d'une méthode update() alors qu'on sait très bien (ou devrait savoir) quel sera l'effet du modificateur, surtout si ce modificateur a un nom suffisamment intuitif, ce qui a l'air d'être le cas. Au final, update() ne sert qu'à rappeler qu'on va faire une modification mais dont la portée ou la signification est déterminée par le modificateur qu'on lui passe, ce qui ajoute encore à la confusion possible. Donc éliminer update() (qui n'apporte rien) et convertir les modificateurs en fonctions serait plus logique et donnerait une syntaxe plus claire et facile à appréhender, selon moi.

            Ah oui, je préfère aussi mais alors nettement ça

                db.unicorns( {name: 'mathusalem'} ).unset( [age, weight] )
            
            

            à ceci

                db.unicorns( {name: 'mathusalem'} ).unset( {age: 1, weight: 1} )
            
            

            Le ": 1" est du bruit inutile, {age: 0} n'a pas plus de sens que {age: 42}; cette syntaxe devrait être simplifiée.

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.