package database import ( "database/sql" "fmt" "time" "veza-backend-api/internal/metrics" "gorm.io/driver/postgres" "gorm.io/gorm" ) // NewDB crée une nouvelle connexion GORM avec pool de connexions optimisé // Prend les paramètres de connexion individuels pour plus de flexibilité func NewDB(host string, port int, user, password, dbname string) (*gorm.DB, error) { dsn := fmt.Sprintf( "host=%s user=%s password=%s dbname=%s port=%d sslmode=disable", host, user, password, dbname, port, ) db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { return nil, fmt.Errorf("failed to open database: %w", err) } sqlDB, err := db.DB() if err != nil { return nil, fmt.Errorf("failed to get underlying sql.DB: %w", err) } // Configuration optimale du pool de connexions // MaxOpenConns: Nombre maximum de connexions ouvertes (25 recommandé pour PostgreSQL) sqlDB.SetMaxOpenConns(25) // MaxIdleConns: Nombre maximum de connexions inactives (5 recommandé) sqlDB.SetMaxIdleConns(5) // ConnMaxLifetime: Durée maximale de vie d'une connexion (5 minutes) // Cela permet de recycler les connexions et éviter les problèmes de timeout sqlDB.SetConnMaxLifetime(5 * time.Minute) // ConnMaxIdleTime: Durée maximale d'inactivité d'une connexion avant fermeture (1 minute) sqlDB.SetConnMaxIdleTime(1 * time.Minute) // Test de la connexion if err := sqlDB.Ping(); err != nil { return nil, fmt.Errorf("failed to ping database: %w", err) } return db, nil } // NewDBFromEnvConfig crée une nouvelle connexion GORM à partir d'un EnvConfig // Cette fonction facilite l'intégration avec le package config func NewDBFromEnvConfig(host string, port int, user, password, dbname string) (*gorm.DB, error) { return NewDB(host, port, user, password, dbname) } // CloseDB ferme proprement la connexion à la base de données func CloseDB(db *gorm.DB) error { if db == nil { return nil } sqlDB, err := db.DB() if err != nil { return fmt.Errorf("failed to get underlying sql.DB: %w", err) } // Fermeture gracieuse de toutes les connexions return sqlDB.Close() } // GetPoolStats retourne les statistiques du pool de connexions // Met également à jour les métriques Prometheus (T0023) func GetPoolStats(db *gorm.DB) (sql.DBStats, error) { if db == nil { return sql.DBStats{}, fmt.Errorf("database connection is nil") } sqlDB, err := db.DB() if err != nil { return sql.DBStats{}, fmt.Errorf("failed to get underlying sql.DB: %w", err) } stats := sqlDB.Stats() // Mettre à jour les métriques Prometheus (T0023) // open: nombre total de connexions ouvertes // idle: nombre de connexions inactives (OpenConnections - InUse) // in_use: nombre de connexions en cours d'utilisation open := stats.OpenConnections idle := open - stats.InUse inUse := stats.InUse metrics.UpdateDBConnections(open, idle, inUse) return stats, nil } // MeasureQuery mesure la durée d'une requête DB et l'enregistre dans Prometheus // Cette fonction helper peut être utilisée pour wrapper les opérations DB // operation: type d'opération (SELECT, INSERT, UPDATE, DELETE, etc.) // table: nom de la table (ou "unknown" si non disponible) // fn: fonction à exécuter et mesurer func MeasureQuery(operation, table string, fn func() error) error { start := time.Now() err := fn() duration := time.Since(start) // Enregistrer la métrique indépendamment de l'erreur metrics.RecordDBQuery(operation, table, duration) return err } // IsConnectionHealthy vérifie si la connexion à la base de données est saine func IsConnectionHealthy(db *gorm.DB, timeout time.Duration) error { if db == nil { return fmt.Errorf("database connection is nil") } sqlDB, err := db.DB() if err != nil { return fmt.Errorf("failed to get underlying sql.DB: %w", err) } // Utiliser Ping avec un timeout personnalisé pingChan := make(chan error, 1) go func() { pingChan <- sqlDB.Ping() }() select { case err := <-pingChan: return err case <-time.After(timeout): return fmt.Errorf("database ping timeout after %v", timeout) } }