Préambule
Dans l’univers du développement logiciel, la scalabilité d’une application est une pierre angulaire déterminante de son succès à long terme. Alors que les entreprises cherchent constamment à optimiser leurs systèmes pour gérer des charges d’utilisateurs croissantes et des volumes de données en augmentation, l’architecture de ces systèmes joue un rôle crucial.
Nous verrons ensemble comment concevoir votre application de manière à ce qu’elle soit scalable, et les solutions possibles qui vous permettront de dormir sur vos deux oreilles !
Cet article est le troisième d’une liste de 5 chapitres, concernant les secrets de la scalabilité :
- Les Pratiques de Développement
- L’Infrastructure
- Les Performances et l’Optimisation
- La Fiabilité et la résilience
- Le Monitoring
Scalabilité : Optimiser l’application
Dans un monde numérique où la vitesse et l’efficacité sont primordiales, optimiser les performances d’une application est crucial pour offrir une expérience utilisateur de qualité et assurer la scalabilité de l’infrastructure. Ce chapitre explore les techniques fondamentales pour optimiser les performances d’une application à travers la gestion des données, l’optimisation front-end, ainsi que la compression et la minification des ressources.
Gestion des Données
La scalabilité horizontale/verticale des bases de données
La scalabilité est un concept clé dans le développement d’applications capables de gérer une augmentation de la charge de travail.
Elle se divise en deux catégories principales : la scalabilité horizontale et la scalabilité verticale.
La scalabilité verticale implique l’ajout de ressources supplémentaires à un seul serveur (ou nœud). Cela signifie qu’il est nécessaire d’augmenter la puissance de calcul, la mémoire, ou le stockage d’une machine pour qu’elle puisse gérer plus de tâches simultanément. Bien que simple à mettre en œuvre, la scalabilité verticale a ses limites physiques et financières, et ne garantit pas la haute disponibilité.
D’un autre côté, la scalabilité horizontale consiste à ajouter plus de machines ou nœuds dans le pool de ressources, répartissant ainsi la charge de travail sur plusieurs serveurs. Cette approche permet théoriquement une évolutivité presque illimitée, car on peut continuer à ajouter des serveurs au fur et à mesure que la demande augmente.
Dans le cadre de la scalabilité, il est préférable d’envisager des technologies évolutives horizontalement comme Cassandra ou encore MongoDB, lesquelles permettent une répartition efficace des données sur plusieurs nœuds, facilitant ainsi la montée en charge (autoscaling par exemple).
Cela ne signifie pas qu’il est impossible de faire de la scalabilité horizontale avec des bases de données comme MySQL ou PostgreSQL.
Seulement, ce type de solution requière généralement plus de travail et de configuration pour le déploiement qu’une base de données conçue nativement pour la scalabilité horizontale.
Caching
Le caching est une méthode efficace pour réduire la charge sur les bases de données et accélérer l’accès aux données fréquemment demandées. Utilisez des systèmes de cache comme Redis ou Memcached pour stocker des copies temporaires de données ou de résultats d’opérations coûteuses. Prenez l’exemple d’un réseau social : les posts les plus populaires pourraient être mis en cache. De cette manière, la lecture ultérieure de ces données pourraient être plus rapide et moins lourde pour le système, le temps que les posts perdent en popularité.
Les données stockées devront être accessibles rapidement (d’où l’utilisation d’un système de cache adapté comme Redis ou Memcached), et être synchronisés avec les données réelles. Cette stratégie permettra une réponse plus rapide pour les utilisateurs, mais également une réduction de la charge de travail pour le système.
Afin d’éviter les effets de bord indésirables, il est important de considérer la mise en place de mécanismes visant à invalider ou mettre à jour le cache lorsque les données sources changent. De plus, le cache doit être une amélioration, et non une fonctionnalité indispensable au bon fonctionnement de votre application. Cela signifie que sans cache, l’application doit pouvoir fonctionner (moins rapidement certes).
Enfin, un système de caching n’a pas pour vocation à remplacer une base de données. Si on reprend l’exemple du réseau social ci-dessus, il faudrait utiliser des TTL (Time To Live) afin d’invalider les données au-delà d’une certaine durée. C’est également un mécanisme courant visant à limiter l’impact de potentiels problèmes de synchronisation. S’il existe par exemple un cas d’utilisation permettant de mettre à jour une donnée, laquelle ne gèreraient pas la mise à jour du cache, alors le TTL pourrait avoir comme avantage la « résolution automatique » du problème au delà d’une certaine durée. Bien sûr, cela n’empêche pas de corriger le problème initial.
Partitionnement des données
Même en utilisant un système de gestion de base de données adapté à l’évolutivité horizontal comme Cassandra ou MongoDB, cela ne suffit pas si vous souhaitez gérer une quantité de données théoriquement « illimitée ». En effet, la réplication permet de pallier à de potentielles pannes d’une instance, sans que les données (ou une partie de celles-ci) ne soient indisponibles. Cela permet également de répartir la charge de travail (en ayant plusieurs instances capables de faire des requêtes en parallèle). Néanmoins, cela ne résout pas le problème de la quantité de données, puisque celles-ci sont intégralement dupliquées sur chaque instance par défaut.
Pour résoudre ce problème, on utilise une technique dite de « partitionnement » (ou sharding), le but étant de diviser les données en parties plus petites nommées « fragments ». Chaque nœud d’un système héberge ainsi un certain nombre de fragments, mais pas l’intégralité. La répartition des données peut se faire de différentes manières suivant le système et la logique utilisée, mais le but reste le même : essayer de regrouper les données requêtées ensembles. Par exemple, pour une application de recettes de cuisine, un des critères sélectionné pour définir le « découpage » pourrait être la langue de la recette, étant donné qu’un utilisateur n’accède aux recettes qu’une seule langue à la fois.
En plus de répartir la charge de stockage, cette approche peut également améliorer les performances, puisque la quantité de données de chaque nœud est moins importante. Enfin, il est tout à fait possible de combiner fragmentation des données et redondance. La plupart des bases de données prévues pour évoluer horizontalement permettent de définir le nombre de réplicas des fragments.
Optimisation des performances front-end
Réduire le poids des pages web
Les images et les vidéos représentent une quantité importante des données transmises sur le web. Heureusement, de nombreux formats optimisés permettent de réduire la taille de ces médias. Il s’agit d’un point non négligeable à prendre en considération si vous souhaitez améliorer les performances de votre application web :
- Réduction du stockage nécessaire sur les serveurs (d’autant plus si le contenu est répliqué)
- Diminution des opérations de lecture/écriture sur le serveur
- Libération de la bande passante
- Contenu plus rapide à charger
- Etc…
Prenons comme exemple le format WEPB. Il est open-source, et procurerait 30% à 80% de réduction sur la taille des fichiers par rapport à PNG ou JPEG.
Pour les vidéos, on retrouve également HLS, qui permet de s’adapter au réseau de l’utilisateur.
Bref, vous l’aurez compris, il s’agit d’une piste très intéressante si vous souhaitez optimiser votre application, d’autant plus que cela vous permettra également un meilleur référencement.
Optimiser la livraison du contenu
Quitte à dupliquer vos contenus statiques (images, vidéos, fichiers Javascript, etc…), il peut être pertinent d’utiliser un Content Delivery Network (CDN).
Ces systèmes permettent de réduire la latence en distribuant le contenu depuis des emplacements plus proches des utilisateurs. Ainsi, au lieu de parcourir la terre entière pour obtenir le contenu souhaité, un utilisateur pourra se connecter (en toute transparence) au serveur le plus près de lui.
Les offres cloud sont nombreuses à permettre une gestion complète du stockage des fichiers, afin de simplifier et d’optimiser leur disponibilité. Azure File Storage, AWS S3, OVH Object Storage, etc… Libre à vous d’utiliser la solution qui vous convient. Avec un Service Level Agreement (SLA) élevé, ces offres sont des alternatives pertinentes si vous souhaitez garantir la disponibilité de vos fichiers.
Vous pouvez également employer des techniques comme le Lazy Loading, consistant à charger une ressource uniquement lorsque celle-ci est utilisée. Dans ce contexte précis, il pourrait s’agir d’une image qu’il ne serait pas nécessaire de charger tant que celle-ci n’entre pas dans le « viewport » de l’utilisateur. Cela réduirait le temps de chargement initiale, et éviterait potentiellement un chargement inutile si l’utilisateur ne venait jamais à scroller jusqu’à l’image.
A noter bien sûr que ces optimisations ne sont pertinentes que pour des applications ayant un trafic très important, ou si vous souhaitez optimiser votre référencement auprès des moteurs de recherche.
Compression et Minification
Compresser les ressources
Aujourd’hui, peu de développeurs diffusent le code HTML, CSS et JavaScript qu’ils produisent directement. On utilise aujourd’hui des outils tels que Gzip ou Brotli, visant à compresser ces ressources, et ainsi réduire significativement leur taille, afin d’accélérer le temps de transfert sur le réseau.
De nombreux Frameworks frontend intègrent nativement ces fonctionnalités, ou proposent en tout cas une intégration simplifiée. Le jeu en vaut la chandelle, puisque cette configuration (si elle est nécessaire) ne demande que très peu de temps grâce aux technologies actuelles.
Minifier les ressources
Sur un registre similaire à la compression des ressources, un aspect qui va généralement de pair est la minification des ressources. Il s’agit ici d’éliminer les données inutiles sans affecter le fonctionnement du code, comme les espaces, les tabulations et les commentaires, ou encore remplacer les noms de variables et de fonctions longs.
Il existe des outils développés dans ce but, comme UglifyJS pour JavaScript ou CSSNano pour le CSS.
Conclusion
L’optimisation des performances est un processus continu qui nécessite une évaluation et une adaptation régulières pour répondre aux nouvelles exigences et aux changements de la charge. En appliquant ces stratégies, vous pouvez non seulement améliorer l’expérience utilisateur mais aussi préparer votre application à une scalabilité efficace.
Nous verrons dans un prochain article les aspects de la scalabilité qui concernent la fiabilité et la résilience, alors j’espère vous revoir bientôt 🙂