← Назад к вопросам

Какие знаешь способы решения проблемы, когда приложение периодически теряет соединение с БД?

2.0 Middle🔥 181 комментариев
#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Решение проблемы периодической потери соединения с БД

Периодическая потеря соединения — одна из самых частых проблем в production. Обычно это вызвано timeout-ами, network issues или неправильной конфигурацией. Вот систематические решения:

1. Проверка Connection Pool (HikariCP)

Проблема:池 исчерпана или соединения зависают

spring:
  datasource:
    hikari:
      maximum-pool-size: 20  # Макс соединений
      minimum-idle: 5  # Мин свободных
      connection-timeout: 30000  # 30 сек ждём соединение
      idle-timeout: 600000  # Закрой неиспользуемое через 10 мин
      max-lifetime: 1800000  # Макс 30 мин жизни соединения
      connection-test-query: "SELECT 1"  # Проверяй живое ли
      leak-detection-threshold: 60000  # Логируй утечки > 60 сек

Диагностика:

import com.zaxxer.hikari.HikariDataSource;

@Component
public class PoolMonitor {
    @Autowired
    private DataSource dataSource;
    
    @Scheduled(fixedDelay = 60000)  // Каждую минуту
    public void logPoolStatus() {
        HikariDataSource pool = (HikariDataSource) dataSource;
        log.info("Active connections: {}. Idle: {}. Max: {}",
            pool.getHikariPoolMXBean().getActiveConnections(),
            pool.getHikariPoolMXBean().getIdleConnections(),
            pool.getMaximumPoolSize());
    }
}

2. Test on Borrow (валидация соединений)

Проблема: БД вернула мёртвое соединение

spring:
  datasource:
    hikari:
      # Проверяй соединение перед выдачей
      connection-test-query: "SELECT 1"
      # Или
      test-on-borrow: true
      validation-query: "SELECT 1"

Java конфиг:

@Configuration
public class DataSourceConfig {
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
        config.setUsername("root");
        config.setPassword("password");
        
        // Критичные настройки
        config.setMaximumPoolSize(20);
        config.setMinimumIdle(5);
        config.setConnectionTimeout(30000);
        config.setIdleTimeout(600000);
        config.setMaxLifetime(1800000);
        
        // Проверка соединения
        config.setConnectionTestQuery("SELECT 1");
        config.setLeakDetectionThreshold(60000);
        
        return new HikariDataSource(config);
    }
}

3. Retry Logic (повтор при ошибке)

Проблема: временный network glitch теряет запрос

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;
    
    @Retryable(
        value = {SQLException.class, DataAccessException.class},
        maxAttempts = 3,
        backoff = @Backoff(delay = 1000)  // 1 сек между попытками
    )
    public User getUserById(Long id) {
        return userRepository.findById(id).orElseThrow();
    }
    
    @Recover
    public User recover(Exception e, Long id) {
        log.error("Failed to get user after 3 attempts", e);
        throw new DataAccessException("Failed to fetch user", e);
    }
}

Spring Boot конфиг:

spring:
  retry:
    enabled: true

Или вручную:

public <T> T executeWithRetry(Supplier<T> operation, int maxRetries) {
    for (int i = 0; i < maxRetries; i++) {
        try {
            return operation.get();
        } catch (Exception e) {
            if (i == maxRetries - 1) {
                throw new RuntimeException("Max retries exceeded", e);
            }
            long delay = (long) Math.pow(2, i) * 1000;  // Exponential backoff
            try {
                Thread.sleep(delay);
            } catch (InterruptedException ie) {
                Thread.currentThread().interrupt();
            }
        }
    }
    throw new RuntimeException("Unreachable");
}

4. Keep-Alive (tcpkeepalives)

Проблема: firewall/LB закрывает idle соединение

spring:
  datasource:
    hikari:
      jdbc-url: "jdbc:mysql://localhost:3306/mydb?autoReconnect=true&autoReconnectForPools=true&maxReconnects=5"

Для PostgreSQL:

spring:
  datasource:
    hikari:
      jdbc-url: "jdbc:postgresql://localhost:5432/mydb?tcpKeepAlives=true&tcpKeepIdle=30"

На уровне ОС (Linux):

sysctl -w net.ipv4.tcp_keepalives_intvl=30
sysctl -w net.ipv4.tcp_keepalives_probes=5
sysctl -w net.ipv4.tcp_keepalives_time=600

5. Connection Eviction (удаление старых соединений)

Проблема: старые соединения становятся неживыми

spring:
  datasource:
    hikari:
      idle-timeout: 600000  # Удали через 10 мин неиспользования
      max-lifetime: 1800000  # Удали через 30 мин в любом случае

6. Увеличение timeout-ов

Проблема: network задержки > timeout

spring:
  datasource:
    hikari:
      connection-timeout: 60000  # Было 30сек, теперь 60
  jpa:
    properties:
      hibernate:
        jdbc:
          fetch_size: 100
          batch_size: 50
  
# MySQL specific
mysql:
  connection:
    timeout: 30000
    read-timeout: 30000
    write-timeout: 30000

JDBC строка:

String url = "jdbc:mysql://localhost:3306/mydb?" +
    "connectTimeout=30000&" +  // Timeout подключения
    "socketTimeout=30000&" +   // Timeout чтения/записи
    "autoReconnect=true";

7. Circuit Breaker (отключи при критичных ошибках)

Проблема: приложение бесконечно пытается к мёртвой БД

import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker;

@Service
public class UserService {
    @CircuitBreaker(
        name = "userService",
        fallbackMethod = "fallbackGetUser"
    )
    public User getUserById(Long id) {
        return userRepository.findById(id).orElseThrow();
    }
    
    public User fallbackGetUser(Long id, Exception e) {
        log.error("Circuit breaker triggered", e);
        // Вернуть cached данные или бросить ошибку
        throw new ServiceUnavailableException("User service temporarily unavailable");
    }
}

Конфиг:

resilience4j:
  circuitbreaker:
    instances:
      userService:
        failure-rate-threshold: 50  # 50% ошибок → open
        slow-call-rate-threshold: 100
        wait-duration-in-open-state: 30000  # Закрыт на 30 сек
        slow-call-duration-threshold: 10000  # > 10 сек = slow
        permitted-number-of-calls-in-half-open-state: 3
        sliding-window-size: 10
        sliding-window-type: COUNT_BASED

8. Pooling при использовании ORM (Hibernate)

Проблема: Hibernate неправильно управляет сессией

spring:
  jpa:
    properties:
      hibernate:
        # Проверяй соединение перед выдачей
        connection:
          isolation: 2  # READ_COMMITTED
        generate_statistics: true  # Логируй статистику
// Явное управление сессией
@Service
public class TransactionService {
    @Transactional
    public void process() {
        // Сессия откроется в начале, закроется в конце
        userRepository.save(user);
    }
}

9. Мониторинг и Логирование

Логируй все соединения:

logging:
  level:
    com.zaxxer.hikari: DEBUG  # HikariCP логи
    org.hibernate.engine.transaction.internal.TransactionImpl: DEBUG
    org.springframework.jdbc: DEBUG

Metrics:

import io.micrometer.core.instrument.MeterRegistry;

@Component
public class DatabaseHealthCheck {
    @Autowired
    private MeterRegistry meterRegistry;
    
    @PostConstruct
    public void registerMetrics() {
        // Метрики пула
        meterRegistry.gauge("db.pool.size", 
            () -> pool.getActiveConnections());
    }
}

10. Правильный shutdown приложения

Проблема: соединения не закрыты при выключении

@Configuration
public class ShutdownConfig {
    @Bean
    public DataSource dataSource() {
        // HikariCP автоматически закроет при shutdown
        return new HikariDataSource(config);
    }
}

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

11. Network diagnostics

# Проверь соединение с БД
ping <db-host>
telnet <db-host> 3306

# MTU size (может быть причина разрывов)
ping -M do -s 1472 <db-host>

# TCP keep-alives
ss -to | grep mysql

Итоговый конфиг для production

spring:
  datasource:
    hikari:
      jdbc-url: jdbc:mysql://prod-db:3306/mydb
      username: ${DB_USER}
      password: ${DB_PASSWORD}
      
      # Pool размеры
      maximum-pool-size: 20
      minimum-idle: 5
      
      # Timeout-ы
      connection-timeout: 60000
      idle-timeout: 600000
      max-lifetime: 1800000
      
      # Здоровье
      connection-test-query: "SELECT 1"
      leak-detection-threshold: 60000
      
      # Reconnect
      jdbc-url: "jdbc:mysql://prod-db:3306/mydb?autoReconnect=true&maxReconnects=3&tcpKeepAlives=true"
  
  jpa:
    properties:
      hibernate:
        generate_statistics: true
  
resilience4j:
  circuitbreaker:
    instances:
      database:
        failure-rate-threshold: 50
        wait-duration-in-open-state: 30000
        permitted-number-of-calls-in-half-open-state: 3

logging:
  level:
    com.zaxxer.hikari: INFO

Чеклист diagnostics

  • ✓ HikariCP метрики (active/idle connections)
  • ✓ Логи с WARN и ERROR уровня
  • ✓ Проверь timeout-ы (connection, socket, query)
  • ✓ Проверь pool size соответствует нагрузке
  • ✓ tcpKeepAlives включены
  • ✓ Connection test query настроена
  • ✓ Retry logic для транзиентных ошибок
  • ✓ Circuit breaker при критичных сбоях
  • ✓ Graceful shutdown реализован
  • ✓ Firewall/Load Balancer не режет idle соединения

Итог

Потеря соединения обычно вызвана одной из пяти причин:

  1. Pool исчерпана или соединения зависают
  2. Firewall закрывает idle соединения
  3. Timeout-ы слишком короткие
  4. БД перезагружается/переполнена
  5. Network issues

Диагностируй через логи HikariCP и метрики, применяй решения в зависимости от причины.