Votre application apparaît dans une émission de télévision ou un envoi à 250 000 prospects est fait et là c'est la catastrophe.
Chaque page prend plusieurs secondes à s'afficher ou pire renvoient une erreur ! Toute cette promotion pour rien !
Vous devez être prêt à scaler. La scaling consiste à être capable de gérer un trafic plus important (plus de pages vues, plus d'utilisateurs simultanés) sans dégradation du service.
Et cela ne se résume pas à avoir plus de serveurs ou des serveurs plus performants. C'est pour cela que l'on va passer en revue toutes les bonnes pratiques qui vous permettront de comprendre :
Le serverless et l'autoscaling ont redéfini la manière de scaler les applications. Mais s'y adapter n'est pas toujours facile.
Voici quelques explications et bonnes pratiques pour y arriver.
Nous avons dédié un article entier au serverless et pour mémoire nous distinguons le serverless avec 2 principaux services :
Le FaaS n'est malheureusement pas compatible avec la majorité des frameworks quelque soit votre langage de programmation et il faudra bien penser votre architecture et le design de votre API avant de vous embarquer dedans.
Au contraire, le PaaS vous permet d'utiliser tous les frameworks que vous désirez.
De plus, des solutions comme Scalingo vous permettent, en plus du PaaS, de mettre en place des systèmes similaires au FaaS :
L'avantage du serverless, c'est qu'il y a beaucoup moins de points de défaillance qu'avec des VPS ou des serveurs dédiés.
Vous avez aussi des solutions pour vos bases de données avec une haute disponibilité sans avoir besoin de connaissances en administration système.
Pour vos autres ressources, utilisez des services qui ont un bon palmarès de stabilité pour éviter de mauvaises surprises.
Il s'agit de tous les éléments externes dont votre application a besoin pour fonctionner correctement en dehors de votre propre code.
Il peut s'agir d'une base de données, d'un moteur de recherche en texte intégral, d'une installation de stockage externe (Outscale Object Storage), d'API (Facebook, Twitter), etc.
À une époque où tout est une API, votre application dépend certainement de ressources externes. Même si votre code peut très bien évoluer, vos ressources externes ne le peuvent peut-être pas. Vous dépendez peut-être d'un micro-service interne qui ne peut pas gérer la charge que vous lui enverrez.
Ou peut-être que ce n'est pas la ressource externe elle-même qui ne peut pas évoluer, mais la façon dont vous y accédez.
Ce problème est particulièrement vrai pour les langages ou les technologies qui ne sont pas conçus pour une forte concurrence, comme PHP, Ruby, etc. C'est la raison principale de l'adoption de NodeJS, Go ou Elixir/Phoenix.
Pour améliorer la concurrence de votre application, la réponse la plus courante est de prendre les parties bloquantes de votre code et de les rendre asynchrones.
Pour illustrer ce point, prenons deux choses que votre application fait sûrement et qui devraient certainement être faites de manière asynchrone : l'envoi d'emails et la création de vignettes d'images.
En effet, si vous le faites de manière synchrone, chaque fois qu'une action déclenchée par un utilisateur envoie un e-mail, le serveur de votre application devra attendre que votre serveur de messagerie réponde et traite l'envoi du courrier. Dans le monde Ruby, Sidekiq est la bibliothèque la plus célèbre pour gérer les choses de manière asynchrone.
L'écriture sur un système de fichiers, par exemple l'accès à un disque dur, est l'une des choses les plus lentes qu'une application puisse faire.
En effet, dans un environnement dans le Cloud, vous n'aurez pas accès à un SSD ultra rapide directement relié à votre machine. C'est pourquoi vous voulez l'éviter autant que possible.
Par exemple, ne stockez pas vos actifs (images, PDF, fichiers excel) sur un disque dur. Envoyez-les vers un service externe spécialement conçu pour cette tâche, comme Outscale Object Storage.
Pour ne pas avoir de problèmes avec le serverless, nous vous conseillons aussi de ne pas utiliser SQLite, ni de stocker des variables d'environnement.
Si cela vous aide à évoluer, le fait d'avoir moins de pièces mobiles vous aidera également à déplacer le code de votre application d'un endroit à l'autre.
Scaler son application, ce n'est pas aussi simple qu'avoir plus de serveurs et optimiser l'application, c'est aussi pouvoir scaler l'opérationnel IT.
Le scaling doit être indolore et quasi instantané. Pendant la phase de build, votre application est construite et toutes ses dépendances (bibliothèques, ressources) sont recherchées, préparées ou emballées.
La phase de run est celle où vous exécutez réellement le code de votre application.
Ces deux étapes sont souvent les mêmes lorsque vous commencez à travailler sur votre application.
Le problème survient lorsque vous ajoutez un serveur de plus à votre mélange. Cela signifie que toutes les dépendances de votre application doivent être récupérées sur chaque serveur de votre cluster.
Il en va de même pour la phase de build des actifs. Cela augmente le temps nécessaire du scaling. Si des outils comme Capistrano vous aideront à déployer plus rapidement, ils ne vous aideront pas à séparer les phases de build et de run.
C'est pourquoi vos étapes de build et de run doivent être indépendantes les unes des autres. Docker vous aide à séparer les étapes de construction et d'exécution.
Le scaling implique le démarrage de nouveaux serveurs ou instances.
Une fois qu'ils ont démarré, vous devez les provisionner, c'est-à-dire installer tous les logiciels et bibliothèques nécessaires à votre application pour qu'elle fonctionne correctement.
Exécuter manuellement chaque étape du provisionnement d'un nouveau serveur est trop lourd, trop long et trop sujet aux erreurs.
Vous devez automatiser le provisionnement. Soit via des scripts shell ou mieux, via un outil d'automatisation comme chef ou puppet.
Plus l'application monte en charge, plus elle est utilisée. Ce qui qui veut dire que la moindre erreur en production deviendra de plus en plus visible et problématique.
Mettre en place un système d'intégration continue plus tôt que tard est donc essentiel pour scaler.
Avec un tel système, vous aurez l'esprit libre. Les petits changements qui semblent anodins mais qui font planter la production ne seront plus qu'un mauvais souvenir.
La détection des problèmes sera plus rapide et cela permettra d'allouer plus de ressources sur des tâches plus importantes comme l'amélioration du produit ou bien... le scaling !
Mais le système doit aussi pouvoir scaler. Si chaque changement de code passe par un serveur d'intégration continue trop lent, le temps d'exécution sera trop long et cela aura des conséquences sur la productivité de tout le monde.
La solution aux problèmes de performances est rarement aussi simple qu'ajouter plus de serveurs ou de ressources. C'est particulièrement le cas pour la base de données.
Optimisez l'accès à la base de données est donc critique.
La migration de bases de données (ou migration de schémas) fait référence à la gestion des modifications incrémentielles et réversibles des schémas de bases de données relationnelles (merci Wikipédia).
Une migration est une tâche ponctuelle effectuée juste après une nouvelle version de votre application. Il s'agit d'une série de commandes SQL, comme CREATE ou DROP, généralement entourée d'une transaction SQL.
Si elle ne prend que quelques millisecondes sur une petite base de données, elle peut bloquer des tables entières pendant un temps très long dès que votre base atteint quelques dizaines de Go de données.
Des outils spécifiques sont alors nécessaires pour réaliser une migration de schéma sans temps d'arrêt. Le plus récent est gh-host, proposé par les excellents utilisateurs de Github.
Bien sûr, vous pensez que vous l'avez fait dans votre application. Mais n'en soyez pas si sûr.
Mais d'abord, décrivons le problème. De nombreuses bases de données incluent le concept d'index. C'est un moyen de les aider à atteindre les données spécifiques que vous recherchez avec vos requêtes (SQL).
Il s'agit d'une étape manuelle qu'un développeur doit suivre pour apprendre à la base de données quelles données doivent être indexées.
Malgré la compréhension générale du problème, la raison pour laquelle vous voyez toujours le "problème d'index" dans la nature est qu'il peut prendre des formes très différentes.
La première, et la plus évidente, est généralement que vous oubliez de configurer certains index et dès que votre application gère un peu plus de trafic que d'habitude, elle devient extrêmement lente.
Le problème du manque d'index se pose lorsque vous franchissez un certain cap en termes de nombre d'éléments dans une collection (ou une table).
Dans d'autres cas, il s'agit d'opérations que vous avez conçues longtemps après la création des index et seules certaines d'entre elles peuvent être assez lentes.
Encore une fois, s'il ne s'agit que de quelques opérations en parallèle, vous ne remarquerez pas que les index ne sont pas en place ou que vous pourriez concevoir des index plus complexes.
Et savez-vous que certaines bases de données ont des index géospatiaux ? Arrêtez de réinventer la roue :)
Imaginez que votre application reçoive des demandes : par exemple, un utilisateur qui surfe sur votre site Web.
À un moment donné, cet utilisateur s'authentifie auprès de votre application. Sur chaque page visitée par cet utilisateur, vous voulez qu'il reste connecté.
Votre application met certaines informations en mémoire et échange un cookie unique avec cet utilisateur, créant ainsi une session utilisateur.
Chaque fois que cet utilisateur visite une page, son navigateur renvoie ce cookie et vous pouvez retrouver ses informations personnelles dans la mémoire de session.
Maintenant, vous voulez changer d'échelle : au lieu d'un serveur, vous en avez besoin de deux.
Si chaque instance a sa propre mémoire de session, selon l'instance qui reçoit le trafic, l'utilisateur ne sera pas authentifié.
Les sessions utilisateur doivent être partagées entre tous les serveurs (ou instances).
Le moyen le plus simple d'y parvenir est de les enregistrer dans une base de données. Bien que vous puissiez techniquement utiliser n'importe quelle base de données, vous choisirez généralement une base de données clé/valeur en mémoire très rapide comme Redis qui fonctionne très bien pour ce cas d'utilisation.
Même avec des index, il est plus rapide de ne pas utiliser la base de données.
C'est bien sûr pas possible, mais c'est avec cette philosophie que vous devriez optimiser votre code : faire le moins de requêtes à la base de données.
N'ouvrez pas de connexions inutiles, ne commencez de transactions que quand vous devez absolument le faire.
Et réduisez le nombre de requêtes. Un code non optimisé aura probablement de nombreuses requêtes N+1. Prenons un exemple.
Vous avez des articles et des commentaires. Vous voulez afficher l'article et ces commentaires sur une page. Si vous faites une requête pour l'article (N) puis une requête pour chaque commentaire (N+1), les performances vont en pâtir avec l'augmentation des données.
Si tous les commentaires étaient chargés avec une seule requête, le temps de réponse pour afficher la page sera beaucoup plus court.
Certains frameworks vont faire ce travail d'eux-mêmes et vous n'avez pas besoin d'y penser mais l'aide qu'ils procurent peuvent aussi cacher des requêtes N+1 qui n'auraient pas été optimisées automatiquement ce qui sera plus dur pour vous à identifier. Il faut donc bien avoir en tête ce problème tout au long du développement.
Le dernier élément qui permet de décharger la base de données est le cache. C'est entre autres ce dont on va parler dans la prochaine section.
Le moyen le plus simple de scaler est de ne pas scaler (!) en économisant les requêtes web. Pour cela, il y a de nombreux moyens d'y arriver et en voici quelques uns.
L'un des moyens d'y parvenir est de minifier vos ressources statiques. Les ressources statiques comprennent les feuilles de style CSS, les fichiers javascript ou les images utilisées par votre interface utilisateur.
La minification est l'action de combiner plusieurs fichiers en un seul (ainsi, au lieu d'une requête web par fichier, vous n'avez qu'une seule requête web au total).
C'est très facile pour les fichiers CSS et Javascript. Des outils comme l'asset pipeline de Rails ou grunt peuvent vous aider à le faire.
Combiner plusieurs images en un seul sprite CSS est un peu plus compliqué et n'est peut-être pas du tout réalisable. Cependant, lorsque votre trafic se compte en millions de requêtes web, ce sont les petits ajouts qui peuvent compter énormément !
Corollaire du point précédent, une fois que vos ressources sont minifiées, il est préférable qu'elles soient servies par un CDN (Content Delivery Network).
Ils sont conçus pour scaler, sont bon marché et faciles à utiliser.
Un CDN enverra vos ressources plus rapidement à vos utilisateurs en les mettant en cache dans le monde entier et en détectant quel cache est le plus proche de chacun de vos utilisateurs finaux. Baleen,que nous avons déjà mentionné dans notre article sur le cloud souverain,Cloudfront et Cloudflare sont de très bons CDN.
Si vous souhaitez utiliser un CDN français, nous vous conseillons bien sûr Baleen.
En envoyant moins de fichiers, moins d'octets par fichier et plus rapidement, vous améliorerez également l'expérience de vos utilisateurs.
Charger une quantité importante avant que l'utilisateur n'en utilise qu'une partie peut coûter très cher.
Imaginez si Netflix ou Youtube devait d'abord télécharger toute la vidéo avant de l'arrêter au bout de 2 minutes parce qu'elle n'est pas intéressante ! Beaucoup de bande passante gaspillée et une expérience utilisateur très mauvaise.
Mais c'est aussi le cas pour des applications web comme Twitter ou Facebook avec du streaming de données plus textuelles. Une fonctionnalité qui peut se faire dans votre application avec des websockets par exemple.
Passez le moins de temps à utiliser le serveur est la méthode ultime pour scaler.
Et pour cela il faut mettre en cache tout ce qui peut l'être. Ce ne sera pas le cas de toute l'application bien sûr mais juste un peu de cache peut faire toute la différence.
Il existe plusieurs types de cache :
En fonction de vos besoins, choisissez le bon moyen pour optimiser toutes vos requêtes et ainsi réduire la charge sur votre infrastructure.
Avoir une bonne stratégie de surveillance est indissociable du scaling. Vous devez être capable de détecter un problème et de prendre des mesures de manières pro-actives.
Cela devrait être évident mais c'est toujours mieux de le dire : si vous voulez que votre application scale, vous devez mesurer la consommation de ressources afin de savoir quand vous devez scaler et quel est le "modèle d'évolution" de votre application.
Par modèle de scaling, j'entends que chaque application a sa propre façon de scaler.
Vous ne pouvez pas simplement prendre un modèle que vous avez découvert dans une application et l'appliquer aveuglément à une autre application.
La consommation des ressources doit inclure l'utilisation du CPU/RAM/Swap du code et des bases de données de votre application, le trafic réseau entrant et sortant de votre application vers vos utilisateurs finaux ou des ressources externes.
L'inspection des logs n'est pas vraiment une tâche fun. Mais parfois vous devrez le faire : pour retracer les mauvais comportements de votre code ou les utilisations malveillantes de votre application.
Au début, les choses sont simples : vous n'avez qu'une seule instance qui exécute le code de votre application.
Pour inspecter les logs, vous vous connectez en SSH à votre serveur et accédez directement au fichier.
Dès que vous scaler, les choses se compliquent. Comment trouver la seule trace dont vous avez besoin sur de nombreux serveurs ? Vous devez disposer d'un moyen centralisé de regrouper vos journaux.
Le meilleur moyen de trouver les problèmes est en observant vos utilisateurs et voir comment exactement ils utilisent votre application.
Pour cela, il existe des solutions telles que AppSignal qui vont venir collecter des informations sur la performance de votre application.
Vous pourrez savoir comment chaque action des utilisateurs utilisent les différentes ressources de votre infrastructure et trouvez les problèmes sans devoir tester vous-même pendant des heures sans comprendre comment reproduire le comportement des utilisateurs.
Toutes les bonnes pratiques données jusqu'à maintenant permettent déjà de nombreuses optimisations sur le coût de votre infrastructure.
Mais il y en a encore plus qui sont moins en rapport avec le scaling mais importantes pour maîtriser ses coûts, facteur à prendre en compte pour chaque décision en relation avec le scaling.
FinOps est la pratique de la gestion financière efficient du Cloud. A la manière du terme DevOps (mix entre développement et opérations), le FinOps lie les aspects financiers et opérationnels. (finance et ops).
Premièrement, listez toutes les ressources qui sont utilisées pour votre application.
Une optimisation possible à ce moment-là est de comparer les objectifs business avec chaque l'application pour savoir si certaines ressources ne pourraient pas être changées pour quelque chose de moins cher ou plus performant.
Cette étape est importante car avec un projet complexe, il est parfois compliqué de prédire les coûts du Cloud avec les nombreux services différents.
Une fois que toute les ressources ont été cartographiées, il est important d’avoir de la visibilité sur ce qui se passe.
Etre en capacité de facilement savoir ce qui est sous-utilisé et ce qui coûte trop cher permettra d’optimiser plus rapidement.
Pour cela, plusieurs moyens peuvent être mis en place :
Les 2 premières étapes vous donneront toutes les informations pour commencer à optimiser vos dépenses. Voici les principales optimisations que vous pouvez faire.
Bien documenter tout ce qui a été fait dans les étapes précédentes est crucial pour que les pratiques FinOps puissent être répétées dans la durée et vous donnera l’assurance dans vos dépenses Cloud.
Une culture du FinOps se développera en continuant les efforts d’optimisation à long terme.
Enfin, en désignant des responsables pour la gestion des ressources Cloud, vous saurez vers qui vous tourner quand vous avez des questions.
Cela vous permettra aussi de restreindre l’accès à la gestion des ressources ce qui évitera de possibles incidents.
Nous avons vu de nombreuses choses qui doivent être corrigées dans votre application et votre infrastructure avant de penser au scaling.
J'espère que vous n'êtes pas trop pessimiste : vous n'avez pas à faire tout cela en une seule fois ! Pour aller plus loin, je vous invite à consulter le manisfeste des 12 facteurs.
En outre, l'hébergement de votre application sur une plateforme en tant que service comme Scalingo vous aidera beaucoup à scaler.
Chez Scalingo (avec nos partenaires), nous utilisons des traceurs sur notre site.
Certains, essentiels et fonctionnels, sont nécessaires au bon fonctionnement du site et ne peuvent pas être refusés.
D'autres sont utilisés pour mesurer notre audience, entretenir notre relation avec vous et vous adresser de temps à autre du contenu qualitatif ainsi que de la publicité.