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

Как определить быстроту запроса?

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

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

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

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

Как определить быстроту запроса

Оценка производительности запросов — критический навык для оптимизации приложения. Существует несколько подходов от простых до продвинутых.

1. Базовое измерение времени выполнения

// Простой способ: System.currentTimeMillis()
public List<User> findUsersSlow() {
    long startTime = System.currentTimeMillis();
    
    List<User> users = userRepository.findAll();
    
    long duration = System.currentTimeMillis() - startTime;
    log.info("Query took: {}ms", duration);
    return users;
}

// Лучше: System.nanoTime() для более точного измерения
public List<User> findUsersFast() {
    long startTime = System.nanoTime();
    
    List<User> users = userRepository.findAll();
    
    long durationNanos = System.nanoTime() - startTime;
    long durationMillis = durationNanos / 1_000_000;
    log.info("Query took: {}μs ({}ms)", durationNanos / 1_000, durationMillis);
    return users;
}

2. Утилита для удобного измерения

public class QueryTimer {
    
    private final long startTime = System.nanoTime();
    private final String queryName;
    
    public QueryTimer(String queryName) {
        this.queryName = queryName;
    }
    
    public void logTime() {
        long durationNanos = System.nanoTime() - startTime;
        long millis = durationNanos / 1_000_000;
        long micros = (durationNanos % 1_000_000) / 1_000;
        
        if (millis > 0) {
            log.info("[{}] {}ms", queryName, millis);
        } else {
            log.info("[{}] {}μs", queryName, micros);
        }
    }
    
    public static void measure(String name, Runnable task) {
        QueryTimer timer = new QueryTimer(name);
        task.run();
        timer.logTime();
    }
}

// Использование
QueryTimer.measure("Find all users", () -> {
    userRepository.findAll();
});

3. Аспект для автоматического логирования

@Component
@Aspect
public class QueryPerformanceAspect {
    
    private static final long SLOW_QUERY_THRESHOLD = 500; // ms
    private static final Logger log = LoggerFactory.getLogger(QueryPerformanceAspect.class);
    
    @Around("execution(* com.example.repository..*.*(..))")
    public Object logQueryPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.nanoTime();
        String methodName = joinPoint.getSignature().getName();
        String className = joinPoint.getTarget().getClass().getSimpleName();
        
        try {
            Object result = joinPoint.proceed();
            return result;
        } finally {
            long durationNanos = System.nanoTime() - startTime;
            long durationMs = durationNanos / 1_000_000;
            
            if (durationMs > SLOW_QUERY_THRESHOLD) {
                log.warn("SLOW QUERY: {}.{} took {}ms", 
                    className, methodName, durationMs);
            } else {
                log.debug("{}.{} took {}ms", 
                    className, methodName, durationMs);
            }
        }
    }
}

4. Бенчмарки с JMH (Java Microbenchmark Harness)

// Зависимость в pom.xml
// <dependency>
//     <groupId>org.openjdk.jmh</groupId>
//     <artifactId>jmh-core</artifactId>
//     <version>1.36</version>
// </dependency>

import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;

@Fork(value = 1, warmups = 0)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 5)
@Measurement(iterations = 5, time = 10)
public class QueryPerformanceBenchmark {
    
    private UserRepository userRepository;
    
    @Setup
    public void setup() {
        // Инициализация репозитория
    }
    
    @Benchmark
    public List<User> findAllUsers() {
        return userRepository.findAll();
    }
    
    @Benchmark
    public List<User> findActiveUsers() {
        return userRepository.findByStatus("ACTIVE");
    }
    
    @Benchmark
    public User findUserById() {
        return userRepository.findById(UUID.randomUUID()).orElse(null);
    }
}

// Запуск: java -jar jmh-generated.jar
// Результат:
// Benchmark                                   Mode  Cnt   Score   Error  Units
// QueryPerformanceBenchmark.findAllUsers      avgt    5 150.234 ± 5.123  ms/op
// QueryPerformanceBenchmark.findActiveUsers   avgt    5  45.891 ± 2.145  ms/op
// QueryPerformanceBenchmark.findUserById      avgt    5   1.234 ± 0.089  ms/op

5. Профилирование с Micrometer

@Configuration
public class MetricsConfig {
    
    @Bean
    public MeterRegistry meterRegistry() {
        return new SimpleMeterRegistry();
    }
}

@Service
public class MonitoredUserService {
    
    private final UserRepository userRepository;
    private final Timer findUsersTimer;
    
    public MonitoredUserService(UserRepository userRepository, 
                               MeterRegistry registry) {
        this.userRepository = userRepository;
        this.findUsersTimer = Timer.builder("query.users.find")
            .description("Time to find all users")
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(registry);
    }
    
    public List<User> findAllUsers() {
        return findUsersTimer.recordCallable(() -> {
            return userRepository.findAll();
        });
    }
    
    // Или через @Timed
    @Timed(value = "query.orders.find", description = "Find orders")
    public List<Order> findOrders() {
        return orderRepository.findAll();
    }
}

// Метрики доступны через Prometheus
// GET /actuator/metrics/query.users.find
// {
//   "name": "query.users.find",
//   "measurements": [
//     {"statistic": "COUNT", "value": 1000},
//     {"statistic": "TOTAL_TIME", "value": 150000},
//     {"statistic": "MEAN", "value": 150}
//   ]
// }

6. Анализ Hibernate статистики

@Service
public class HibernateQueryAnalysis {
    
    @PersistenceUnit
    private EntityManagerFactory emf;
    
    @Transactional(readOnly = true)
    public void analyzeQueryStats() {
        SessionFactory sf = emf.unwrap(SessionFactory.class);
        Statistics stats = sf.getStatistics();
        stats.clear();
        
        // Выполни запрос
        List<User> users = userRepository.findAll();
        
        // Анализируй результаты
        log.info("=== Hibernrate Statistics ===");
        log.info("Entity load count: {}", stats.getEntityLoadCount());
        log.info("Entity fetch count: {}", stats.getEntityFetchCount());
        log.info("Query execution count: {}", stats.getQueryExecutionCount());
        log.info("Query execution avg time: {}", stats.getQueryExecutionAvgTime());
        log.info("Cache hit count: {}", stats.getCacheHitCount());
        log.info("Cache miss count: {}", stats.getCacheMissCount());
        
        // Детали по каждому запросу
        for (String query : stats.getQueries()) {
            log.info("Query: {}", query);
            log.info("  Execution count: {}", stats.getQueryExecutionCount(query));
            log.info("  Avg time: {}ms", stats.getQueryExecutionAvgTime(query));
        }
    }
}

7. JDBC логирование

# application.yml
spring:
  jpa:
    show-sql: true
    properties:
      hibernate:
        format_sql: true
        use_sql_comments: true

logging:
  level:
    org.hibernate.SQL: DEBUG
    org.hibernate.type.descriptor.sql.BasicBinder: TRACE
    org.springframework.jdbc.core: DEBUG

# Для более детального логирования времени
logging:
  pattern:
    console: "%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"

8. Query Profiler как Spring Bean

@Component
public class QueryProfiler {
    
    private static final ThreadLocal<Map<String, Long>> queryTimes = ThreadLocal.withInitial(HashMap::new);
    
    public void startQuery(String queryName) {
        queryTimes.get().put(queryName, System.nanoTime());
    }
    
    public long endQuery(String queryName) {
        long startTime = queryTimes.get().remove(queryName);
        long durationNanos = System.nanoTime() - startTime;
        return durationNanos / 1_000_000; // convert to millis
    }
    
    public void clear() {
        queryTimes.remove();
    }
}

@Service
public class ProfiledUserService {
    
    private final QueryProfiler profiler;
    
    public List<User> findUsers() {
        profiler.startQuery("findAll");
        try {
            return userRepository.findAll();
        } finally {
            long duration = profiler.endQuery("findAll");
            log.info("findAll took {}ms", duration);
        }
    }
}

9. Сравнение запросов (бенчмарк)

@Service
public class QueryComparisonBenchmark {
    
    private final UserRepository userRepository;
    
    public void compareQueryPerformance() {
        // Запрос 1: БЕЗ индекса
        long start1 = System.nanoTime();
        List<User> users1 = userRepository.findByEmail("test@example.com");
        long time1 = (System.nanoTime() - start1) / 1_000_000;
        
        // Запрос 2: С индексом
        long start2 = System.nanoTime();
        List<User> users2 = userRepository.findByEmailIndexed("test@example.com");
        long time2 = (System.nanoTime() - start2) / 1_000_000;
        
        // Сравнение
        double speedup = (double) time1 / time2;
        log.info("Без индекса: {}ms, С индексом: {}ms, Ускорение: {}x", 
            time1, time2, String.format("%.2f", speedup));
    }
}

10. Performance Testing Framework

public class PerformanceTester {
    
    private static final int WARMUP_ITERATIONS = 1000;
    private static final int TEST_ITERATIONS = 10000;
    
    public PerformanceResult testQuery(String name, Supplier<?> query) {
        // Warmup (JIT компиляция)
        for (int i = 0; i < WARMUP_ITERATIONS; i++) {
            query.get();
        }
        
        // Тест
        long[] times = new long[TEST_ITERATIONS];
        for (int i = 0; i < TEST_ITERATIONS; i++) {
            long start = System.nanoTime();
            query.get();
            times[i] = System.nanoTime() - start;
        }
        
        return new PerformanceResult(
            name,
            Arrays.stream(times).min().orElse(0) / 1_000_000,      // min
            Arrays.stream(times).max().orElse(0) / 1_000_000,      // max
            Arrays.stream(times).average().orElse(0) / 1_000_000   // average
        );
    }
}

public class PerformanceResult {
    public final String name;
    public final long minMs;
    public final long maxMs;  
    public final double avgMs;
    
    public PerformanceResult(String name, long min, long max, double avg) {
        this.name = name;
        this.minMs = min;
        this.maxMs = max;
        this.avgMs = avg;
    }
    
    @Override
    public String toString() {
        return String.format("%s: min=%dms, max=%dms, avg=%.2fms", 
            name, minMs, maxMs, avgMs);
    }
}

Метрики и пороги производительности

┌─ Отличная производительность
│  ├─ < 10ms  — Simple queries (find by ID)
│  ├─ < 50ms  — Complex queries with joins  
│  ├─ < 200ms — Batch operations
│  └─ < 500ms — Full table scans
│
├─ Нужна оптимизация
│  ├─ 500ms - 2s — Медленные запросы
│  ├─ 2s - 10s   — Очень медленные
│  └─ > 10s      — Критически медленные
│
└─ Решения
   ├─ Добавить индексы
   ├─ Оптимизировать JOIN'ы
   ├─ Использовать кэширование
   ├─ Переписать запрос
   └─ Распределить нагрузку

Измерение производительности — это постоянный процесс оптимизации приложения!

Как определить быстроту запроса? | PrepBro