Backend Go: - Remplacement complet des anciennes migrations par la base V1 alignée sur ORIGIN. - Durcissement global du parsing JSON (BindAndValidateJSON + RespondWithAppError). - Sécurisation de config.go, CORS, statuts de santé et monitoring. - Implémentation des transactions P0 (RBAC, duplication de playlists, social toggles). - Ajout d’un job worker structuré (emails, analytics, thumbnails) + tests associés. - Nouvelle doc backend : AUDIT_CONFIG, BACKEND_CONFIG, AUTH_PASSWORD_RESET, JOB_WORKER_*. Chat server (Rust): - Refonte du pipeline JWT + sécurité, audit et rate limiting avancé. - Implémentation complète du cycle de message (read receipts, delivered, edit/delete, typing). - Nettoyage des panics, gestion d’erreurs robuste, logs structurés. - Migrations chat alignées sur le schéma UUID et nouvelles features. Stream server (Rust): - Refonte du moteur de streaming (encoding pipeline + HLS) et des modules core. - Transactions P0 pour les jobs et segments, garanties d’atomicité. - Documentation détaillée de la pipeline (AUDIT_STREAM_*, DESIGN_STREAM_PIPELINE, TRANSACTIONS_P0_IMPLEMENTATION). Documentation & audits: - TRIAGE.md et AUDIT_STABILITY.md à jour avec l’état réel des 3 services. - Cartographie complète des migrations et des transactions (DB_MIGRATIONS_*, DB_TRANSACTION_PLAN, AUDIT_DB_TRANSACTIONS, TRANSACTION_TESTS_PHASE3). - Scripts de reset et de cleanup pour la lab DB et la V1. Ce commit fige l’ensemble du travail de stabilisation P0 (UUID, backend, chat et stream) avant les phases suivantes (Coherence Guardian, WS hardening, etc.).
19 KiB
🔍 AUDIT DES TRANSACTIONS DB — PROJET VEZA
Date : 2025-01-27
Objectif : Identifier toutes les opérations multi-étapes non transactionnelles qui peuvent laisser la DB dans un état incohérent
Phase : Hardening — Élimination des risques d'incohérence de données
📋 TABLE DES MATIÈRES
- Résumé Exécutif
- Backend Go
- Stream Server (Rust)
- Chat Server (Rust)
- Table Récapitulative
- Liste P0 Prioritaire
1. RÉSUMÉ EXÉCUTIF
Statistiques Globales
- Total opérations multi-étapes identifiées : 18
- Opérations transactionnelles : 8 (44%)
- Opérations non transactionnelles : 10 (56%)
- P0 (Critique) : 5 opérations
- P1 (Important) : 5 opérations
Risques Principaux
- Marketplace : Commandes partiellement créées (items sans order, licenses sans order)
- Playlists : Duplication incomplète, collaborateurs sans playlist
- Social : Compteurs de likes/comments désynchronisés
- Stream : Segments orphelins sans job, jobs sans segments
- RBAC : Assignations de rôles partiellement appliquées
2. BACKEND GO
2.1 Marketplace Service
✅ CreateOrder — TRANSACTIONNEL
Localisation : internal/core/marketplace/service.go:136-215
Flow actuel :
s.db.Transaction(func(tx *gorm.DB) error {
1. Valider produits + calculer total
2. CREATE order (PENDING)
3. UPDATE order (COMPLETED) + PaymentIntent
4. CREATE order_items (pour chaque produit)
5. CREATE licenses (pour chaque track)
})
État : ✅ Transactionnel — Toutes les écritures sont dans une transaction GORM
Risques : Aucun — En cas d'erreur, rollback complet
✅ CreateProduct — TRANSACTIONNEL
Localisation : internal/core/marketplace/service.go:69-99
Flow actuel :
s.db.Transaction(func(tx *gorm.DB) error {
1. Valider track existence + ownership
2. CREATE product
})
État : ✅ Transactionnel — Validation + création dans une transaction
Risques : Aucun
2.2 Playlist Services
✅ AddTrack — TRANSACTIONNEL
Localisation : internal/repositories/playlist_track_repository.go:41-124
Flow actuel :
r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
1. CREATE playlist_track
2. UPDATE playlists.track_count (+1)
})
État : ✅ Transactionnel — Création + mise à jour du compteur dans une transaction
Risques : Aucun
✅ RemoveTrack — TRANSACTIONNEL
Localisation : internal/repositories/playlist_track_repository.go:127-162
Flow actuel :
r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
1. DELETE playlist_track
2. UPDATE playlist_tracks.position (décalage)
3. UPDATE playlists.track_count (-1)
})
État : ✅ Transactionnel — Suppression + décalage positions + compteur dans une transaction
Risques : Aucun
✅ ReorderTracks — TRANSACTIONNEL
Localisation : internal/repositories/playlist_track_repository.go:165-198
Flow actuel :
r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
1. UPDATE playlist_tracks.position (pour chaque track)
})
État : ✅ Transactionnel — Toutes les mises à jour de positions dans une transaction
Risques : Aucun
❌ DuplicatePlaylist — NON TRANSACTIONNEL — P0
Localisation : internal/services/playlist_duplicate_service.go:41-131
Flow actuel :
1. GET original playlist
2. CREATE new playlist (via CreatePlaylist)
3. FOR each track:
4. AddTrackToPlaylist (chaque appel est transactionnel, mais pas l'ensemble)
État : ❌ NON Transactionnel — La duplication complète n'est pas dans une transaction
Risques concrets :
- Si crash après création de la playlist mais avant fin de l'ajout des tracks → Playlist vide créée
- Si crash au milieu de l'ajout des tracks → Playlist partiellement dupliquée (certains tracks manquants)
- Si
AddTrackToPlaylistéchoue pour un track, on continue avec les autres (ligne 117) → Playlist incomplète
Impact métier : ÉLEVÉ — Playlists dupliquées incomplètes, confusion utilisateur
Recommandation : Wrapper toute la duplication dans une transaction :
return s.playlistService.db.Transaction(func(tx *gorm.DB) error {
// Créer playlist
// Ajouter tous les tracks
// Si erreur → rollback complet
})
❌ AddCollaborator — NON TRANSACTIONNEL — P1
Localisation : internal/services/playlist_service.go:611-665
Flow actuel :
1. GET playlist (vérification ownership)
2. GET user (vérification existence)
3. CREATE playlist_collaborator (via repository)
État : ❌ NON Transactionnel — Vérifications + création séparées
Risques concrets :
- Si crash entre vérification et création → Pas de collaborateur créé (acceptable, mais incohérent si d'autres opérations dépendent)
- Si playlist supprimée entre vérification et création → Collaborateur créé pour playlist inexistante (contrainte FK devrait bloquer, mais pas garanti)
Impact métier : MOYEN — Risque faible mais possible
Recommandation : Wrapper dans une transaction si on veut garantir l'atomicité des vérifications + création
2.3 Social Services
❌ ToggleLike — NON TRANSACTIONNEL — P1
Localisation : internal/core/social/service.go:131-167
Flow actuel :
// Cas 1: Unlike
1. DELETE like
2. UPDATE post.like_count (-1) // ⚠️ Pas dans la même transaction
// Cas 2: Like
1. CREATE like
2. UPDATE post.like_count (+1) // ⚠️ Pas dans la même transaction
État : ❌ NON Transactionnel — Create/Delete + Update compteur séparés
Risques concrets :
- Si crash après DELETE like mais avant UPDATE compteur → Like supprimé mais compteur non décrémenté → Compteur désynchronisé
- Si crash après CREATE like mais avant UPDATE compteur → Like créé mais compteur non incrémenté → Compteur désynchronisé
Impact métier : MOYEN — Compteurs désynchronisés, mais données principales (like) cohérentes
Recommandation : Wrapper dans une transaction :
return s.db.Transaction(func(tx *gorm.DB) error {
// DELETE ou CREATE like
// UPDATE post.like_count
})
❌ AddComment — NON TRANSACTIONNEL — P1
Localisation : internal/core/social/service.go:169-188
Flow actuel :
1. CREATE comment
2. UPDATE post.comment_count (+1) // ⚠️ Pas dans la même transaction
État : ❌ NON Transactionnel — Création commentaire + mise à jour compteur séparés
Risques concrets :
- Si crash après CREATE comment mais avant UPDATE compteur → Commentaire créé mais compteur non incrémenté → Compteur désynchronisé
Impact métier : MOYEN — Compteurs désynchronisés, mais commentaire créé
Recommandation : Wrapper dans une transaction
2.4 RBAC Services
❌ AssignRoleToUser (RBACService) — NON TRANSACTIONNEL — P0
Localisation : internal/services/rbac_service.go:168-210
Flow actuel :
1. SELECT COUNT(*) FROM users WHERE id = $1 // Vérification existence
2. SELECT COUNT(*) FROM roles WHERE id = $1 // Vérification existence
3. SELECT COUNT(*) FROM user_roles WHERE ... // Vérification doublon
4. INSERT INTO user_roles ... // Assignation
État : ❌ NON Transactionnel — 4 queries séparées, pas de transaction
Risques concrets :
- Si crash entre vérifications et INSERT → Pas d'assignation créée (acceptable)
- Si user/role supprimé entre vérification et INSERT → Assignation créée pour user/role inexistant (contrainte FK devrait bloquer, mais pas garanti si suppression soft)
- Si race condition : 2 requêtes simultanées peuvent toutes deux passer les vérifications et créer 2 assignations → Doublon (contrainte UNIQUE devrait bloquer, mais erreur non gérée proprement)
Impact métier : ÉLEVÉ — Assignations de rôles incohérentes, sécurité compromise
Recommandation : Wrapper dans une transaction avec isolation level approprié :
return s.db.Transaction(func(tx *gorm.DB) error {
// Vérifications + INSERT dans la même transaction
})
❌ AssignRoleToUser (RoleService) — NON TRANSACTIONNEL — P1
Localisation : internal/services/role_service.go:86-99
Flow actuel :
1. CREATE user_role
État : ❌ NON Transactionnel — Simple CREATE, mais devrait vérifier existence user/role avant
Risques concrets :
- Si user/role n'existe pas → Erreur FK (gérée par DB, mais pas de validation préalable)
- Pas de vérification de doublon avant création
Impact métier : MOYEN — Erreurs DB non gérées proprement
Recommandation : Ajouter vérifications + wrapper dans transaction
2.5 HLS Queue Service
✅ CreateJob — TRANSACTIONNEL
Localisation : internal/services/hls_queue_service.go:77
Flow actuel :
s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// Création job + initialisation
})
État : ✅ Transactionnel
Risques : Aucun
2.6 Refresh Token Service
✅ RotateToken — TRANSACTIONNEL
Localisation : internal/services/refresh_token_service.go:70
Flow actuel :
s.db.Transaction(func(tx *gorm.DB) error {
// Invalider ancien token + créer nouveau
})
État : ✅ Transactionnel
Risques : Aucun
3. STREAM SERVER (RUST)
3.1 Segment Tracker
❌ persist_segment — NON TRANSACTIONNEL — P0
Localisation : src/core/processing/segment_tracker.rs:82-106
Flow actuel :
async fn persist_segment(&self, segment: &SegmentInfo) -> Result<(), AppError> {
1. INSERT INTO stream_segments (...) // Insert segment
2. self.update_current_duration().await?; // UPDATE stream_jobs.updated_at
}
État : ❌ NON Transactionnel — INSERT segment + UPDATE job séparés
Risques concrets :
- Si crash après INSERT segment mais avant UPDATE job → Segment créé mais job non mis à jour → Segments orphelins
- Si crash après UPDATE job mais avant INSERT segment → Job mis à jour mais segment non créé → Incohérence durée
Impact métier : ÉLEVÉ — Segments HLS orphelins, jobs avec métadonnées incorrectes, streaming cassé
Recommandation : Utiliser une transaction SQLx :
let mut tx = self.db.begin().await?;
sqlx::query!("INSERT INTO stream_segments ...").execute(&mut *tx).await?;
sqlx::query!("UPDATE stream_jobs ...").execute(&mut *tx).await?;
tx.commit().await?;
❌ EncodingPool (insert_segments_from_playlist) — NON TRANSACTIONNEL — P1
Localisation : src/core/encoding_pool.rs:300-349
Flow actuel :
for line in lines {
if segment_path.exists() {
sqlx::query!("INSERT INTO stream_segments ...")
.execute(&self.db_pool) // ⚠️ Pas de transaction
.await?;
segment_index += 1;
}
}
État : ❌ NON Transactionnel — Insertions de segments multiples sans transaction
Risques concrets :
- Si crash au milieu de la boucle → Segments partiellement insérés → Playlist HLS incomplète
Impact métier : MOYEN — Playlist HLS partiellement générée
Recommandation : Wrapper toutes les insertions dans une transaction :
let mut tx = self.db_pool.begin().await?;
for segment in segments {
sqlx::query!("INSERT ...").execute(&mut *tx).await?;
}
tx.commit().await?;
3.2 Stream Jobs
❌ Job Creation + Segment Persistence — NON TRANSACTIONNEL — P0
Localisation : src/core/processing/processor.rs + segment_tracker.rs
Flow actuel :
// Dans processor.rs
1. CREATE stream_job (status: processing)
2. Spawn FFmpeg
3. Segments détectés → persist_segment() (appelé plusieurs fois)
4. UPDATE stream_job (status: completed)
État : ❌ NON Transactionnel — Job créé, puis segments persistés individuellement, puis job mis à jour
Risques concrets :
- Si crash après création job mais avant segments → Job créé sans segments → Job orphelin
- Si crash pendant persistance segments → Segments partiellement créés → Job incomplet
- Si crash après segments mais avant UPDATE job → Segments créés mais job non finalisé → Job bloqué en "processing"
Impact métier : ÉLEVÉ — Jobs de transcodage incomplets, streaming cassé
Recommandation :
- Option 1 : Persister segments en batch à la fin (déjà fait dans
persist_all(), mais pas utilisé systématiquement) - Option 2 : Utiliser un pattern "two-phase" : job créé en "pending", segments persistés en batch, puis job finalisé en "completed" dans une transaction
4. CHAT SERVER (RUST)
4.1 Message Operations
✅ send_room_message — TRANSACTIONNEL
Localisation : src/hub/channels.rs:301-388
Flow actuel :
let mut tx = hub.db.begin().await?;
1. Vérifier membership
2. INSERT INTO messages
3. UPDATE messages.thread_count (si parent)
4. process_mentions() (INSERT mentions)
tx.commit().await?;
État : ✅ Transactionnel — Toutes les écritures dans une transaction SQLx
Risques : Aucun
✅ send_dm_message — TRANSACTIONNEL
Localisation : src/hub/direct_messages.rs:278-336
Flow actuel :
let mut tx = hub.db.begin().await?;
1. INSERT INTO messages
2. UPDATE messages.thread_count (si parent)
3. process_dm_mentions()
4. UPDATE dm_conversations.updated_at
tx.commit().await?;
État : ✅ Transactionnel — Toutes les écritures dans une transaction
Risques : Aucun
✅ process_batch (OptimizedPersistence) — TRANSACTIONNEL
Localisation : src/optimized_persistence.rs:663-699
Flow actuel :
let mut tx = self.pg_pool.begin().await?;
for message in &messages {
sqlx::query("INSERT INTO messages ...").execute(&mut *tx).await?;
}
tx.commit().await?;
État : ✅ Transactionnel — Toutes les insertions en batch dans une transaction
Risques : Aucun
5. TABLE RÉCAPITULATIVE
| Service | Opération | État | Priorité | Risque en cas de crash |
|---|---|---|---|---|
| Backend Go | ||||
| Marketplace | CreateOrder |
✅ Transactionnel | - | Aucun |
| Marketplace | CreateProduct |
✅ Transactionnel | - | Aucun |
| Playlist | AddTrack |
✅ Transactionnel | - | Aucun |
| Playlist | RemoveTrack |
✅ Transactionnel | - | Aucun |
| Playlist | ReorderTracks |
✅ Transactionnel | - | Aucun |
| Playlist | DuplicatePlaylist |
❌ NON | P0 | Playlist vide ou incomplète |
| Playlist | AddCollaborator |
❌ NON | P1 | Collaborateur sans playlist |
| Social | ToggleLike |
❌ NON | P1 | Compteur désynchronisé |
| Social | AddComment |
❌ NON | P1 | Compteur désynchronisé |
| RBAC | AssignRoleToUser (RBACService) |
❌ NON | P0 | Assignation incohérente |
| RBAC | AssignRoleToUser (RoleService) |
❌ NON | P1 | Erreurs non gérées |
| HLS | CreateJob |
✅ Transactionnel | - | Aucun |
| Auth | RotateToken |
✅ Transactionnel | - | Aucun |
| Stream Server | ||||
| SegmentTracker | persist_segment |
❌ NON | P0 | Segments orphelins |
| EncodingPool | insert_segments_from_playlist |
❌ NON | P1 | Playlist HLS incomplète |
| Processor | Job + Segments | ❌ NON | P0 | Jobs incomplets |
| Chat Server | ||||
| Channels | send_room_message |
✅ Transactionnel | - | Aucun |
| DirectMessages | send_dm_message |
✅ Transactionnel | - | Aucun |
| Persistence | process_batch |
✅ Transactionnel | - | Aucun |
6. LISTE P0 PRIORITAIRE
🔴 P0 — Must-Fix avant déploiement
-
PlaylistDuplicateService.DuplicatePlaylist(Backend Go)- Risque : Playlists dupliquées incomplètes
- Impact : Confusion utilisateur, données corrompues
- Fix : Wrapper création playlist + ajout tracks dans une transaction
-
RBACService.AssignRoleToUser(Backend Go)- Risque : Assignations de rôles incohérentes, sécurité compromise
- Impact : Permissions incorrectes, accès non autorisés
- Fix : Wrapper toutes les vérifications + INSERT dans une transaction
-
SegmentTracker.persist_segment(Stream Server)- Risque : Segments HLS orphelins, jobs avec métadonnées incorrectes
- Impact : Streaming cassé, playlists HLS incomplètes
- Fix : Utiliser transaction SQLx pour INSERT segment + UPDATE job
-
StreamProcessor(Job + Segments) (Stream Server)- Risque : Jobs de transcodage incomplets, segments partiellement créés
- Impact : Streaming cassé, jobs bloqués
- Fix : Pattern "two-phase" ou persistance batch à la fin
-
SocialService.ToggleLike/AddComment(Backend Go) — P0 si compteurs critiques- Risque : Compteurs désynchronisés
- Impact : Métriques incorrectes (si critiques pour business)
- Fix : Wrapper dans transaction
7. RECOMMANDATIONS GÉNÉRALES
Pattern Transactionnel Standard (Backend Go)
Créer un helper dans internal/database/ ou utiliser directement GORM :
// Pattern recommandé
func (s *Service) OperationMultiSteps(ctx context.Context, ...) error {
return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
// 1. Validations
// 2. Écritures multiples
// 3. Retour erreur si problème → rollback automatique
return nil
})
}
Pattern Transactionnel Standard (Rust - SQLx)
// Pattern recommandé
async fn operation_multi_steps(&self, ...) -> Result<()> {
let mut tx = self.db.begin().await?;
sqlx::query!("INSERT ...").execute(&mut *tx).await?;
sqlx::query!("UPDATE ...").execute(&mut *tx).await?;
tx.commit().await?;
Ok(())
}
Règles de Gestion d'Erreur
- Toute erreur dans la transaction → rollback automatique
- Wrapper des erreurs avec contexte :
fmt.Errorf("OperationName: %w", err) - Pas d'écritures "post-transaction" qui pourraient réintroduire de l'incohérence
- Logs structurés au niveau transaction, pas dans chaque sous-étape
8. PROCHAINES ÉTAPES
- ✅ Phase 1 : Audit — COMPLÉTÉ (ce document)
- ⏳ Phase 2 : Design — Créer
docs/DB_TRANSACTION_PLAN.mdavec plan d'implémentation - ⏳ Phase 3 : Implémentation — Corriger les P0 identifiés
- ⏳ Phase 4 : Tests — Tests ciblés pour vérifier rollback en cas d'erreur
- ⏳ Phase 5 : Documentation — Mettre à jour
TRIAGE.mdetAUDIT_STABILITY.md
Date de création : 2025-01-27
Dernière mise à jour : 2025-01-27
Statut : ✅ Audit complet — En attente feu vert pour Phase 2 (Design)