Devoxx 2013 : La mort de l'UPDATE ?
Après trois jours de Devoxx où j'ai assisté à de nombreuses présentations il me semble identifier une tendance forte pour l'avenir de notre profession. J'ai assisté à des conférences très différentes sur des sujets très variés. Elles ont un point commun.
Nous sommes à un jalon de notre profession. Nous ne travaillerons pas demain comme aujourd'hui. En effet, nous devrons trouver des solutions à un nouveau challenge : comment gérer l'augmentation des volumes à traiter sans pouvoir augmenter la puissance des traitements ?
La réponse est l'immuabilité. Un objet est construit puis n’évolue plus.
Dans pratiquement toutes les conférences auxquelles j'ai assisté, il y avait en filigrane ce concept. Nous n'avons plus le temps de faire le ménage dans les données car elles sont trop nombreuses. Alors pourquoi ne pas les garder ? Nous ne voulons plus gérer des verrous au risque d’avoir des dead-locks. Pourquoi ne pas travailler sur d’anciennes versions des objets, sans aucun verrou ?
Il n’est plus nécessaire de mettre à jour les données. Ajoutons simplement une nouvelle version. INSERT à la place de UPDATE. Les volumes à disposition permettent d'envisager cette nouvelle approche. Avec des bénéfices certains.
Lors de la conférence "Le temps c'est de l'argent. Prenez le en compte avec Distributed DDD, CQRS et EventSourcing", +Jérémie Chassaing (@thinkb4coding) nous présente une approche où les données ne sont pas consolidées dans une base de données. Ce sont toutes les modifications qui sont mémorisées, à un niveau transactionnel (Bounded contexts) pour garantir la consistance. L’état de la base de données est reconstruit en rejouant toutes les modifications. Avec une approche sémantique des modifications, il est possible d'extraire de nouvelles informations de l'évolution des données. Par exemple, un déménagement n'est pas équivalent à modifier une adresse. Des règles métiers peuvent exploiter cela pour proposer de nouveaux services. Comme il est facile de rejouer toutes les modifications, il est possible de faire évoluer ou d’enrichir le modèle. En partant de zéro ou d’un snapshot. Il n’est pas nécessaire de migrer une base de donnée. Il suffit de concevoir un nouveau schéma en mémoire. Si nécessaire, il est possible rejouer les millions d’évènements en batch, tous en continuant à enregistrer les évolutions. Le nouveau snapshot ainsi produit est alors déployé, puis les derniers évènements sont rejoués. Éventuellement, une base en lecture permet d'exposer des synthèses, des vues, adaptées à la version de l’application du moment, calculées en batch en parallèle, pendant la production. Les dernières requêtes sont appliquées juste après la mise à jour à chaud du nouveau schéma. C'est l'équivalent d’une approche comptable. Chaque écriture peut être compensée mais pas modifiée. Le solde des comptes peut être recalculé facilement si nécessaire, même si le programme le garde en mémoire. Pour optimiser le modèle on persiste le solde du mois précédent, mais il est possible de tous recalculer.
Avec cette approche, les données sont immuables. Elles ne sont jamais effacées. Le fichier des transactions augmente sans arrêt. Au fur et à mesure de l’évolution du programme, de nouvelles transactions sont proposées, plus riches, plus précises. C’est très pratique pour gérer les backups pendant la production.
Dans l'excellente conférence "Memory Systems and how to get the best out of them", Martin Thompson (@mjpt777) nous explique les différents caches présents dans les processeurs, sur disques magnétiques et statiques. Il nous montre qu'il est préférable d’avoir des objets proches en mémoire plutôt que répartis dans plein d’objets disparates un peu partout. Il est préférable d'ajouter à la fin des fichiers sur DDR plutôt que de les modifier. Les performances sont alors bien meilleures. APPEND plutôt que UPDATE.
La présentation “Objects and functions, conflict without a cause?” sur Scala par +Martin Odersky (@odersky) lors de la première keynote du jeudi, nous montre comment il est possible d'intégrer progressivement l'approche fonctionnelle à un langage objet vieillissant.
L'approche fonctionnelle est différente de l'approche objet. Chaque état d’un objet a une identité propre. La référence sur l’objet, le pointeur sur ce dernier, correspond à un état des valeurs à un moment donné. Chaque modification d'un état entraîne une modification de l'identité de l'objet, donc la mise à jour du pointeur. Il est ainsi possible de manipuler un état d’une grappe d’objets, comme si aucune modification n’était en cours. Le temps est figé, tant que le pointeur sur la racine de la grappe d’objet ne change pas. Cela permet de s’affranchir des problèmes des accès concurrents, car le temps est figé. Il s'agit de bouger la flèche du temps et non de modifier les données. Des structures de données sont conçues pour cela. Ajouter un objet à un conteneur c’est obtenir un nouveau conteneur avec le nouvel objet. L’ancienne version est toujours accessible pour les traitements en cours. Avec cette approche, il n'y a plus de problème de concurrence, d'unicité des caches, etc. Les modifications se traduisent par l’ajout d’objets et non par leurs modifications. On n’a plus besoin de variables mais de valeurs. Les valeurs sont immuables. Pas les variables. NEW plutôt que UPDATE.
“Flapmap Zat Shit : Les monades expliquées aux geeks” présenté par +François Sarradin (@fsarradin) démontre la puissance de ce langage.
Je pense que Scala va jouer le même rôle que le C++ lors de l'introduction des paradigmes objets. Avec le C++, il est possible de continuer à coder en C. Il est même autorisé de faire un code procédural avec des objets qui n'en sont pas. Ce n’est pas bien mais c'est possible. Les premiers programmes C++ étaient souvent ainsi. Cela a permis de diffuser l'approche objets en douceur. À l'époque, tout le monde disait que c'était trop compliqué, qu'il y avait trop de concepts, etc. Les mêmes reproches que pour Scala aujourd'hui. Qui remet en cause le modèle objet de nos jours ? Ensuite, une fois que les développeurs ont été convaincus de l'intérêt du modèle objet, il a été possible de proposer un nouveau langage : Java. Ce dernier a (trop) simplifié le C++. Sans le passage par le C++, Java ne serait pas où il est.
Scala est le cheval de Troie de l'approche fonctionnelle. Il permet d'écrire de mauvais programmes fonctionnels, avec des effets de bords, mélangeant l'approche objet et l'approche fonctionnelle. Et alors ? C'est une bonne chose. Lorsque les développeurs maîtriseront les nouveaux concepts, il sera temps de proposer éventuellement un nouveau langage.
Les présentations sur “Clojure” et “Gavagai” par +Nils Grunwald (@nilsgrunwald), de Frege par +Yorick Laupa (@yoeight) ou de Haskell par +Mathieu Châtaignier et Yves Parès (@YvesPares) confirment que l'approche fonctionnelle finira par s'imposer.
La présentation "What every hipster should know about functional programming" de +Bodil Stokke (@bodiltv) montre que les structures de données et les concepts des langages fonctionnels sont simples et peuvent être codés en quelques lignes de JavaScript.
Même la présentation “The lightning Memory-Mapped Database” de +Howard Chu (@hyc_symas) sur une implémentation extrêmement rapide d’une base de données codée en C, en remplacement de Berkeley DB, utilise une approche UPDATE with APPEND. Les performances sont incroyables.
Cela n’est pas nouveau. Le gestionnaire de source Git propose également une approche équivalente. La modification d’un seul caractère dans un fichier est mémorisée dans Git par une nouvelle copie de l’intégralité du fichier. Tout l’historique est présent et n’est jamais modifié. Les performances sont alors incroyables.
On retrouve également ce concept dans GMail (on archive, on ne détruit plus) ou dans les réseaux sociaux (Glups).
Lors de ma (@pprados) présentation sur “La mort prochaine du GC ?” un participant m’a fait part que pour les calculs financiers codés en Java, ils donnent suffisamment de mémoire à la JVM pour que le GC n’intervienne jamais pendant la journée. On ajoute des objets tant que possible et on nettoie lorsque cela ne gène plus.
Il faut revoir les bases du langage Java. Proposer une sémantique aux pointeurs. Par exemple, Ceylon présenté par +Stéphane Epardaud (@unfromage) et +Emmanuel Bernard (@emmanuelbernard), propose un flag sur les pointeurs acceptant la valeur null. D’autres concepts plus forts sont possibles, comme évoqué dans ma conférence.
Java est maintenant fortement endetté. Les évolutions minimes de JavaEE7 présentées par +David Delabassee (@delabassee) ou les difficultés qu'a le langage à trouver des solutions pour traiter les difficultés des ClassLoader par +Alexis Hassler (@AlexisHassler) démontrent que continuer avec un passif trop lourd devient un véritable frein. La présentation “Normal ou décaféiné ?” de +Alexis Moussine-Pouchkine (@alexismp) montre bien que Java est devenu une pile de valises en équilibre instable. Il est très difficile de maîtriser la consommation des ressources avec Java et donc le coût sur le Cloud.
Faut-il injecter du fonctionnel directement dans Java ? Changer vers un langage plus adapté avec Scala ? Prendre une approche plus radicale et s’affranchir de la JVM ? Je suis convaincu qu’il faut une rupture, une remise à plat. Partir sur de nouvelles bases. L’approche fonctionnelle présente des avantages certains. C’est une solution efficace pour gérer le multi-tâche, pour intégrer l’évolution des modèles, pour intégrer des agents distribués.
Quelle est la solution ? Je penche pour Scala dans un premier temps, avec ses avantages et ses défauts. C’est une étape comme l’a été le C++. Cela permet de laisser Java dans la course. Mais il faudra proposer des frameworks spécifiques Scala en lieu et place des frameworks Java. En exploitant dès le début les annotations, les surcharges d’opérateurs, les conteneurs immuables, etc. Cela permettra d’abandonner petit à petit les anciens frameworks Java.