veza/veza-backend-api/P1_RES_002_STREAM_SERVICE_RETRY_REPORT.md

319 lines
9.6 KiB
Markdown
Raw Normal View History

2025-12-13 02:34:34 +00:00
# ✅ P1-RES-002 — RETRY MÉCANISME POUR STREAMSERVICE
**Date**: 2025-12-13
**Objectif**: Ajouter un mécanisme de retry avec backoff exponentiel pour `StreamService` afin d'éviter la perte de jobs si le stream-server est temporairement indisponible
---
## 📋 RÉSUMÉ
**Retry implémenté** : Pattern similaire à `WebhookService` (3 tentatives, backoff exponentiel)
**Respect du contexte** : Vérification du contexte avant chaque tentative et pendant le backoff
**Tests complets** : 6 tests couvrent tous les scénarios (retry, max retries, erreurs réseau, timeout, cancellation, backoff timing)
**Compilation réussie** : Aucune erreur de compilation
---
## 📁 FICHIERS MODIFIÉS
### 1. `internal/services/stream_service.go`
-**Méthode `StartProcessing` modifiée** : Ajout du mécanisme de retry avec backoff exponentiel
-**Pattern identique à `WebhookService`** : Réutilisation du pattern existant (maxRetries=3, backoff exponentiel)
-**Respect du contexte** : Vérification du contexte avant chaque tentative et pendant le backoff
### 2. `internal/services/stream_service_retry_test.go` (nouveau)
-**6 tests complets** :
1. `TestStreamService_StartProcessing_RetryOnFailure` : Retry fonctionne quand le serveur échoue temporairement
2. `TestStreamService_StartProcessing_MaxRetriesExceeded` : Échec après max retries
3. `TestStreamService_StartProcessing_RetryOnNetworkError` : Retry sur erreurs réseau
4. `TestStreamService_StartProcessing_ContextTimeout` : Respect du timeout du contexte
5. `TestStreamService_StartProcessing_ContextCancelledDuringBackoff` : Annulation pendant le backoff
6. `TestStreamService_StartProcessing_BackoffTiming` : Vérification du timing du backoff exponentiel
---
## 🔍 IMPLÉMENTATION
### Pattern de retry (identique à WebhookService)
```go
maxRetries := 3
backoff := time.Second
for i := 0; i < maxRetries; i++ {
// Vérifier si le contexte est annulé avant chaque tentative
select {
case <-ctx.Done():
return fmt.Errorf("context cancelled before attempt %d: %w", i+1, ctx.Err())
default:
}
// Créer une nouvelle requête pour chaque tentative
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(jsonBody))
// ...
resp, err := s.client.Do(req)
if err != nil {
// Log et retry si possible
if i < maxRetries-1 {
select {
case <-ctx.Done():
return fmt.Errorf("context cancelled during backoff: %w", ctx.Err())
case <-time.After(backoff):
backoff *= 2 // Exponential backoff: 1s, 2s, 4s
}
continue
}
return fmt.Errorf("stream server request failed after %d attempts: %w", maxRetries, err)
}
if resp.StatusCode == http.StatusOK {
return nil // Succès
}
// Status code non-OK : retry si possible
if i < maxRetries-1 {
select {
case <-ctx.Done():
return fmt.Errorf("context cancelled during backoff: %w", ctx.Err())
case <-time.After(backoff):
backoff *= 2 // Exponential backoff: 1s, 2s, 4s
}
}
}
```
### Comportement exact
**N tentatives** : 3 maximum (configurable via `maxRetries`)
**Backoff exponentiel** :
- Tentative 1 : Immédiate
- Tentative 2 : Après 1 seconde (backoff initial)
- Tentative 3 : Après 2 secondes (backoff * 2)
- Total minimum : ~3 secondes (1s + 2s)
**Erreurs finales** :
- Si toutes les tentatives échouent : `"stream server request failed after 3 attempts: <error>"`
- Si le contexte est annulé : `"context cancelled before attempt N: <error>"` ou `"context cancelled during backoff: <error>"`
- Si le status code est non-OK après toutes les tentatives : `"stream server returned non-200 status after 3 attempts"`
**Respect du contexte** :
- Vérification avant chaque tentative
- Vérification pendant le backoff (avec `select` sur `ctx.Done()`)
- Annulation immédiate si le contexte est annulé
---
## 🧪 PREUVES (TESTS)
### Tests unitaires
```bash
go test ./internal/services -run TestStreamService_StartProcessing_Retry -v -count=1
```
**Résultat** : ✅ **Tous les tests passent (6/6)**
#### Test 1 : Retry sur échec temporaire
```go
TestStreamService_StartProcessing_RetryOnFailure
```
- ✅ Mock serveur échoue 2 fois puis réussit
- ✅ Vérifie que 3 tentatives sont faites
- ✅ Vérifie que la requête réussit finalement
#### Test 2 : Max retries atteint
```go
TestStreamService_StartProcessing_MaxRetriesExceeded
```
- ✅ Mock serveur échoue toujours
- ✅ Vérifie que exactement 3 tentatives sont faites
- ✅ Vérifie que l'erreur mentionne "after 3 attempts"
#### Test 3 : Retry sur erreur réseau
```go
TestStreamService_StartProcessing_RetryOnNetworkError
```
- ✅ Mock serveur ferme la connexion (simule erreur réseau)
- ✅ Vérifie que le retry fonctionne pour les erreurs réseau
- ✅ Vérifie que 3 tentatives sont faites
#### Test 4 : Timeout du contexte
```go
TestStreamService_StartProcessing_ContextTimeout
```
- ✅ Mock serveur prend trop de temps
- ✅ Contexte avec timeout de 500ms
- ✅ Vérifie que l'erreur mentionne "context"
#### Test 5 : Annulation pendant backoff
```go
TestStreamService_StartProcessing_ContextCancelledDuringBackoff
```
- ✅ Mock serveur échoue toujours
- ✅ Contexte annulé après le premier échec (pendant le backoff)
- ✅ Vérifie que l'erreur mentionne "context cancelled"
- ✅ Vérifie que seulement 1 tentative est faite
#### Test 6 : Timing du backoff
```go
TestStreamService_StartProcessing_BackoffTiming
```
- ✅ Mock serveur échoue 2 fois puis réussit
- ✅ Vérifie que le timing total est ~3s minimum
- ✅ Vérifie que les intervalles entre tentatives sont corrects (1s, 2s)
### Tests complets
```bash
go test ./internal/services -v -count=1
```
**Résultat** : ✅ **Tous les tests passent**
---
## 📊 COMPARAISON AVANT/APRÈS
### Avant (Sans retry)
```go
resp, err := s.client.Do(req)
if err != nil {
return fmt.Errorf("failed to send request: %w", err) // ❌ Pas de retry
}
```
**Problèmes** :
- ❌ Une panne temporaire du stream-server cause la perte du job
- ❌ Pas de résilience face aux erreurs réseau temporaires
- ❌ État incohérent si le job est créé mais le stream-server ne le reçoit pas
### Après (Avec retry)
```go
maxRetries := 3
backoff := time.Second
for i := 0; i < maxRetries; i++ {
// Vérification contexte + retry avec backoff
// ...
}
```
**Avantages** :
- ✅ Résilience face aux pannes temporaires (3 tentatives)
- ✅ Backoff exponentiel pour éviter la surcharge
- ✅ Respect du contexte (annulation possible)
- ✅ Logs détaillés pour debugging
- ✅ Pattern cohérent avec `WebhookService`
---
## ✅ VALIDATION
### Compilation
```bash
go build ./internal/services/...
```
**Résultat** : ✅ **Compilation réussie**
### Tests unitaires
```bash
go test ./internal/services -run TestStreamService_StartProcessing_Retry -v
```
**Résultat** : ✅ **Tous les tests passent (6/6)**
### Tests complets
```bash
go test ./internal/services -v -count=1
```
**Résultat** : ✅ **Tous les tests passent**
### Tests existants (non-régression)
```bash
go test ./internal/services -run TestStreamService_StartProcessing -v
```
**Résultat** : ✅ **Tous les tests existants passent**
---
## 🎯 OBJECTIFS ATTEINTS
-**Retry implémenté** : 3 tentatives avec backoff exponentiel (pattern identique à `WebhookService`)
-**Respect du contexte** : Vérification avant chaque tentative et pendant le backoff
-**Tests complets** : 6 tests couvrent tous les scénarios (retry, max retries, erreurs réseau, timeout, cancellation, backoff timing)
-**Non-régression** : Tous les tests existants passent toujours
-**Pattern réutilisé** : Aucune nouvelle dépendance, réutilisation du pattern existant
---
## 📋 COMMANDES DE VALIDATION
### Compilation
```bash
go build ./internal/services/...
```
### Tests spécifiques
```bash
go test ./internal/services -run TestStreamService_StartProcessing_Retry -v -count=1
go test ./internal/services -run TestStreamService_StartProcessing -v -count=1
```
### Tests complets
```bash
go test ./internal/services -v -count=1
```
---
## 📝 COMPORTEMENT EXACT
### N tentatives
**Maximum** : 3 tentatives (configurable via `maxRetries`)
**Scénarios** :
- **Succès à la tentative N** : Retourne immédiatement avec succès
- **Échec après 3 tentatives** : Retourne une erreur avec message "after 3 attempts"
- **Annulation du contexte** : Retourne immédiatement avec erreur "context cancelled"
### Backoff exponentiel
**Séquence** :
- Tentative 1 : Immédiate (pas de backoff)
- Tentative 2 : Après 1 seconde (backoff initial = 1s)
- Tentative 3 : Après 2 secondes (backoff * 2 = 2s)
**Total minimum** : ~3 secondes (1s + 2s) si toutes les tentatives échouent
**Respect du contexte** : Le backoff peut être interrompu si le contexte est annulé
### Erreurs finales
**Format** :
- Erreur réseau après max retries : `"stream server request failed after 3 attempts: <error>"`
- Status code non-OK après max retries : `"stream server returned non-200 status after 3 attempts"`
- Contexte annulé avant tentative : `"context cancelled before attempt N: <error>"`
- Contexte annulé pendant backoff : `"context cancelled during backoff: <error>"`
**Logs** :
- Tentative échouée : `Warn` avec `attempt`, `max_retries`, `error`
- Succès : `Info` avec `track_id`, `attempt`
---
**Statut final** : ✅ **P1-RES-002 IMPLÉMENTÉ ET VALIDÉ**
**Note** : Le mécanisme de retry est maintenant identique à celui de `WebhookService`, assurant une cohérence dans la gestion des appels HTTP externes.