222 lines
4.3 KiB
Go
222 lines
4.3 KiB
Go
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)
|
|
}
|