package storage import ( "context" "database/sql" "fmt" "log" "time" _ "github.com/ClickHouse/clickhouse-go/v2" ) type ClickHouseConfig struct { Host string User string Password string Database string } func OpenClickHouse(cfg ClickHouseConfig) (*sql.DB, error) { dsn := fmt.Sprintf("clickhouse://%s:%s@%s/%s", cfg.User, cfg.Password, cfg.Host, cfg.Database) db, err := sql.Open("clickhouse", dsn) if err != nil { return nil, fmt.Errorf("clickhouse open: %w", err) } db.SetMaxOpenConns(5) db.SetMaxIdleConns(2) db.SetConnMaxLifetime(10 * time.Minute) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := db.PingContext(ctx); err != nil { db.Close() return nil, fmt.Errorf("clickhouse ping: %w", err) } log.Printf("ClickHouse connected via database/sql: %s", cfg.Host) return db, nil } // OpenClickHouseWithRetry attempts to connect to ClickHouse with retries and backoff. func OpenClickHouseWithRetry( cfg ClickHouseConfig, maxRetries int, initialDelay time.Duration, ) (*sql.DB, error) { var lastErr error delay := initialDelay for i := 0; i < maxRetries; i++ { db, err := OpenClickHouse(cfg) if err == nil { return db, nil } lastErr = err log.Printf( "ClickHouse connection attempt %d/%d failed: %v, retrying in %v...", i+1, maxRetries, err, delay, ) time.Sleep(delay) delay *= 2 } return nil, fmt.Errorf( "clickhouse connection failed after %d attempts: %w", maxRetries, lastErr, ) }