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

На какую метрику лучше ориентироваться после усовершенствования медленного запроса

2.0 Middle🔥 71 комментариев
#Базы данных и SQL

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

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

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

На какую метрику лучше ориентироваться после усовершенствования медленного запроса

Введение

После оптимизации медленного SQL-запроса или доступа к базе данных важно выбрать правильные метрики для оценки эффекта. Неправильный выбор метрики может привести к тому, что оптимизация покажется неэффективной, даже если она на самом деле улучшила производительность. Рассмотрим, какие метрики использовать и почему.

1. Время выполнения запроса (Query Execution Time)

Это первая и самая очевидная метрика.

long startTime = System.currentTimeMillis();
List<User> users = userRepository.findActiveUsers();
long executionTime = System.currentTimeMillis() - startTime;

logger.info("Query execution time: {} ms", executionTime);

Плюсы:

  • Прямая метрика того, насколько быстро выполняется запрос
  • Легко измерять и отслеживать
  • Понятна всем членам команды

Минусы:

  • Может варьироваться в зависимости от нагрузки на БД
  • На dev/staging окружении может отличаться от production
  • Нужно измерять в контексте реальной нагрузки (не просто один запрос)

Рекомендация: Измеряйте percentiles (p50, p95, p99), а не просто среднее значение. На production медленные запросы (p99) создают плохой UX.

2. Пропускная способность (Throughput) — ЧАСТО БОЛЕЕ ВАЖНО

Это количество запросов в секунду, которые можно обработать.

Executor executor = Executors.newFixedThreadPool(100);
long startTime = System.currentTimeMillis();
int requestCount = 10000;

for (int i = 0; i < requestCount; i++) {
    executor.execute(() -> userRepository.findById(i));
}

long duration = System.currentTimeMillis() - startTime;
int throughput = (int) (requestCount / (duration / 1000.0));

logger.info("Throughput: {} requests/sec", throughput);

Почему это важнее:

  • В production часто обрабатывается множество конкурирующих запросов
  • Оптимизация может ускорить один запрос на 10%, но пропускная способность вырастет на 50% благодаря лучшему использованию ресурсов
  • Это метрика, которая напрямую влияет на SLA (Service Level Agreement)

Пример: Если запрос работал 100ms с throughput 10 req/sec, а после оптимизации работает 80ms, это не 20% улучшение — это 25% улучшение пропускной способности (до 12.5 req/sec).

3. Задержка (Latency) — P95 и P99 перцентили

Среднее время не всегда полезно. Важны перцентили:

// Используем Micrometer/Prometheus для сбора перцентилей
Timer.Sample sample = Timer.start();
try {
    result = userRepository.findById(userId);
} finally {
    sample.stop(Timer.builder("db.query.latency")
        .publishPercentiles(0.5, 0.95, 0.99)
        .register(meterRegistry));
}

Что это значит:

  • p50 (медиана): 50% запросов выполнены за это время
  • p95: 95% запросов выполнены за это время (5% медленнее)
  • p99: 99% запросов выполнены за это время (1% очень медленные)

Пример с реальными числами:

МетрикаДо оптимизацииПосле оптимизацииУлучшение
Среднее50ms45ms-10%
p5030ms20ms-33%
p95200ms80ms-60%
p99500ms150ms-70%

Важность p99: Если 1% пользователей ждёт 500ms, это создаёт плохой опыт. После оптимизации — только 150ms. Вот что действительно улучшилось!

4. Утилизация ресурсов

Оптимизация хороша, если она не сжигает CPU/Memory:

// Мониторим CPU и Memory использование
OperatingSystemMXBean osBean = 
    (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean();

double cpuUsage = osBean.getProcessCpuLoad() * 100;
long memoryUsed = Runtime.getRuntime().totalMemory() - 
    Runtime.getRuntime().freeMemory();

logger.info("CPU: {}%, Memory: {} MB", cpuUsage, memoryUsed / 1_000_000);

Важно:

  • Оптимизированный запрос не должен использовать больше CPU
  • Если вы используете caching, монитор memory leaks
  • Если добавили индекс, проверьте, что он эффективен (не full scan)

5. Объём передаваемых данных (Data transferred)

// Уменьшение объёма данных = уменьшение сетевой нагрузки
List<User> users = repository.findAll(); // ПЛОХО: вся таблица
List<UserDTO> users = repository.findUserProjection(); // ХОРОШО: только нужные поля

Оптимизация:

  • SELECT только нужные столбцы, не SELECT *
  • Используйте LIMIT если не нужны все строки
  • Компрессируйте данные при передаче

6. Количество database round trips (N+1 problem)

// ПЛОХО: N+1 problem
List<User> users = repository.findAll();
for (User user : users) {
    List<Order> orders = orderRepository.findByUserId(user.getId());
    // 1 запрос для users + N запросов для каждого user
}

// ХОРОШО: используем JOIN или fetch
List<UserWithOrders> users = repository.findAllWithOrders(); // 1 запрос

Метрика: Считайте количество SQL запросов в логах.

  • До: 100 запросов для 100 пользователей
  • После: 1 запрос

7. Hit rate кэша (если используется caching)

// Мониторим эффективность кэша
CacheManager cacheManager = ...; // Spring Cache или Redis
Cache cache = cacheManager.getCache("users");

long hits = cache.getStatistics().getCacheHits();
long misses = cache.getStatistics().getCacheMisses();
long hitRate = (hits / (hits + misses)) * 100;

logger.info("Cache hit rate: {}%", hitRate);

Идеальная hit rate: 80%+

  • Меньше 50% — кэш неэффективен
  • 80%+ — хороший результат

8. Cost of database query (на облачных сервисах)

// На AWS RDS, Azure SQL, GCP — оптимизация может снизить счёт
long bytes_scanned = 1_000_000_000; // 1GB
long cost_per_gb = 5; // $5 per GB

long query_cost_before = bytes_scanned * cost_per_gb; // $5000
long query_cost_after = (bytes_scanned / 10) * cost_per_gb; // $500 (с индексом)

logger.info("Cost savings: ${}", query_cost_before - query_cost_after);

Это важный фактор для бизнеса.

9. User-perceived latency (End-to-End)

// Самая важная метрика для пользователя!
// Не забывайте про сетевую задержку, сериализацию и т.д.

long totalTime = System.currentTimeMillis() - requestStartTime;
// Это включает:
// - Network delay до сервера
// - Query execution
// - Data serialization
// - Network delay обратно

logger.info("Total response time: {} ms", totalTime);

Часто оптимизация на 50% query времени даёт всего 10% улучшение end-to-end, потому что есть другие задержки!

Рекомендуемый подход

Оценивайте оптимизацию по этому порядку приоритета:

  1. P95/P99 latency — улучшение опыта для большинства пользователей
  2. Throughput — способность обработать пиковую нагрузку
  3. Query execution time — базовая метрика
  4. Resource utilization — не сжигайте CPU/Memory
  5. Cost (на облаке) — важно для бизнеса
  6. Hit rate кэша (если используется)
  7. End-to-end latency — итоговый результат для пользователя

Как правильно измерять

// На production используйте:
- APM tools (Datadog, New Relic, Dynatrace)
- Prometheus + Grafana для метрик
- OpenTelemetry для distributed tracing
- Database query logs для анализа

// Нарезайте результаты по временным интервалам:
- До оптимизации: 1 неделя baseline
- После оптимизации: 1 неделя на production
- Убедитесь, что нагрузка была примерно одинаковой

Типичные ошибки при измерении

  • Сравнивать пиковые нагрузки с off-peak
  • Забывать про cache warm-up период
  • Измерять только на dev окружении
  • Анализировать только среднее время (должны быть перцентили)
  • Забывать про конкуренцию — один запрос в vacuum != в боевых условиях

Заключение

Оптимальный набор метрик для оценки оптимизации:

  • P95/P99 latency — главная метрика UX
  • Throughput — главная метрика масштабируемости
  • Resource utilization — контролируем, что не деградирует
  • Query execution time — детальный анализ

Измеряйте на production, смотрите перцентили, а не средние значения, и помните: цель — лучший опыт для пользователя, а не просто быстрый код.