Architecture

Go… Perf !

Publié le : Auteur: Marc Divet Laisser un commentaire
Go01

Pourquoi s’intéresser à Go ? Parce qu’à Go sont associés les mots suivant : simplicité, rapidité, concurrence, microservice et surtout parce que Docker est écrit en Go. Go est fait pour la partie serveur, c’est un concurrent direct de Node.js.

Quelles sont les qualités d’une nouvelle plateforme de développement côté « serveur » ? Revenir à la simplicité, mettre en œuvre les principes de séparation d’intention et de cohérence forte. Simplicité pour le développeur, mais aussi un cycle de vie efficace de la conception, en passant par le développement, par les tests, par l’intégration et enfin par la mise en production.

Cet article n’est pas un point de vue d’expert sur les particularités de ce langage : les ressources d’apprentissage sont suffisamment disponibles sur le web pour ne pas paraphraser les documents déjà existants. Ce qui m’intéresse ici c’est d’évaluer l’efficacité de Go à répondre à un flot de requêtes massif. Ce caractère est une obligation pour les applications digitales. Il faut disposer d’une plateforme efficace pour ne pas brûler des watts et consommer de l’infrastructure inutilement.

C’est ce point que je vais explorer dans un simulateur de transaction, soit la capacité à traiter en parallèle des requêtes multiples tout en implémentant un mécanisme de régulation qui protégera le système des engorgements. J’examinerai aussi les capacités de Go à exploiter correctement les architectures multicores des processeurs actuels.

Avant toute chose, comment simuler un  service dans la vie réelle ? Ce composant doit faire des acquisitions, traiter de l’information, mémoriser un résultat et répondre à la requête initiale. Pour cela le service va donc utiliser un système de base de données. Cette interaction est par nature synchrone et longue, car elle doit subir les temps de latence liés aux problématiques de persistance et de transport.

L’algorithme de test va donc réaliser 10 appels de 5ms à une fonction de lecture de données. A la suite de chacun de ses appels, un algorithme de tri à bulle sera exécuté. Cet algorithme est de complexité n², ici il faudra trier un tableau de 100 réels de 64 bits. Puis un appel de 20ms simulant une écriture de donnée. Ce qui va faire :

  • 10 allocations d’un tableau de 100 réels
  • 100 000 boucles, comparaisons et permutations de nombres réels
  • 10 pauses de 5 ms et une pause de 20 ms

Le temps d’exécution unitaire sur ma machine (4 cores à 2,7 Ghz, 8 threads, 8Go de RAM) est de 70,008 ms, qui se décompose entre 70 ms de temps d’attente et 9 microsecondes de traitement.

L’idée est de reproduire le comportement d’un serveur d’application (ou de moniteur TP). Pour cela, nous allons faire varier le nombre d’exécutions simultanées de notre algorithme de test au sein d’un pool d’exécution de taille fixe. C’est cette taille que nous allons faire varier, jusqu’à atteindre le niveau d’écroulement du système.

Pour réaliser le graphique si dessous j’ai réalisé 27 expériences, la première avec 1 exécution et la dernière avec 1 800 000 exécutions simultanées. Pour chacune de ces expériences, le programme de test a relevé de manière aléatoire 100 000 mesures. Les mesures portent sur les variables suivantes :

  • Le « time laps », c’est le temps total entre le lancement de la routine de test et la fin du traitement. Ce temps comprend les temps d’attentes simulant les accès aux données distantes, les temps liés à l’ordonnancement des tâches et les temps CPU de traitement de l’algorithmique
  • Le nombre de tir/s, soit le nombre de routines traitées jusqu’à exécution complète par le système par seconde
  • Le pourcentage CPU, mesure relevée à partir des indications du système d’exploitation
  • Le volume de RAM consommé, mesure relevée à partir des indications du système d’exploitation

Voici comment j’ai présenté les résultats sur le graphique si dessous :

  • En abscisse, les conditions de l’expérience, soit le nombre maximum de routines en exécution concourante, avec une courbe par mesure
  • Les échelles sont toutes les deux logarithmiques, ce qui permet de rendre compte du comportement sur les faibles valeurs, comme sur les fortes valeurs sur un même schéma

Go01

Cette capacité à traiter un si grand nombre de traitements simultanés (177 000 tir/s) est remarquable, d’autant plus que les tests ont été réalisés sur un portable bureautique.  La capacité de montée en charge et d’exploiter les architectures multicores par Go est ici confirmée.

Go est bien fait par Google pour des besoins d’applications « digital oriented ». La raison de l’émergence du big et fast data est là, les architectures traditionnelles de stockage de l’information sont hors du terrain de jeux imposé par le volume et la rapidité exigée des applications digitales. Pour donner une valeur de comparaison, un serveur de 20 cores à 2,5 Ghz, 40 threads, 128Go de RAM est capable de servir 2 000 transactions par seconde sur un SGBDR classique…

Ce différentiel quantitatif énorme entre la capacité de traitement d’un langage comme Go et la capacité de persistance des SGBDR traditionnels doit être placé au cœur des réflexions sur la transformation « digitale » des SI. Que pouvons-nous conclure ? Il est temps de reconsidérer les architectures traditionnelles de stockage de l’information… c’est le goulet d’étranglement de nos architectures léguées.

Pour les purs players que sont Google, Netflix et les autres la messe est dite : microservices et NoSQL à tous les étages. Mais pour les DSI traditionnelles, il faut regarder la réalité en face : il n’y a pas d’évolution de rupture sans destruction. Ce n’est pas le SQL qui est en cause, c’est l’architecture léguée des SGBDR qui l’est. Avec le New SQL des solutions in memory, nous pouvons garder ce qui fait la force du modèle relationnel et être enfin compatible avec les niveaux de production nécessaires au monde digital. C’est ce que vient de faire SAP avec HANA…  Pour SAP c’est une question de survie. Ce qui est vrai pour SAP pourrait-il être vrai pour les grandes DSI de l’hexagone ?