veza/veza-backend-api/docs/CIRCUIT_BREAKERS.md

273 lines
6 KiB
Markdown
Raw Normal View History

2025-12-16 16:23:49 +00:00
# Circuit Breakers — Documentation
**Date**: 2025-01-27
**Status**: ✅ **IMPLEMENTED** - MOD-P2-007
---
## Vue d'ensemble
Les circuit breakers protègent l'application contre les dépendances externes lentes ou indisponibles en interrompant automatiquement les appels après un seuil d'échecs.
### Implémentation
- **Bibliothèque**: `github.com/sony/gobreaker`
- **Wrapper**: `internal/services/circuit_breaker.go`
- **Métriques**: `internal/metrics/circuit_breaker.go`
---
## Configuration
### Paramètres par défaut
```go
MaxRequests: 3 // Requêtes simultanées max
Interval: 60s // Réinitialisation des compteurs
Timeout: 30s // Délai avant half-open
ReadyToTrip: 5 échecs // Seuil pour ouvrir le circuit
```
### États du Circuit Breaker
1. **Closed** (Fermé): État normal, toutes les requêtes passent
2. **Open** (Ouvert): Circuit ouvert après 5 échecs consécutifs, requêtes rejetées
3. **Half-Open** (Demi-ouvert): Après 30s, permet quelques requêtes de test
---
## Utilisation
### Création d'un client avec circuit breaker
```go
import (
"veza-backend-api/internal/services"
"go.uber.org/zap"
)
logger := zap.NewNop()
httpClient := &http.Client{Timeout: 10 * time.Second}
cbClient := services.NewCircuitBreakerHTTPClient(
httpClient,
"my-service", // Nom du circuit breaker (pour métriques)
logger,
)
```
### Exécution d'une requête
```go
req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
resp, err := cbClient.Do(req)
if err != nil {
// Gérer l'erreur (circuit ouvert, timeout, 5xx, etc.)
return err
}
defer resp.Body.Close()
```
### Avec contexte (timeout/cancellation)
```go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
resp, err := cbClient.DoWithContext(ctx, req)
```
---
## Services Intégrés
### 1. Stream Service
**Fichier**: `internal/services/stream_service.go`
```go
circuitBreaker: NewCircuitBreakerHTTPClient(
httpClient,
"stream-service",
logger,
)
```
**Utilisation**: Appels HTTP vers le serveur de streaming pour transcodage.
### 2. OAuth Service
**Fichier**: `internal/services/oauth_service.go`
```go
circuitBreaker: NewCircuitBreakerHTTPClient(
httpClient,
"oauth-service",
logger,
)
```
**Utilisation**: Appels HTTP vers les providers OAuth (Google, GitHub, Discord).
---
## Métriques Prometheus
Les métriques suivantes sont exposées automatiquement:
### `veza_circuit_breaker_state`
**Type**: Gauge
**Labels**: `circuit_breaker_name`
**Valeurs**:
- `0` = Closed
- `1` = Half-Open
- `2` = Open
**Exemple**:
```
veza_circuit_breaker_state{circuit_breaker_name="stream-service"} 0
```
### `veza_circuit_breaker_requests_total`
**Type**: Counter
**Labels**: `circuit_breaker_name`, `result` (success|failure|rejected)
**Exemple**:
```
veza_circuit_breaker_requests_total{circuit_breaker_name="stream-service",result="success"} 150
veza_circuit_breaker_requests_total{circuit_breaker_name="stream-service",result="failure"} 5
veza_circuit_breaker_requests_total{circuit_breaker_name="stream-service",result="rejected"} 2
```
### `veza_circuit_breaker_failures_total`
**Type**: Counter
**Labels**: `circuit_breaker_name`
**Exemple**:
```
veza_circuit_breaker_failures_total{circuit_breaker_name="stream-service"} 5
```
### `veza_circuit_breaker_consecutive_failures`
**Type**: Gauge
**Labels**: `circuit_breaker_name`
**Exemple**:
```
veza_circuit_breaker_consecutive_failures{circuit_breaker_name="stream-service"} 3
```
---
## Comportement sur Erreurs
### Codes HTTP 5xx
Les codes HTTP 5xx (500, 502, 503, etc.) sont considérés comme des **échecs** et comptent pour le circuit breaker:
```go
if resp.StatusCode >= 500 {
resp.Body.Close()
return nil, fmt.Errorf("server error: %d", resp.StatusCode)
}
```
### Circuit Ouvert
Quand le circuit est ouvert, les requêtes sont **rejetées immédiatement** sans appel HTTP:
```go
if err == gobreaker.ErrOpenState {
return nil, fmt.Errorf("circuit breaker is open: service unavailable")
}
```
---
## Tests
### Tests unitaires
```bash
go test ./internal/services -v -run TestCircuitBreaker
```
**Tests inclus**:
- Création du client
- Requêtes réussies
- Gestion des erreurs 5xx
- Ouverture du circuit après seuil
- Rejet de requêtes quand circuit ouvert
- Support du contexte (timeout/cancellation)
### Test d'intégration (mock server)
Un test simule un serveur qui retourne 5xx pour déclencher l'ouverture du circuit:
```bash
go test ./internal/services -v -run TestCircuitBreakerHTTPClient_Do_ServerError
```
---
## Variables d'Environnement
Aucune variable d'environnement requise. La configuration est codée en dur dans le wrapper pour simplifier.
**Pour personnaliser** (si nécessaire):
- Modifier `internal/services/circuit_breaker.go`
- Ajuster `MaxRequests`, `Interval`, `Timeout`, `ReadyToTrip`
---
## Monitoring et Alertes
### Alertes recommandées
1. **Circuit ouvert trop souvent**:
```
veza_circuit_breaker_state{circuit_breaker_name="stream-service"} == 2
```
2. **Taux d'échec élevé**:
```
rate(veza_circuit_breaker_requests_total{result="failure"}[5m]) > 0.1
```
3. **Échecs consécutifs**:
```
veza_circuit_breaker_consecutive_failures > 3
```
---
## Dépannage
### Circuit reste ouvert
**Cause**: Service externe toujours en erreur
**Solution**: Vérifier la santé du service externe, attendre 30s (Timeout) pour half-open
### Trop de rejets
**Cause**: Seuil trop bas (5 échecs)
**Solution**: Augmenter `ReadyToTrip` dans `circuit_breaker.go`
### Métriques manquantes
**Cause**: Métriques non initialisées
**Solution**: Vérifier que `internal/metrics/circuit_breaker.go` est importé
---
## Références
- [gobreaker Documentation](https://github.com/sony/gobreaker)
- [Circuit Breaker Pattern](https://martinfowler.com/bliki/CircuitBreaker.html)
- [Prometheus Metrics](https://prometheus.io/docs/concepts/metric_types/)
---
**Dernière mise à jour**: 2025-01-27
**Maintenu par**: Veza Backend Team