À la recherche du temps perdu
Il est reconnu qu’il existe des seuils temporels qui affectent la façon dont nous percevons notre travail et affectent notre performance. (Et ceci indépendamment de tout biscuit.) Si une tâche prend trop de temps à réaliser, nous la mettons de côté ou nous dérivons vers autre chose. Par exemple, dans un récent projet interne, nous avons utilisé la technologie AWS Lambda pour gérer une API web REST. Le problème que nous avions était que l’exécution des tests d’intégration prenait plus de 15 minutes. Cela avait un impact direct sur la fréquence à laquelle nous étions prêts à effectuer les tests : 15 minutes, c’est trop long pour être exécuté après chaque petit changement de code. Rouler les tests signifiait mettre tout le reste en pause. Cela avait également des répercussions sur le coût de l’écriture de nouveaux tests : chaque nouveau test rendait les tests d’intégration encore plus lents.
Je trouvais cette situation inacceptable. Et, à un certain niveau, incompréhensible : pourquoi les tests prenaient-ils autant de temps ? J’ai donc profilé le code, en commençant par le côté client. Il s’est avéré que presque tout le temps était passé à ouvrir des connexions et à attendre des réponses. J’ai donc profilé le code du côté serveur. Il s’est avéré que le code serveur était très rapide.
Alors, où donc le temps était-il perdu ?
Il est devenu évident que le problème se situait au niveau du système AWS. Ce que j’ai découvert est une bonne illustration d’un mauvais choix d’ingénierie. L’un des arguments de vente du système AWS sans serveur est que le serveur utilise toujours le code le plus récent, il n’y a rien à faire pour utiliser les dernières modifications que vous avez apportées à votre code. Le problème, c’est que cela est mis en œuvre en faisant tourner et en arrêtant Docker pour chaque requête web ! Pour nous éviter de devoir arrêter Docker manuellement pour essayer du nouveau code, AWS rend chaque requête extrêmement lente. Pour gagner quelques secondes de temps en temps, il ajoute 2 à 3 secondes à chaque requête. C’est un compromis acceptable si vous modifiez votre code très souvent et ne testez qu’un seul service web de temps en temps. Mais ce n’est pas comme ça que je travaille : j’aime écrire une fonctionnalité complète et la tester minutieusement par la suite. AWS se mettait en travers de la route.
Le temps retrouvé
La solution que j’ai mise au point est d’éviter d’utiliser les outils AWS lors du développement du back-end web. En remplacement on émulerait plutôt le système AWS Lambda. Comme le back-end était écrit en Python, j’ai décidé d’écrire cet émulateur en utilisant le module Python Flask. L’émulateur Lambda AWS fait le pont entre Flask et l’API AWS Lambda. Il convertit la requête et la réponse Flask en leurs équivalents AWS Lambda.
Cela permet d’écrire une petite application Flask qui émule l’AWS Lambda en appelant le même code d’implémentation. Lors de nos tests d’intégration, nous avons constaté que cela permettait d’obtenir une accélération d’environ 80x (oui, 80 fois) la vitesse d’exécution locale de l’AWS Lambda dans le Docker. Comme nous l’avons expliqué précédemment, la raison de cette vitesse étonnante est que l’AWS Lambda démarre et arrête l’instance Docker pour toutes les requêtes ! Dans notre application, l’utilisation de l’émulateur AWS réduit le temps de test de 15 minutes à 10 secondes. Ces 10 secondes sont suffisamment courtes pour que nous puissions exécuter toute la suite de tests sans affecter notre flux de travail. Cela a également permis d’éliminer le coût associé à l’écriture de nouveaux tests. Chaque nouveau test n’ajouterait désormais que 30 ms ou moins. Cela nous a permis d’écrire suffisamment de tests pour atteindre une couverture de code de 99 %, ce qui est le type de mesure qui contribue réellement à la qualité du produit final.
Le code de l’émulateur Python Flask AWS est disponible sur GitHub. Il fait partie d’une application de démonstration qui montre également d’autres fonctionnalités intéressantes pour le développement web en Python. Le projet GitHub est disponible ici. Le code d’émulation est contenu dans le fichier src/flask-app/aws_emulator.py. Il a trois fonctions :
get_event()
: crée un événement Lambda AWS à partir de la requête Flask courante.get_context()
: crée un contexte Lambda AWS factice.convert_result()
: convertit un dictionnaire de résultats Lambda AWS en une réponse Flask.
Vous trouverez un exemple d’application Flask qui utilise l’émulation pour appeler des fonctions de type AWS-Lambda dans le fichier example_app.py. Cette application appelle simplement get_event()
, get_context()
et convert_result()
dans chaque point d’accès au web.
En effet, l’émulateur nécessite qu’on ajoute chaque point d’accès Lambda à l’application Flask en parallèle à la description Lambda YAML du système AWS. Ceci demeure très simple et suit toujours le même schéma. Une amélioration possible dans le futur serait de générer cette application Flask à partir du fichier de description du système AWS Lambda. Ceci étant dit, étant donné l’énorme accélération fournie par l’émulateur, ce petit travail en vaut pleinement la peine. Si vous utilisez AWS Lambda avec un back-end Python, vous devriez y jeter un coup d’œil.