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énario | Recommandation |
|---|---|
| Base de données relationnelle | Verticale d’abord, puis read replicas |
| Services web stateless | Horizontale |
| Files de messages | Horizontale (partitions) |
| Cache | Horizontale (sharding) |
| Application monolithique | Verticale 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
- Définir des minimums et maximums raisonnables : éviter de descendre à 0 (cold start) ou de monter à l’infini (coûts)
- Cooldown periods : attendre avant de redescendre pour éviter les oscillations
- Monter en charge à l’avance : si l’on connaît des pics prévisibles (ex. : Black Friday), pré-provisionner
- 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
- Définir les métriques clés : requêtes par seconde, utilisateurs concurrents, volume de données
- Mesurer la capacité actuelle : quelle charge le système peut gérer aujourd’hui
- Projeter la croissance : à partir des données historiques et des plans d’affaires
- Calculer les ressources nécessaires : CPU, mémoire, stockage, bande passante
- 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.