veza/docs/AUDIT_DB_TRANSACTIONS.md
okinrev b7955a680c P0: stabilisation backend/chat/stream + nouvelle base migrations v1
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.).
2025-12-06 11:14:38 +01:00

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

  1. Résumé Exécutif
  2. Backend Go
  3. Stream Server (Rust)
  4. Chat Server (Rust)
  5. Table Récapitulative
  6. 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

  1. Marketplace : Commandes partiellement créées (items sans order, licenses sans order)
  2. Playlists : Duplication incomplète, collaborateurs sans playlist
  3. Social : Compteurs de likes/comments désynchronisés
  4. Stream : Segments orphelins sans job, jobs sans segments
  5. RBAC : Assignations de rôles partiellement appliquées

2. BACKEND GO

2.1 Marketplace Service

CreateOrderTRANSACTIONNEL

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


CreateProductTRANSACTIONNEL

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

AddTrackTRANSACTIONNEL

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


RemoveTrackTRANSACTIONNEL

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


ReorderTracksTRANSACTIONNEL

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


DuplicatePlaylistNON TRANSACTIONNELP0

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
})

AddCollaboratorNON TRANSACTIONNELP1

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

ToggleLikeNON TRANSACTIONNELP1

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
})

AddCommentNON TRANSACTIONNELP1

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 TRANSACTIONNELP0

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 TRANSACTIONNELP1

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

CreateJobTRANSACTIONNEL

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

RotateTokenTRANSACTIONNEL

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_segmentNON TRANSACTIONNELP0

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 à jourSegments 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 TRANSACTIONNELP1

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ésPlaylist 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 PersistenceNON TRANSACTIONNELP0

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 segmentsJob orphelin
  • Si crash pendant persistance segments → Segments partiellement créésJob 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_messageTRANSACTIONNEL

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_messageTRANSACTIONNEL

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

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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

  1. Toute erreur dans la transaction → rollback automatique
  2. Wrapper des erreurs avec contexte : fmt.Errorf("OperationName: %w", err)
  3. Pas d'écritures "post-transaction" qui pourraient réintroduire de l'incohérence
  4. Logs structurés au niveau transaction, pas dans chaque sous-étape

8. PROCHAINES ÉTAPES

  1. Phase 1 : AuditCOMPLÉTÉ (ce document)
  2. Phase 2 : Design — Créer docs/DB_TRANSACTION_PLAN.md avec plan d'implémentation
  3. Phase 3 : Implémentation — Corriger les P0 identifiés
  4. Phase 4 : Tests — Tests ciblés pour vérifier rollback en cas d'erreur
  5. Phase 5 : Documentation — Mettre à jour TRIAGE.md et AUDIT_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)