Cette année, les Xebians étaient encore à Devoxx France. Ils vous proposent aujourd’hui de revenir sur leurs conférences « coup de coeur » du Vendredi.
Probabilistic Programming : Tuer un piéton ou sacrifier les passagers? – Didier Girard
Dans ce quickie, Didier Girard explique les bases de la programmation probabiliste (en réalité plutôt la classification probabiliste que la programmation probabiliste). Elle consiste en la création de systèmes qui prennent des décisions à partir d’observations dont la certitude n’est pas garantie.
Il commence son talk avec une photographie d’une personne de dos. La question est ici de savoir si c’est une femme ou un homme. En générant plusieurs modèles par rapport à l’entourage de la personne, nous classifions la photo. Par l’entourage, nous comprenons les faits suivants : la personne fait la queue pour aller aux toilettes hommes, les toilettes femmes sont fermées, etc. En rassemblant plusieurs conditions, nous arrivons alors à attribuer la classe la plus probable.
Dans un second exemple, Didier nous propose de déterminer si l’objet sur la photo est un Segway. Après avoir déterminé les éléments composant un Segway (les deux roues, le guidon), nous les recherchons sur une nouvelle photo pour déterminer la probabilité d’être face à un autre Segway.
Les deux exemples nous amènent à une analogie derrière une proposition d’un algorithme d’une voiture automatique. En analysant les images, le modèle donnera la probabilité que l’on voit le piéton sur la route sachant que l’on se trouve soit sur une autoroute soit sur une voie piétonne. La probabilité sera plus élevée si la voiture se trouve sur cette dernière.
En utilisant donc la classification bayésienne, nous analysons plusieurs caractéristiques indépendamment, afin d’attribuer une classe. Le speaker souligne que contrairement aux algorithmes de Deep Learning nous n’avons pas besoin d’autant de données d’entraînement, ce qui est fortement avantageux.
Ce que j’ai appris en construisant des systèmes distribués – Michaël Figuière
Dans ce talk, Michaël Figuière, ancien Xebian et actuellement ingénieur chez Netflix, nous parle des besoins qui ont favorisé la popularisation des systèmes distribués. Nous pouvons citer principalement :
- la capacité de supporter la charge croissante,
- la flexibilité de pouvoir augmenter/réduire la quantité d’instances,
- la maitrise de coût,
- et finalement, la gestion de la disponibilité.
Une fois que nous avons une vision claire de ces besoins, c’est le moment de définir comment les atteindre. Vu qu’il s’agit avant tout d’un problème d’architecture, nous abordons ensuite les styles plus populaires :
- Stateless. Il s’agit du style d’architecture le plus simple. Ici, tous les serveurs sont une copie de la même instance. Évidement, cette architecture est très adaptée pour supporter la charge croissante.
- Master-slave. Dans ce style, un membre du groupe est nommé maître et est le responsable de la gestion des écritures. Typiquement, le quorum est nécessaire pour arriver à la consistence. Cependant, son plus gros problème est l’indisponibilité lors de l’élection du master. Un exemple typique de ce style est mongodb.
- Multi-master. Chaque réplique est responsable de la gestion des écritures et des lectures. Donc, si un master se déconnecte du cluster, n’importe quel autre peut prendre le relais. Son avantage principal est une disponibilité encore plus importante. Un exemple de ce style est Cassandra.
Ensuite, Michaël nous parle des différents aspects à garder en tête pour tout système distribué :
- Failover. Le problème récent d’indisponibilité du service S3 a mis en évidence la méconnaissance / mauvaise gestion des erreurs (failover en anglais) dans nos systèmes dans le cloud. Certains aspects se montrent d’avantage critiques :
- Cascade de timeouts. Le fait qu’un service dépende d’autres services rend la gestion du timeout particulièrement critique. L’indisponibilité de l’un peut générer l’indisponibilité de toute la chaîne d’appel.
- Selection de noeud. Lors d’un pic de charge inattendu, le choix du nœud s’avère parfois très compliqué. En effet un simple round-robin n’est pas toujours suffisant. Nombreux sont les cas où une différence de vitesse de production et de consommation noie complètement un système. Une solution de back pressure est souvent la plus adéquate.
- Conscience d’emplacement. L’emplacement d’un nœud est très important. Chez Amazon, le choix de la région et de la zone de disponibilité influence fortement la manière dont votre système répond lors d’une erreur catastrophique. Par exemple Netflix est capable de basculer tout leur trafic d’une région à une autre en quelques minutes. Cette problématique a justement donné naissance à Simian Army.
- Retry. Pour finir, Michaël nous donne quelque conseils pour le rejeux des taches.
- Speculative retry est la technique utilisée chez Netflix pour les recommencements.
- Il nous conseille archaius comme alternative au JMX pour la mise à jour dynamique de la configuration.
- Spinakker semble être son alternative de choix au moment de la release.
- Et n’oubliez pas que l’idempotence va vous aider à dormir tranquille la nuit.
Pourquoi j’ai essayé de faire une API de détection de trolls – Jonathan Michaux
Il est connu que l’anonymat fourni par un user-name, sur n’importe quel réseau social ou plateforme de commentaire, a permis une diffusion de troll à une échelle jamais vue auparavant. Déjà, qu’est-ce qu’est un troll ? Une définition possible est la suivante : provocation volontaire d’une réaction adverse grâce à une polémique hors sujet pendant une discussion. Il existe même une loi empirique, la loi de Godwin, selon laquelle « Plus une discussion en ligne dure longtemps, plus la probabilité d’y trouver une comparaison impliquant les nazis ou Adolf Hitler s’approche de 1. »
Le phénomène est tellement répandu que les réseaux sociaux recrutent des modérateurs pour essayer de le limiter. Pratiquement, ils interviennent dans les discussions, ils les surveillent et suppriment les commentaires troll. C’est évident que cette approche est onéreuse en temps et en argent. D’où l’idée d’essayer d’automatiser leur détection grâce au Machine Learning.
L’approche choisie est une approche supervisée, c’est-a-dire que le modèle de Machine Learning apprendra sur un jeu de données de commentaires pour lesquels nous savons auparavant si c’est un troll ou pas. Trouver le jeu de données apparemment n’a pas été facile. Finalement, c’est un dataset des commentaires de wikipedia qui a été choisi. Il s’agit d’un ensemble de commentaires collectés entre 2001 et 2015 assez conséquent.
Une fois les données récupérées, le speaker a utilisé Python et en particulier la librairie scikit-learn pour dériver des attributs pour chaque commentaire et ensuite construire le modèle de Machine Learning. Les étapes principales de son modèle sont : count_vectorizer, tf-idf et régression logistique. Les deux premières étapes sont des traitements typiquement utilisés dans le domaine du text mining pour la classification du texte. Le count_vectorizer convertit du texte en matrice de comptage de mots. Ensuite, tf-idf (term frequency-inverse document frequency) est une transformation qui se base sur l’observation empirique suivante : “Si une requête contient le terme T, un document a d’autant plus de chances d’y répondre qu’il contient ce terme : la fréquence du terme au sein du document (TF) est grande. Néanmoins, si le terme T est lui-même très fréquent au sein du corpus, c’est-à-dire qu’il est présent dans de nombreux documents (e.g. les articles définis – le, la, les), il est en fait peu discriminant.”. Une fois le modèle entraîné, il a sérialisé la pipeline de traitement et l’a utilisé dans une API web, avec laquelle nous pouvons nous amuser à tester des phrases gentilles ou méchantes.
Le projet est intéressant et j’apprécie l’idée de mettre le modèle à disposition dans l’API. Par contre, le modèle demande d’être amélioré, car son pouvoir prédictif n’a pas été évalué. Le speaker en est conscient, et cela fait partie des next steps. Nous attendons !
Philosophie de l’intelligence artificielle, une introduction – Eric Lefevre-Ardant et Sonia Auchtar
La grande protagoniste de cette édition de Devoxx est l’Intelligence Artificielle, accompagnée par des questions liées à l’éthique et la morale. Si les questions éthiques, morales et de responsabilité légale ont plutôt été adressées pendant la keynote, cette présentation traite une question qui vient en amont. Avant de se poser les questions “Selon quelle morale une machine devrait-elle agir ? Comment apprend-on la morale à une machine ? Qui est le responsable de l’accident mortel impliquant une voiture autonome ? Comment peut-on décider qu’une machine, un système ou toute autre chose autre que l’humain, est intelligente ? », etc. Voici là où la philosophie intervient et les speakers sont là pour nous guider à travers le chemin de la philosophie de l’esprit.
Nous pouvons parcourir l’histoire de cette philosophie, en partant de la théorie du dualisme de substance de Réné Descartes. Selon lui, corps et esprit sont deux entités complètement séparées mais qui interagissent entre-elles.
Nous avons ensuite la théorie de l’identité de l’esprit-cerveau de Thomas Hobbes, selon laquelle chaque « type » d’état mental est identique à un « type » d’état du cerveau. Chacune de ces théories a généré des débats qui ont conduit à leur évolution ou abandon.
Nous passons maintenant au jeu de l’imitation d’Alan Turing. Nous sommes dans les années 50. Le jeu est un test qui a pour but de déterminer si une machine est intelligente. Turing a imaginé comparer une machine à un humain en les faisant dialoguer avec un troisième humain. Si ce dernier n’est pas capable de dire lequel de ses interlocuteurs est un ordinateur, en sachant que l’identité de ses interlocuteurs lui est cachée, on dira que la machine a passé le test, et nous en déduirons que la machine est intelligente.
Évidemment, cela suscite bien des discussions, dont une des plus importantes est celle avec John Searle qui présente un contre-exemple. Sa thèse est simple : un comportement intelligent n’implique pas une intelligence. Il démontre cela grâce à l’exemple de la chambre chinoise. Imaginez que, dans une chambre fermée, se trouvent un humain et un livret d’instructions faisant correspondre deux suites de symboles chinois. Si nous passons à cet humain une série de symboles, il sera capable de la transformer en une autre série de symboles via le livret d’instructions. Maintenant, si nous lui passons une phrase écrite en symboles de la langue chinoise, il serait potentiellement capable de soutenir un dialogue en chinois, grâce aux instructions, et sans forcement connaître la langue. La chambre chinoise, ainsi conçue, passerait le test de Turing. Pourtant peut-on la déclarer intelligente ? C’est clair que pour être intelligent il faut de la compréhension, la simulation de l’intelligence n’est pas suffisante !
Ce n’est que la surface d’un débat qui se poursuit encore aujourd’hui et qui est d’une extrême importance.
Comment être Tech Lead dans une pizza team XXL sans finir sous l’eau ? – Damien Beaufils
Nous entendons souvent dire que la taille idéale d’une équipe produit est celle qui arrive à se nourrir avec 2 pizzas.
Damien nous a partagé son expérience d’un an en tant que tech lead d’une équipe bien plus grande, 10 développeurs (!) et comment il est arrivé à améliorer la qualité de cette équipe.
Selon lui, un tech lead doit passer au moins 30% de son temps à écrire du code pour mieux comprendre les enjeux de l’équipe. Le reste du temps, il doit s’assurer que l’équipe arrive à produire du code dans les meilleurs conditions avec les meilleurs moyens.
Il a passé sa première semaine avec l’équipe en faisant du « shadowing », observant toutes leurs habitudes. Plusieurs signaux faibles de problèmes ont été notés.
Suite à ces observations, une rétrospective technique hebdomadaire d’une heure a été mise en place. Grâce aux rétrospectives, ils ont identifié des règles techniques. Comme par exemple la mise en place de revues de code obligatoires. Ils ont également décidé de travailler avec du code legacy (code sans tests) en pair ou mob programming car c’est parfois très compliqué et démotivant de mettre en place des tests sur de l’ancien code.
Mais en écrivant les tests, ils avaient besoin d’un moyen d’en mesurer la qualité. Ils ont commencés par définir ensemble ce qu’est un test unitaire, un test d’intégration et un test fonctionnel. Il déconseille d’ailleurs de mesurer la couverture de tests car une couverture haute n’implique pas le bon fonctionnement du système.
Un meilleur moyen de mesurer la qualité des tests est de les catégoriser grâce à la pyramide de tests. Avec leur définition de chaque type de tests, ils ont constaté que leur base de code ressemblait à une soucoupe volante. 30% de tests fonctionnels, 50% de tests d’ intégration et seulement de 20% tests unitaires. Il propose alors que l’équipe pratique le TDD pour avoir plus de tests unitaires. Mais l’équipe n’était pas formée et ce n’était pas envisageable pour le tech lead de les former. Il a demandé 3 jours de formation TDD pour son équipe qui après un travail important de préparation, ont été acceptés par le client.
Selon Damien, savoir communiquer fait partie du rôle de tech lead. Il se doit aussi de porter les décisions prises par l’équipe auprès des décideurs. De même, il veille à ce que la communication dans l’équipe reste neutre. Enfin, il s’assure de l’inclusion de tous les membres. Des points de type « one-to-one » avec tous les équipiers (30 minutes par personne par itération) est un bon moyen de gagner la confiance en tant que tech lead.
Pour conclure, il nous a montré d’autres indicateurs mesurés pour son équipe (qui sont d’ailleurs affichés sur leur tableau Kanban) :
- Nombre de bugs restant à corriger par itération : devrait descendre.
- Nombre de livraisons par itération : devrait descendre.
- Nombre de fonctionnalités réalisées par itération : devrait monter.
Techniques modernes pour l’écriture de serveurs web performants – Geoffroy Couprie
Dans cette conférence, Geoffroy Couprie nous présente des techniques permettant d’écrire des applications web performantes.
Pour démarrer, Geoffroy nous présente un outil qu’il développe et qui viendra illustrer les problématiques et les techniques présentées. L’outil en question est un reverse proxy écrit en Rust nommé sozu.
Ce reverse proxy répond à des besoins de rechargement à chaud et à granularité fine de la configuration – on ne recharge que les parties de l’application concernées par le changement – et sans interruption de service. Un aspect primordial sur ce type d’application est bien sur la performance.
Geoffroy nous présente sa vision de la performance et du temps de réponse d’une application, d’un service, qu’il définit comme suit.
Response time = Network latency + Service time + Wait time
Les deux premiers temps du membre droit de cette équation sont des mesures directes du temps consommé par l’échange de données dans le réseau et du temps consommé par l’exécution des instructions de l’application.
Le Wait time dépend, quant à lui, du temps consommé par les autres processus du système et peut s’écrire comme suit.
Wait time = N * (Service time + Context Switch time)
N étant le nombre de processus actifs dans le système
Geoffroy nous rappelle que la mesure de la performance doit prendre en compte, non pas le temps moyen, mais bien le temps au 99ème percentile, car comme le montre l’équation précédente, la dégradation de la performance d’un processus du système impact nécessairement la performance des autres processus du système.
Geoffroy nous propose le constat suivant. La latence réseau est un coût fixe qui ne peut pas être réduit applicativement. Le temps de service est le temps utilisé par l’application, qui peut être optimisé, mais à quel coût et pour quel gain ? Le gain se situe donc au niveau du Wait time qui en dehors du temps de service des autres processus contient du temps perdu en context switching.
Pour montrer comment ce temps peut être réduit, Geoffroy fait un rappel des architectures serveurs et notamment des architectures des processeurs modernes.
Ceux-ci sont en effet multi-coeurs et pour fonctionner accèdent à des données à différents endroits : caches (L1, L2 et L3), mémoire RAM et mémoire disque (swap). Geoffroy insiste sur les différentes vitesses d’accès de ces mémoires et nous présente donc comment, les caches miss ou le context switching peuvent avoir un impact négatif au niveau des performances d’une application.
Geoffroy nous parle ensuite des architectures SN (Shared Nothing architecture) dans lesquelles tout noeud est indépendant et autosuffisant. Dans ces architectures, les données ne sont pas centralisées et les différents noeuds ne se partagent pas de stockage mémoire ou disque – pas de point de contention. Geoffroy nous présente deux techniques fondées sur ce principe.
La première technique, Core affinity, permet de dédier un processus à un coeur spécifique du processeur. Ce qui permet d’éliminer ou réduire les temps de context switching car un même processus s’exécutera toujours sur le même coeur.
La seconde technique, Multiqueue NICs (Network Interface Controller), permet de paralléliser sur plusieurs coeurs les interruptions systèmes liés au trafic réseau et surtout de cibler l’émission de ces interruptions sur le coeur dédié à l’application qui émet ou reçoit les données concernées.
En revenant sur l’implémentation du reverse proxy sozu, Geoffroy nous présente splice() qui est un appel système sous Linux qui permet, notamment, de transférer des données d’une socket à une autre sans passage par le user space.
Geoffroy illustre comment, dans le cadre du proxy, cela permet de transférer les données reçues de manière performante sans que l’application s’occupe du chargement et de la copie de l’intégralité des données. Seule la partie des entêtes permettant de contrôler le trafic étant chargée et analysée par l’application.
Reactive Spring – Sebastien Deleuze et Brian Clozel
Deuxième épisode d’une série de trois présentations réalisées par les équipes de Spring, cette session de live coding avait pour but de faire découvrir l’entrée de Spring dans le monde Reactive avec son nouveau framework web Spring WebFlux.
Plusieurs problèmes peuvent dégrader les performances d’une application web. Parmi lesquels, nous retrouvons :
- des requêtes complexes,
- la connexion via des appareils mobiles dont le réseau est souvent instable,
- des connexions internes à des services distants (avec la démocratisation des microservices par exemple).
Ces problèmes ont souvent la même conséquence, à savoir surcharger les serveurs applicatifs. Contrairement à beaucoup de développeurs qui optent pour la programmation réactive dans le but d’améliorer les performances, Spring a préféré choisir cette solution afin d’améliorer la scalabilité et la stabilité de son framework.
Spring Framework 5 intègre donc une compatibilité avec Reactor et aussi avec l’ensemble des frameworks (via des adaptateurs) implémentant l’API « Reactive Streams », comme par exemple :
Afin de garantir un support sur le long terme, Spring a préféré choisir d’utiliser Reactor, puisqu’il s’agit d’un projet initialisé par leurs soins.
Reactor, c’est quoi ?
Comme indiqué précédemment, Reactor est une implémentation de l’API Reactive Steams. Elle expose deux types de « Publisher » :
- Mono : Représentant un publisher renvoyant jusqu’à 1 élément et se terminant par un succès ou une erreur.
- Flux : Représentant un publisher renvoyant jusqu’à n éléments et se terminant par un succès ou une erreur.
Le point fort de Reactor est de gérer la notion de « Back Pressure » ou pression arrière sur l’ensemble de ses publishers.
L’API de Reactor permet de gérer cette pression arrière simplement afin de contrôler le débit des données échangées entre Publisher et Subscriber.
Spring WebFlux, c’est quoi ?
Spring WebFlux est une adaptation de Spring MVC et utilisant les principes de programmation réactive.
Il est donc possible dans un contrôleur de retourner directement un Flux ou un Mono.
@GetMapping(path = "/persons", produces = "text/event-stream") public Flux<Person> getPersons() { return this.repository.getPersons(); }
Dans cet exemple, l’ensemble des couches sont réalisées en appliquant les principes de programmation réactive, du contrôleur au repository. Il est important de noter que ces principes n’ont de sens que dans le cas où l’ensemble des briques sont elles-aussi réactives, que ce soit le client, le serveur ou le système de base de données utilisé.
Spring Frameworks 5 intègre aussi de nombreuses modifications émanant de la communauté comme par exemple la possibilité d’ajouter et configurer les différentes règles de mapping des routes de manière programmatique.
Bonus Stage : Support natif de Kotlin !
Cerise sur le gateau, Spring a décidé de proposer un support natif du langage Kotlin afin de bénéficier de son approche fonctionnelle. Ce support risque de donner un beau coup de pouce à Kotlin et de faire ainsi grossir sa communauté.
Pour en savoir plus : http://start.spring.io
Les bornes des limites : comment maintenir de la cohérence dans votre architecture microservices – Clément Delafargue
Les nombreux bénéfices qu’apporte une architecture microservices.
- Séparation des responsabilités et les logiques métiers.
- Réduction de la taille de la base de code pour rendre plus facile la prise en main et la modification.
- Indépendance des cycles de vie de chaque service.
- Réduction de la taille des équipes.
- Compartimentation des échecs.
- Mise à l’échelle (scale) indépendante de chaque service.
- Utilisation possible de différents outils / langages
- Essai de nouvelles choses facilitées.
- Mélange des technologies pour la couche de persistance.
- Mise à l’échelle des stockages de données de façon indépendante.
Là où l’approche monolithique garde l’avantage.
- Les microservices augmentent de beaucoup la complexité et, avec elle, la possibilité d’erreurs.
- La mise en route d’une application microservices nécessite le déploiement et le démarrage de beaucoup plus d’éléments.
- Les modes d’échecs sont plus variés et plus durs à débugger.
- Le réseau, surtout quand il est mauvais, a une très grande influence sur la bonne marche du système.
- Être capable de provisionner rapidement des serveurs.
- Que le monitoring et les logs soient agrégés et configurés.
- Être en mesure de déployer très rapidement.
La première chose à faire : tout automatiser.
- Le build : tout doit se builder avec le package manager et les commandes de base associées.
- Les configurations : elles doivent être mises à jour et injectées automatiquement en fonction des changements.
- Le déploiement et le redémarrage : un robot chargé de ces tâches évitera de devoir faire veiller un humain inutilement.
- Le provisionning des environnements.
Perdre / prendre certaines habitudes au niveau du code.
- Ne pas avoir un état mutable partagé : chaque microservice doit être responsable de son état et le meilleur moyen est de le rendre explicite dans le code.
- Pas de persistance locale sur le file system.
- Les sessions doivent être stockées en base de données.
- Utiliser S3 pour le stockage des fichiers.
- Utiliser des synchronisations explicites.
Pour éviter les catastrophes, plusieurs points sont à tenir à l’oeil.
- Connaître de quelles manières le système peut tomber en erreur.
- Savoir explicitement lorsque le système fait un appel local ou un appel distant. S’il y a trop d’appels distants, c’est que les frontières des services sont mal définies.
- Définir de manière explicite les limites et les frontières (pas d’ORM ou de RMI).
- Ne pas tenter de cacher la complexité en faisant trop d’abstraction, ce qui peut facilement aboutir à de la complexité « accidentelle ». L’architecture hexagonale est un bon exemple pour obtenir des frontières bien définies et délimitées.
- Bien connaître la typologie du système et pour cela utiliser une sémantique explicite.
Sur la typologie.
Les difficultés sont aux frontières.
La sérialisation doit être au carrée.
- Swagger / RAML pour documenter les end points.
- Diagramme de séquence UML pour documenter les échanges.
- Avro pour gérer la sérialisation. Clément semble très enthousiaste en décrivant cette technologie. C’est un langage de défiinition de schéma avec une sérialisation compacte. Il est cross langage, cross paradigme et peut être désérialisé en JSON. Il autorise une lecture partielle grâce à l’utilisation de resolvers, la déclaration d’évolution de schéma, la définition de RPC, etc. Pour aller plus loin, vous pouvez lire la spécification.
- Tout automatiser.
- Avoir une bonne documentation.
- Ne pas partager d’état.
- Connaître les limites des services en les rendant explicites.