package db import ( "context" "database/sql" "fmt" "nearle/utils" "os" "time" "github.com/redis/go-redis/v9" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) var ( DB *gorm.DB ) // -------------------- // DATABASE CONNECTION // -------------------- func Connect() { dsn := fmt.Sprintf( "host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=Asia/Kolkata", mustEnv("DB_HOST"), mustEnv("DB_USER"), mustEnv("DB_PASSWORD"), mustEnv("DB_NAME"), getEnv("DB_PORT", "5432"), ) var err error maxRetries := 10 backoff := 2 * time.Second for i := 1; i <= maxRetries; i++ { utils.Logger.Infow("Connecting to database", "attempt", i) DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Error), }) if err == nil { sqlDB, dbErr := DB.DB() if dbErr == nil { // 🔥 Ping with timeout (important) ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if pingErr := sqlDB.PingContext(ctx); pingErr == nil { setupDB(DB) utils.Logger.Infow("✅ Database connected successfully") // Start background health check startDBHealthCheck(sqlDB) return } else { err = pingErr } } else { err = dbErr } } utils.Logger.Errorw("❌ DB connection failed", "attempt", i, "error", err) time.Sleep(backoff) backoff *= 2 } utils.Logger.Fatalw("☢️ CRITICAL: DB connection failed after retries") } // -------------------- // CONNECTION POOLING // -------------------- func setupDB(database *gorm.DB) { sqlDB, err := database.DB() if err != nil { utils.Logger.Errorw("Failed to get sql.DB", "error", err) return } // 🔥 K8s SAFE CONFIG sqlDB.SetMaxOpenConns(30) // total open connections sqlDB.SetMaxIdleConns(5) // idle connections sqlDB.SetConnMaxLifetime(5 * time.Minute) // avoid stale connections sqlDB.SetConnMaxIdleTime(2 * time.Minute) stats := sqlDB.Stats() utils.Logger.Infow("DB Pool configured", "MaxOpen", stats.MaxOpenConnections, ) } // -------------------- // DB HEALTH CHECK // -------------------- func startDBHealthCheck(sqlDB *sql.DB) { go func() { for { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) err := sqlDB.PingContext(ctx) cancel() if err != nil { utils.Logger.Errorw("❌ DB connection lost", "error", err) } time.Sleep(30 * time.Second) } }() } // -------------------- // CLOSE DB // -------------------- func CloseDB() { if DB == nil { return } sqlDB, err := DB.DB() if err != nil { utils.Logger.Errorw("Error retrieving sql.DB", "error", err) return } utils.Logger.Infow("Closing DB connection") sqlDB.Close() } // -------------------- // ENV HELPERS // -------------------- func mustEnv(key string) string { val := os.Getenv(key) if val == "" { utils.Logger.Warnw("Missing env variable", "key", key) } return val } func getEnv(key, fallback string) string { if val := os.Getenv(key); val != "" { return val } return fallback } // -------------------- // REDIS CONNECTION // -------------------- var Rdb *redis.Client var Ctx = context.Background() func InitRedis() { redisHost := getEnv("REDIS_HOST", "66.116.226.255") // ✅ FIXED IP redisPort := getEnv("REDIS_PORT", "6379") addr := fmt.Sprintf("%s:%s", redisHost, redisPort) Rdb = redis.NewClient(&redis.Options{ Addr: addr, Username: "default", Password: "Package@324969#", DB: 0, // ✅ TIMEOUTS (VERY IMPORTANT) DialTimeout: 10 * time.Second, ReadTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second, // ✅ POOL PoolSize: 50, MinIdleConns: 10, // ✅ RETRIES MaxRetries: 3, MinRetryBackoff: 500 * time.Millisecond, MaxRetryBackoff: 2 * time.Second, }) maxRetries := 5 backoff := 1 * time.Second for i := 1; i <= maxRetries; i++ { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) _, err := Rdb.Ping(ctx).Result() cancel() if err == nil { utils.Logger.Infow("✅ Redis connected successfully", "addr", addr) return } utils.Logger.Errorw("❌ Redis connection failed", "attempt", i, "addr", addr, "error", err, ) time.Sleep(backoff) backoff *= 2 } utils.Logger.Fatalw("☢️ CRITICAL: Redis connection failed after retries", "addr", addr) }