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

Что будешь делать при задержках при низкой нагрузке в базе данных

2.4 Senior🔥 141 комментариев
#Базы данных и SQL

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

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

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

Что делать при задержках при низкой нагрузке в базе данных

Отличный практический вопрос. Когда задержки происходят при низкой нагрузке на БД, это указывает на проблему, которая не в самой базе данных, а скорее всего в приложении или сетевой конфигурации. Давайте рассмотрим диагностический подход.

Шаг 1: Оценить масштаб проблемы

Собрать данные о задержках:

public class LatencyDiagnostics {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final MeterRegistry meterRegistry;
    
    public User getUserWithMetrics(Long userId) {
        long startTime = System.nanoTime();
        
        try {
            User user = userRepository.findById(userId).orElse(null);
            long duration = System.nanoTime() - startTime;
            
            // Логирование медленных запросов
            if (duration > 1_000_000_000) {  // > 1 сек
                logger.warn("Slow query: findById took {}ms", duration / 1_000_000);
            }
            
            // Метрика
            meterRegistry.timer("db.query.latency").record(duration, TimeUnit.NANOSECONDS);
            
            return user;
        } catch (Exception e) {
            logger.error("Database error", e);
            throw e;
        }
    }
}

Вопросы для себя:

  1. Какая именно задержка? (50ms? 500ms? 5s?)
  2. Все запросы медленные или только некоторые?
  3. Задержка при первом запросе или при последующих?
  4. Интернет приемлемая ли скорость?

Шаг 2: Проверить Connection Pool

Проблема может быть в pooling конфигурации:

@Configuration
public class ConnectionPoolDiagnostics {
    @Bean
    public DataSource dataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:postgresql://db:5432/mydb");
        config.setUsername("user");
        config.setPassword("password");
        
        // ВАЖНЫЕ ПАРАМЕТРЫ
        config.setMaximumPoolSize(20);        // Размер pool
        config.setMinimumIdle(5);             // Минимум idle connections
        config.setConnectionTimeout(30000);   // 30 сек timeout
        config.setIdleTimeout(600000);        // 10 мин before closing
        config.setMaxLifetime(1800000);       // 30 мин max lifetime
        
        // Логирование pool статистики
        config.setLeakDetectionThreshold(60000);  // Alert если connection не возвращен за 1 мин
        
        return new HikariDataSource(config);
    }
}

Логирование health pool:

@Component
public class ConnectionPoolMonitor {
    private final DataSource dataSource;
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Scheduled(fixedDelay = 10000)  // Каждые 10 секунд
    public void checkPoolHealth() {
        HikariDataSource hikariDataSource = (HikariDataSource) dataSource;
        
        int active = hikariDataSource.getHikariPoolMXBean().getActiveConnections();
        int idle = hikariDataSource.getHikariPoolMXBean().getIdleConnections();
        int total = hikariDataSource.getHikariPoolMXBean().getTotalConnections();
        int pending = hikariDataSource.getHikariPoolMXBean().getPendingThreads();
        
        logger.info(
            "Connection Pool - Active: {}, Idle: {}, Total: {}, Pending: {}",
            active, idle, total, pending
        );
        
        // Если много pending threads — pool истощается
        if (pending > 5) {
            logger.warn("WARNING: {} threads waiting for connection!", pending);
        }
    }
}

Шаг 3: Проверить Network Latency

Задержка может быть в сетевом соединении:

public class NetworkLatencyCheck {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    public void checkDatabaseLatency() {
        // Простой ping запрос
        long startTime = System.currentTimeMillis();
        
        try {
            // SELECT 1 — самый быстрый запрос
            template.queryForObject(
                "SELECT 1",
                Integer.class
            );
            
            long latency = System.currentTimeMillis() - startTime;
            logger.info("Database ping latency: {}ms", latency);
            
            // Если > 100ms при низкой нагрузке — проблема в сети
            if (latency > 100) {
                logger.warn("Network latency is high: {}ms", latency);
                // Проверить:
                // 1. Geolocation DB vs Application
                // 2. Network bandwidth
                // 3. DNS resolution time
            }
        } catch (Exception e) {
            logger.error("Failed to check database latency", e);
        }
    }
}

Команды для диагностики (Linux):

# Проверить ping
ping -c 5 database.example.com

# Проверить TCP connection time
time nc -zv database.example.com 5432

# Проверить DNS resolution
time nslookup database.example.com

# Проверить network bandwidth
iperf -c database-host

Шаг 4: Проверить Query Performance

Даже при низкой нагрузке запрос может быть медленным:

@Service
public class QueryPerformanceAnalysis {
    private final JdbcTemplate jdbcTemplate;
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    // Медленный запрос даже при низкой нагрузке
    @Query("SELECT u FROM User u WHERE u.email LIKE :email")
    public List<User> findByEmailLike(String email) {
        // PROBLEM: LIKE с wildcard без индекса
        // Даже 1000 записей может быть медленным
        return null;
    }
    
    // Правильный способ
    @Query("SELECT u FROM User u WHERE u.email = :email")
    public Optional<User> findByEmail(String email) {
        // Быстро даже с миллионами записей (есть индекс)
        return null;
    }
}

Включить query logging:

# application.properties
# Логирование SQL запросов
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

# Логирование времени выполнения
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

# PostgreSQL slow query log
log_min_duration_statement=1000  # Log queries > 1 sec

Анализ плана запроса:

public class ExplainPlan {
    public void analyzeQueryPerformance() {
        // Выполнить EXPLAIN ANALYZE в PostgreSQL
        String sql = "EXPLAIN ANALYZE SELECT * FROM users WHERE id = 1";
        
        /*
         Seq Scan on users  (cost=0.00..35.50 rows=1 width=..)
           Filter: (id = 1)
           Planning Time: 0.234 ms
           Execution Time: 0.532 ms
         
         Если видим Seq Scan вместо Index Scan — нужен индекс!
         Если Execution Time высокое — проблема в самом запросе
        */
    }
}

Шаг 5: Проверить Application Bottlenecks

Медленность может быть в приложении, а не в БД:

@Service
public class ApplicationBottleneck {
    private final UserRepository userRepository;
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    // ПРОБЛЕМА 1: N+1 Query Problem
    public List<UserDTO> getUsersWithOrdersWrong() {
        List<User> users = userRepository.findAll();  // 1 query
        
        for (User user : users) {
            user.getOrders();  // N queries! (по одному за каждого пользователя)
        }
        
        return users.stream().map(this::toDTO).collect(toList());
    }
    
    // РЕШЕНИЕ: Eager Loading
    @Query("SELECT u FROM User u LEFT JOIN FETCH u.orders")
    public List<User> getUsersWithOrdersRight() {
        // 1 query с JOIN
        return userRepository.getUsersWithOrdersRight();
    }
    
    // ПРОБЛЕМА 2: Лишние поля в SELECT
    public List<User> getAllUserFieldsWrong() {
        return userRepository.findAll();  // SELECT * — все 50 полей
    }
    
    // РЕШЕНИЕ: SELECT только нужные поля
    @Query("SELECT new UserDTO(u.id, u.name, u.email) FROM User u")
    public List<UserDTO> getAllUserFieldsRight() {
        return userRepository.getAllUserFieldsRight();
    }
}

Профилирование приложения:

@Component
public class PerformanceProfiler {
    @Around("@annotation(Profiled)")
    public Object profile(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        
        try {
            return joinPoint.proceed();
        } finally {
            long duration = System.currentTimeMillis() - startTime;
            String methodName = joinPoint.getSignature().getName();
            
            if (duration > 100) {  // Log если > 100ms
                logger.warn("Slow method: {} took {}ms",
                    methodName, duration);
            }
        }
    }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Profiled {}

Шаг 6: Проверить Caching

Добавить кэширование для часто запрашиваемых данных:

@Service
@EnableCaching
public class UserServiceWithCache {
    private final UserRepository userRepository;
    
    // Кэшировать результат на 1 час
    @Cacheable(
        value = "users",
        key = "#id",
        unless = "#result == null"
    )
    public User getUserById(Long id) {
        return userRepository.findById(id).orElse(null);
    }
    
    // Инвалидировать кэш при обновлении
    @CacheEvict(value = "users", key = "#user.id")
    public void updateUser(User user) {
        userRepository.save(user);
    }
}

Комплексный подход к диагностике

Здравый смысл в решении проблем:

public class DiagnosticChecklist {
    
    // 1. Базовые метрики
    public void checkBasics() {
        // - CPU usage на сервере БД
        // - Memory usage
        // - Disk I/O
        // - Network bandwidth
    }
    
    // 2. Database Level
    public void checkDatabase() {
        // - EXPLAIN ANALYZE на медленном запросе
        // - Индексы (есть ли нужные?)
        // - Query execution time (без приложения)
        // - Connection count
    }
    
    // 3. Application Level
    public void checkApplication() {
        // - N+1 queries
        // - Memory leaks
        // - Thread contention
        // - Garbage collection pauses
    }
    
    // 4. Network Level
    public void checkNetwork() {
        // - Latency (ping)
        // - Bandwidth
        // - DNS resolution time
        // - Connection establishment time
    }
}

Инструменты для диагностики

// 1. Spring Boot Actuator для метрик
// http://localhost:8080/actuator/metrics

// 2. JProfiler / YourKit для профилирования

// 3. DataGrip — IDE для работы с БД
// Встроенный профайлер запросов

// 4. pgAdmin для PostgreSQL
// Встроенный инструмент для анализа запросов

// 5. New Relic / Datadog для production мониторинга

Заключение

При задержках при низкой нагрузке на БД:

  1. Проверить connection pool — может быть истощен
  2. Проверить network latency — может быть проблема в сети
  3. Проверить query performance — даже один медленный запрос
  4. Проверить N+1 queries — частая причина
  5. Добавить кэширование — для часто запрашиваемых данных
  6. Профилировать — найти точное место узкого места

Самое главное: собирать данные и анализировать, а не гадать. Метрики, логи и профилирование — это ключи к решению таких задач.