Scalabilité et capacité

Mise à l'échelle horizontale et verticale, auto-scaling, planification de capacité et tests de charge pour préparer le système à la demande.

Qu’est-ce que la scalabilité ?

La scalabilité est la capacité d’un système à gérer une augmentation de la charge de travail sans dégrader ses performances. Un système scalable peut croître pour servir davantage d’utilisateurs, de requêtes ou de données sans qu’il soit nécessaire de le repenser.

La scalabilité n’est pas la même chose que les performances :

  • Performances : la vitesse à laquelle le système répond avec la charge actuelle
  • Scalabilité : la capacité à maintenir ses performances lorsque la charge augmente

Mise à l’échelle verticale et horizontale

Mise à l’échelle verticale (scale up)

Elle consiste à ajouter davantage de ressources à une machine existante : plus de CPU, plus de RAM, plus de disque.

Avantages :

  • Simple à mettre en œuvre — ne nécessite aucune modification de l’application
  • N’introduit pas de complexité liée à la distribution

Inconvénients :

  • Elle a une limite physique — on ne peut pas monter en charge à l’infini
  • Point unique de défaillance — si la machine tombe, tout tombe
  • Généralement plus coûteuse à grande échelle

Mise à l’échelle horizontale (scale out)

Elle consiste à ajouter davantage d’instances du service et à répartir la charge entre elles.

Avantages :

  • Théoriquement illimitée — on peut ajouter autant d’instances que nécessaire
  • Plus grande résilience — si une instance tombe, les autres continuent de fonctionner
  • Plus économique à grande échelle (matériel standard)

Inconvénients :

  • Nécessite que l’application soit stateless ou qu’elle gère un état distribué
  • Introduit de la complexité : répartition de charge, cohérence des données, sessions distribuées
  • Plus difficile à déboguer

Quand utiliser chacune ?

ScénarioRecommandation
Base de données relationnelleVerticale d’abord, puis read replicas
Services web statelessHorizontale
Files de messagesHorizontale (partitions)
CacheHorizontale (sharding)
Application monolithiqueVerticale d’abord, puis décomposer

Auto-scaling

L’auto-scaling ajuste automatiquement le nombre d’instances en fonction de la demande actuelle.

Horizontal Pod Autoscaler (HPA) dans Kubernetes

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: my-service-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: my-service
  minReplicas: 2
  maxReplicas: 20
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80

Métriques pour l’auto-scaling

  • CPU utilization : la plus courante, met à l’échelle lorsque le CPU dépasse un seuil
  • Memory utilization : utile pour les applications memory-intensive
  • Request rate : met à l’échelle en fonction du nombre de requêtes par seconde
  • Queue depth : met à l’échelle lorsque la file de messages grandit
  • Custom metrics : latence p99, erreurs par seconde, etc.

Bonnes pratiques d’auto-scaling

  1. Définir des minimums et maximums raisonnables : éviter de descendre à 0 (cold start) ou de monter à l’infini (coûts)
  2. Cooldown periods : attendre avant de redescendre pour éviter les oscillations
  3. Monter en charge à l’avance : si l’on connaît des pics prévisibles (ex. : Black Friday), pré-provisionner
  4. Surveiller l’auto-scaler : vérifier qu’il monte en charge quand il le doit et qu’il n’oscille pas

Planification de capacité

La planification de capacité est le processus qui consiste à déterminer combien de ressources le système a besoin pour gérer la charge attendue.

Processus

  1. Définir les métriques clés : requêtes par seconde, utilisateurs concurrents, volume de données
  2. Mesurer la capacité actuelle : quelle charge le système peut gérer aujourd’hui
  3. Projeter la croissance : à partir des données historiques et des plans d’affaires
  4. Calculer les ressources nécessaires : CPU, mémoire, stockage, bande passante
  5. Ajouter une marge de sécurité : généralement 30 à 50 % au-dessus de la projection

Exemple pratique

Situation actuelle :
- 1000 requêtes/seconde avec 3 pods
- Chaque pod gère ~350 req/s avant de se dégrader
- CPU moyen : 60%

Projection à 6 mois :
- 3000 requêtes/seconde attendues
- Nous avons besoin de : 3000 / 350 = ~9 pods
- Avec marge de sécurité (30%) : 12 pods
- Auto-scaling : min=9, max=15

Tests de charge

Les tests de charge vérifient que le système peut gérer la charge attendue et aident à identifier les goulots d’étranglement.

Types de tests de charge

  • Load test : simule la charge attendue dans des conditions normales
  • Stress test : augmente la charge au-delà de ce qui est attendu pour trouver le point de rupture
  • Spike test : simule des pics de trafic soudains
  • Soak test : maintient une charge constante pendant des heures pour détecter les memory leaks et la dégradation progressive

Outils populaires

  • k6 : outil moderne de test de charge avec des scripts en JavaScript
  • Apache JMeter : outil classique doté d’une interface graphique
  • Locust : test de charge en Python, distribué
  • Artillery : test de charge pour les API et les microservices
  • Gatling : test de charge en Scala avec des rapports détaillés

Exemple avec k6

import http from 'k6/http';
import { check, sleep } from 'k6';

export const options = {
  stages: [
    { duration: '2m', target: 100 },  // ramp up a 100 usuarios
    { duration: '5m', target: 100 },  // mantener 100 usuarios
    { duration: '2m', target: 200 },  // subir a 200 usuarios
    { duration: '5m', target: 200 },  // mantener 200 usuarios
    { duration: '2m', target: 0 },    // ramp down
  ],
  thresholds: {
    http_req_duration: ['p(95)<500'],  // 95% de requests < 500ms
    http_req_failed: ['rate<0.01'],    // menos de 1% de errores
  },
};

export default function () {
  const res = http.get('https://api.example.com/products');
  check(res, {
    'status is 200': (r) => r.status === 200,
    'response time < 500ms': (r) => r.timings.duration < 500,
  });
  sleep(1);
}

Que mesurer dans les tests de charge

  • Throughput : requêtes par seconde que le système peut gérer
  • Latence : temps de réponse (p50, p95, p99)
  • Error rate : pourcentage de requêtes qui échouent
  • Ressources : CPU, mémoire, connexions à la BD pendant le test
  • Point de rupture : charge à laquelle le système commence à se dégrader

Patterns de scalabilité

Caching

Réduire la charge sur les services et les bases de données en stockant les réponses fréquentes :

  • CDN : pour les assets statiques et les réponses cacheables
  • Redis/Memcached : pour les données à accès fréquent
  • Cache applicatif : pour les calculs coûteux

Database read replicas

Répartir les lectures entre plusieurs réplicas de la base de données tandis que les écritures sont dirigées vers le primaire.

Sharding

Diviser les données horizontalement entre plusieurs instances de base de données à partir d’une clé de partition.

Event-driven architecture

Découpler les services à l’aide d’événements asynchrones pour absorber les pics de charge sans saturer les services downstream.

Résumé

La scalabilité nécessite planification et mesure continue. La mise à l’échelle horizontale est préférable pour les services stateless, l’auto-scaling automatise la réponse à la demande, la planification de capacité anticipe les besoins futurs et les tests de charge valident que le système peut gérer la charge attendue.