Plus que toute autre chose, les choix d'architecture ont une importance majeure lors de la conception d'un système destiné à être hautement scalable et hautement disponible. En prenant les clients d'Azure comme exemple, Microsoft aborde les patterns et les anti-patterns qu'ils observent chez leurs clients Azure et les effets qu'ils ont sur les quatre aspects de l'architecture système suivants :
- Scalablitié : Puis-je ajouter de la capacité pour faire face à une demande plus forte ?
- Disponibilité : Mon application pourra-t-elle supporter les défaillances transitoires ou durables ?
- Supervision : Est-ce que je dispose de moyens pour analyser l'état et les performances du système en production ?
- Faisabilité : Puis-je créer et maintenir le système avec le temps et le budget qui me sont impartis ?
Scalabilité
La scalabilité comporte deux aspects : capacité et densité. La capacité consiste à ajouter de nouveaux composants matériels, ce qui peut être trivial (ajouter un nouveau serveur web derrière un loab balancer) ou très difficile (ajouter un second serveur de données). La densité fait référence à l'efficacité avec laquelle vous utilisez la capacité dont vous disposez déjà. Une optimisation classique des performances peut permettre d'augmenter la densité de manière significative.
Encadré : S'éclairer en brûlant des billets de banque
Un thème courant pendant la présentation est "S'éclairer en brûlant des billets de banque". Par cette expression, Mark Simms désigne une action inefficace effectuée sans raison. Comme par exemple, utiliser un NAT au lieu d'un load balancer, ou utiliser XML comme format d'échange de données interne.
Les Ressources mesurées
Une ressource mesurée est une ressource qui doit être surveillée avec soin. Un exemple de ressource mesurée est une connexion à une base de données. Comme il s'agit d'une ressource limitée, l'utiliser à mauvais escient peut affecter radicalement la densité.
Pour illustrer ce propos, considérons Azure SQL. La version standard offre 180 connexions par base de données. La taille par défaut du pool de connexion de ADO.NET est de 100. Par conséquent, si vous disposez de deux serveurs connectés à une base de données Asure SQL, et que ces serveurs subissent des pertes de connexions, vous pouvez facilement dépasser la limite.
D'autres exemples de ressources mesurées sont les serveurs d'authentification et les web services tiers. Ces ressources constituent parfois des "ressources cachées", car souvent, les développeurs les négligent quand ils planifient leur architecture.
Répartir la charge à l'aide de files d'attente
Les pics d'uploads peuvent être problématiques, particulièrement dans les systèmes qui sont réglés pour offrir une forte charge de lecture. Une manière d'alléger le problème est de négocier la latence de disponibilité en utilisant des files d'attente.
Cette stratégie implique que les données ne soient pas stockées en base de manière synchrone. A la place, elles sont envoyées à une file d'attente consommée par des processus en arrière plan. Ce processus en arrière plan peut lisser la charge afin que la base de données subisse une charge régulière plutôt qu'elle soit martelée à certains moments et inactive le reste du temps.
Un autre avantage qu'offre l’utilisation de files d'attente est la possibilité de traiter les opérations par lots. D'une manière générale, le chargement d'une base de données par lots offre des performances meilleures que l'écriture des enregistrements un par un.
Enfin, l'ajout d'une file apporte un point de découplage. Le processus en arrière plan ou la base de données pourraient être complètement hors service sans que la capacité de l'application à recevoir de nouvelles données soit affectée.
Améliorer la disponibilité des files d'attentes de messages
Si trop de messages sont reçus au même moment, des files d'attente de secours peuvent être utilisées pour répartir le stockage des messages. Afin de mettre ceci en oeuvre, vous devez concevoir une application qui supporte plusieurs files d'attente, même si vous comptez n'en mettre qu'une seule en place dans un premier temps.
Quand les messages dépassent la taille que l'application permet de gérer, une technique qui permet d'éviter la perte de données consiste à stocker le message dans une base orientée documents (blob storage). Plutôt que de stocker le message complet dans la file d'attente, on se contente de stocker un pointeur vers l'entrée stockée dans la base.
Disponibilité du serveur web
Afin de préserver la disponibilité du serveur web, tous les flux descendants doivent être asynchrones et limités. Ces limitations concernent aussi bien les time-outs que le nombre de requêtes simultanées. Ce second aspect est souvent négligé. Un exemple un peu embarrassant de ce problème est la coupure de deux heures qu'a connu Visual Studio Online. La cause initiale de ce problème était liée à un serveur d'authentification qui recevait un nombre trop important de requêtes simultanées et qui s'est retrouvé temporairement hors service.
Services d'authentification
Ce qui nous amène à notre sujet suivant, les services d'authentification. Lorsqu'un serveur d'authentification tombe en panne, il peut littéralement entraîner avec lui une application par ailleurs stable. C'est pour cette raison que Microsoft recommande fortement l'utilisation d'un système d'authentification décentralisé.
Log des données invalides
La plupart des développeurs comprennent l’intérêt de valider des données, mais ils ne savent généralement pas quoi faire lorsque cette validation échoue. Ecarter simplement les données et lever une exception ne suffit pas. Les données invalides devraient être loggées dans leur forme brute afin que les développeurs puissent comprendre pourquoi une requête invalide a été soumise.
La plupart des requêtes invalides proviennent de problèmes de compatibilité de versions. Ce type de problèmes survient généralement lorsque l'utilisateur dispose d'un client qui est dans une version plus ancienne ou plus récente que celle du serveur.
Anti-Pattern : Configuration
Lorsque l'équipe de Microsoft Azure audite du code client, ils voient souvent des chaînes de connexion codées en dur et autres données de configuration. Ce type de pratiques peut poser de réels problèmes lorsque la configuration doit être changée rapidement pour pointer vers des ressources différentes.
Anti-Pattern : Présomption de fiabilité de la base de données
Pour la dernière génération de développeurs, la connectivité aux bases de données est un acquis. Les défaillances de bases de données et de réseaux internes n'arrivent pratiquement jamais. Il est donc fréquent que les développeurs ne vérifient pas les exceptions. Ou s'ils le font, ils ne les traitent pas correctement et les données sont perdues.
Anti-Pattern : Injection de SQL
Oui, il s'agit d'un problème courant. Dans certains cas, la première requête web examinée présente une faille évidente d'injection de SQL.
Anti-Pattern : Logger vers une ressource hors service
L'infrastructure chargée des logs doit être isolée du reste des applications. Si les logs sont écrits dans la même base de données que les données de production, perdre une des bases de données implique la perte de l'autre base.
Anti-Pattern : relancer les exceptions
On observe deux anti-patterns communs dans ce domaine. Le premier consiste à relancer les exceptions à l'aide de "throw ex;" au lieu de "throw;", ce qui entraîne la perte de la stacktrace. Le second consiste à relancer l'exception quand il n'existe pas de gestionnaire d'exceptions à un niveau supérieur. Ceci entraîne une défaillance de l'application dans son ensemble dans les versions de .NET 2.0 et plus récentes.
Pour visionner la vidéo entièrement, rendez-vous sur Building Big : Les lessons apprises grâce aux clients Azure, sur Channel 9.