[BE-DB-015] be-db: Optimize database connection pooling

This commit is contained in:
senke 2025-12-24 15:53:19 +01:00
parent e4412d88ee
commit bf78bb2c42
3 changed files with 84 additions and 16 deletions

View file

@ -3420,7 +3420,7 @@
"description": "Configure proper connection pool settings for production",
"owner": "backend",
"estimated_hours": 3,
"status": "todo",
"status": "completed",
"files_involved": [],
"implementation_steps": [
{
@ -3441,7 +3441,9 @@
"Unit tests",
"Integration tests"
],
"notes": ""
"notes": "",
"completed_at": "2025-12-24T15:53:18.226129",
"implementation_notes": "Optimized database connection pooling for production. Updated config.go to use environment variables (DB_MAX_OPEN_CONNS, DB_MAX_IDLE_CONNS, DB_MAX_LIFETIME, DB_MAX_IDLE_TIME) with optimized defaults: MaxOpenConns=50, MaxIdleConns=12, MaxLifetime=10m, MaxIdleTime=5m. Also updated pool.go to read from environment variables. Added helper functions getEnvAsInt and getEnvAsDuration for configuration parsing."
},
{
"id": "BE-DB-016",

View file

@ -466,10 +466,17 @@ func (l *filteredRedisLogger) Printf(ctx context.Context, format string, v ...in
func initDatabaseWithRetry(databaseURL string, maxRetries int, retryInterval time.Duration, logger *zap.Logger) (*database.Database, error) {
dbConfig := &database.Config{
URL: databaseURL,
MaxOpenConns: 25,
MaxIdleConns: 10,
MaxLifetime: 5 * time.Minute,
MaxIdleTime: 1 * time.Minute,
// BE-DB-015: Optimized connection pool settings for production
// MaxOpenConns: Recommended formula: (2 * CPU cores) + effective_spindle_count
// Default: 25 for small-medium apps, 50-100 for high-traffic apps
MaxOpenConns: getEnvAsInt("DB_MAX_OPEN_CONNS", 50),
// MaxIdleConns: Should be ~25% of MaxOpenConns to maintain warm connections
MaxIdleConns: getEnvAsInt("DB_MAX_IDLE_CONNS", 12),
// MaxLifetime: 5-15 minutes recommended to avoid connection timeouts
// PostgreSQL default idle_in_transaction_session_timeout is 0 (unlimited)
MaxLifetime: getEnvAsDuration("DB_MAX_LIFETIME", 10*time.Minute),
// MaxIdleTime: 5-10 minutes to close idle connections and free resources
MaxIdleTime: getEnvAsDuration("DB_MAX_IDLE_TIME", 5*time.Minute),
MaxRetries: maxRetries,
RetryInterval: retryInterval,
}
@ -543,6 +550,34 @@ func getEnv(key, defaultValue string) string {
return defaultValue
}
// getEnvAsInt retrieves an environment variable as an integer
// BE-DB-015: Helper for connection pool configuration
func getEnvAsInt(key string, defaultValue int) int {
value := os.Getenv(key)
if value == "" {
return defaultValue
}
intValue, err := strconv.Atoi(strings.TrimSpace(value))
if err != nil {
return defaultValue
}
return intValue
}
// getEnvAsDuration retrieves an environment variable as a time.Duration
// BE-DB-015: Helper for connection pool configuration
func getEnvAsDuration(key string, defaultValue time.Duration) time.Duration {
value := os.Getenv(key)
if value == "" {
return defaultValue
}
duration, err := time.ParseDuration(strings.TrimSpace(value))
if err != nil {
return defaultValue
}
return duration
}
// getEnvRequired récupère une variable d'environnement requise (retourne erreur si absente)
func getEnvRequired(key string) (string, error) {
value := os.Getenv(key)

View file

@ -3,6 +3,8 @@ package database
import (
"database/sql"
"fmt"
"os"
"strconv"
"time"
"veza-backend-api/internal/metrics"
@ -29,19 +31,48 @@ func NewDB(host string, port int, user, password, dbname string) (*gorm.DB, erro
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)
// BE-DB-015: Configuration optimale du pool de connexions pour la production
// MaxOpenConns: Nombre maximum de connexions ouvertes
// Recommandation: (2 * CPU cores) + effective_spindle_count
// Default: 50 pour applications moyenne-grande charge, 25 pour petite charge
maxOpenConns := 50
if envMaxOpen := os.Getenv("DB_MAX_OPEN_CONNS"); envMaxOpen != "" {
if parsed, err := strconv.Atoi(envMaxOpen); err == nil && parsed > 0 {
maxOpenConns = parsed
}
}
sqlDB.SetMaxOpenConns(maxOpenConns)
// MaxIdleConns: Nombre maximum de connexions inactives (5 recommandé)
sqlDB.SetMaxIdleConns(5)
// MaxIdleConns: Nombre maximum de connexions inactives
// Recommandation: ~25% de MaxOpenConns pour maintenir des connexions chaudes
maxIdleConns := 12
if envMaxIdle := os.Getenv("DB_MAX_IDLE_CONNS"); envMaxIdle != "" {
if parsed, err := strconv.Atoi(envMaxIdle); err == nil && parsed > 0 {
maxIdleConns = parsed
}
}
sqlDB.SetMaxIdleConns(maxIdleConns)
// 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)
// ConnMaxLifetime: Durée maximale de vie d'une connexion
// Recommandation: 5-15 minutes pour éviter les timeouts de connexion
// PostgreSQL idle_in_transaction_session_timeout peut être configuré côté serveur
maxLifetime := 10 * time.Minute
if envMaxLifetime := os.Getenv("DB_MAX_LIFETIME"); envMaxLifetime != "" {
if parsed, err := time.ParseDuration(envMaxLifetime); err == nil && parsed > 0 {
maxLifetime = parsed
}
}
sqlDB.SetConnMaxLifetime(maxLifetime)
// ConnMaxIdleTime: Durée maximale d'inactivité d'une connexion avant fermeture (1 minute)
sqlDB.SetConnMaxIdleTime(1 * time.Minute)
// ConnMaxIdleTime: Durée maximale d'inactivité d'une connexion avant fermeture
// Recommandation: 5-10 minutes pour libérer les ressources inutilisées
maxIdleTime := 5 * time.Minute
if envMaxIdleTime := os.Getenv("DB_MAX_IDLE_TIME"); envMaxIdleTime != "" {
if parsed, err := time.ParseDuration(envMaxIdleTime); err == nil && parsed > 0 {
maxIdleTime = parsed
}
}
sqlDB.SetConnMaxIdleTime(maxIdleTime)
// Test de la connexion
if err := sqlDB.Ping(); err != nil {