Как отследить ошибки соединения с БД?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Отслеживание ошибок соединения с БД в Go
Отслеживание ошибок соединения с базой данных — критически важная задача для обеспечения стабильности приложения. В Go существует несколько подходов, которые можно комбинировать для создания надежной системы мониторинга.
Основные методы отслеживания
1. Непосредственная обработка ошибок при операциях
При каждом вызове метода, взаимодействующего с БД, нужно проверять возвращаемую ошибку:
func getUserByID(db *sql.DB, id int) (*User, error) {
var user User
err := db.QueryRow("SELECT id, name FROM users WHERE id = $1", id).Scan(&user.ID, &user.Name)
if err != nil {
// Определяем тип ошибки
if errors.Is(err, sql.ErrNoRows) {
return nil, fmt.Errorf("user not found: %w", err)
}
// Проверяем ошибки соединения
if isConnectionError(err) {
log.Printf("Database connection error: %v", err)
metrics.Increment("db_connection_errors")
}
return nil, fmt.Errorf("query failed: %w", err)
}
return &user, nil
}
func isConnectionError(err error) bool {
// Проверяем различные типы ошибок соединения
errStr := err.Error()
return strings.Contains(errStr, "connection refused") ||
strings.Contains(errStr, "network unreachable") ||
strings.Contains(errStr, "timeout") ||
strings.Contains(errStr, "reset by peer")
}
2. Использование Health Checks
Реализация периодических проверок здоровья соединения:
func checkDBHealth(db *sql.DB) bool {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Простой запрос для проверки соединения
err := db.PingContext(ctx)
if err != nil {
log.Printf("Database health check failed: %v", err)
return false
}
// Проверка более сложных сценариев
var result int
err = db.QueryRowContext(ctx, "SELECT 1").Scan(&result)
return err == nil && result == ap
}
3. Настройка мониторинга через sql.DB
Объект sql.DB предоставляет встроенные метрики:
func setupDBMonitoring(db *sql.DB) {
// Статистика пула соединений
stats := db.Stats()
log.Printf("DB Stats - OpenConnections: %d, InUse: %d, Idle: %d",
stats.OpenConnections, stats.InUse, stats.Idle)
// Мониторим ключевые метрики
if stats.OpenConnections == 0 {
log.Warn("No database connections available")
}
if stats.WaitCount > 0 {
log.Warn("Clients waiting for database connections")
}
}
Расширенное отслеживание с контекстом
4. Интеграция с трейсингом и контекстом
func queryWithContext(ctx context.Context, db *sql.DB) error {
// Добавляем таймаут для предотвращения зависаний
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
// Используем контекст в запросе
rows, err := db.QueryContext(ctx, "SELECT * FROM large_table")
if err != nil {
// Проверяем, была ли ошибка из-за таймаута контекста
if ctx.Err() == context.DeadlineExceeded {
metrics.Increment("db_query_timeouts")
return fmt.Errorf("query timeout: %w", err)
}
return fmt.Errorf("query failed: %w", err)
}
defer rows.Close()
return nil
}
Архитектурные подходы
5. Использование Circuit Breaker
Реализация паттерна Circuit Breaker для защиты БД:
type DBCircuitBreaker struct {
mu sync.RWMutex
failures int
threshold int
cooldown time.Duration
lastFailure time.Time
isOpen bool
}
func (cb *DBCircuitBreaker) Execute(db *sql.DB, query string) error {
cb.mu.RLock()
if cb.isOpen && time.Since(cb.lastFailure) < cb.cooldown {
cb.mu.RUnlock()
return fmt.Errorf("circuit breaker is open")
}
cb.mu.RUnlock()
err := db.QueryRow(query).Scan()
cb.mu.Lock()
defer cb.mu.Unlock()
if err != nil {
cb.failures++
cb.lastFailure = time.Now()
if cb.failures >= cb.threshold {
cb.isOpen = true
log.Printf("Circuit breaker opened after %d failures", cb.failures)
}
} else {
cb.failures = 0
if cb.isOpen {
cb.isOpen = false
log.Println("Circuit breaker closed")
}
}
return err
}
6. Интеграция с системами мониторинга
func setupDBMetrics(db *sql.DB, registry *prometheus.Registry) {
// Метрики для Prometheus
dbErrors := prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "db_errors_total",
Help: "Total database errors",
},
[]string{"error_type"},
)
queryDuration := prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Name: "db_query_duration_seconds",
Help: "Database query duration",
Buckets: prometheus.DefBuckets,
},
[]string{"query_type"},
)
registry.MustRegister(dbErrors, queryDuration)
// Обертка для отслеживания запросов
wrappedDB := &monitoredDB{
db: db,
errors: dbErrors,
duration: queryDuration,
}
}
Практические рекомендации
- Логирование с достаточным контекстом — всегда включайте информацию о запросе, параметрах и времени возникновения ошибки
- Многоуровневое отслеживание — комбинируйте health checks, метрики и алертинг
- Автоматическое восстановление — реализуйте retry логику с экспоненциальной задержкой
- Зависимость от типа БД — учитывайте специфичные для вашей СУБД коды ошибок
- Тестирование сценариев — имитируйте сетевые проблемы и таймауты в тестах
Пример комплексного решения
type DBMonitor struct {
db *sql.DB
logger *zap.Logger
metrics MetricsCollector
breaker *CircuitBreaker
healthTicker *time.Ticker
}
func NewDBMonitor(dsn string) (*DBMonitor, error) {
db, err := sql.Open("postgres", dsn)
if err != nil {
return nil, fmt.Errorf("open db: %w", err)
}
// Настройка пула соединений
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)
monitor := &DBMonitor{
db: db,
logger: zap.NewExample(),
metrics: NewPrometheusMetrics(),
breaker: NewCircuitBreaker(5, 30*time.Second),
}
// Запуск периодических проверок
monitor.startHealthChecks(30 * time.Second)
return monitor, nil
}
func (m *DBMonitor) startHealthChecks(interval time.Duration) {
m.healthTicker = time.NewTicker(interval)
go func() {
for range m.healthTicker.C {
if !m.checkHealth() {
m.logger.Error("Database health check failed")
m.metrics.IncHealthCheckFailure()
// Попытка восстановления
m.reconnect()
}
}
}()
}
Эффективное отслеживание ошибок соединения с БД требует многоуровневого подхода, сочетающего непосредственную обработку ошибок, health checks, метрики и архитектурные паттерны. Это обеспечивает не только обнаружение проблем, но и возможности для автоматического восстановления и оперативного реагирования.