← Назад к вопросам
Были ли проблемы с Latency запросов
2.2 Middle🔥 191 комментариев
#JVM и управление памятью#REST API и микросервисы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
# Были ли проблемы с Latency запросов
Проблемы с задержками (latency) запросов — это одна из самых частых проблем в production-окружении. Я столкнулся с несколькими типичными сценариями и способами их решения.
Типичные причины высокой латентности
1. Неоптимизированные SQL запросы
Проблема: N+1 queries
// ❌ Плохо: N+1 запросы
public List<User> getUsersWithOrders() {
List<User> users = userRepository.findAll(); // 1 запрос
for (User user : users) {
user.getOrders(); // N дополнительных запросов!
}
return users;
}
// ✅ Хорошо: Eager loading
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u LEFT JOIN FETCH u.orders")
List<User> findAllWithOrders();
}
// ✅ или через @EntityGraph
@EntityGraph(attributePaths = {"orders"})
List<User> findAll();
2. Неиндексированные колонки
Проблема: Полное сканирование таблицы
// ❌ Медленно без индекса
SELECT * FROM orders WHERE status = "COMPLETED" AND created_at > ?
// ✅ Решение: создать составной индекс
CREATE INDEX idx_orders_status_date ON orders(status, created_at);
// В Hibernate можно указать индексы через аннотации
@Entity
@Table(indexes = {
@Index(name = "idx_orders_status_date",
columnList = "status, created_at", unique = false)
})
public class Order {
// ...
}
3. Неправильное использование connection pool
Проблема: Истощение connections
# Неправильная конфигурация (по умолчанию)
spring.datasource.hikari.maximum-pool-size=10 # Слишком мало!
spring.datasource.hikari.connection-timeout=30000 # Может истечь
# ✅ Оптимальная конфигурация
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
# Мониторинг
logging.level.com.zaxxer.hikari=DEBUG # Видим когда pool saturated
4. Сетевые задержки
// ❌ Проблема: синхронные вызовы API
public UserProfile buildProfile(Long userId) {
User user = userService.getUser(userId); // 50ms
List<Order> orders = orderService.getOrders(userId); // 100ms
List<Review> reviews = reviewService.getReviews(userId); // 75ms
return new UserProfile(user, orders, reviews); // Итого 225ms
}
// ✅ Решение: параллельные запросы
public UserProfile buildProfile(Long userId) {
CompletableFuture<User> userFuture =
CompletableFuture.supplyAsync(() -> userService.getUser(userId));
CompletableFuture<List<Order>> ordersFuture =
CompletableFuture.supplyAsync(() -> orderService.getOrders(userId));
CompletableFuture<List<Review>> reviewsFuture =
CompletableFuture.supplyAsync(() -> reviewService.getReviews(userId));
CompletableFuture.allOf(userFuture, ordersFuture, reviewsFuture).join();
return new UserProfile(
userFuture.join(),
ordersFuture.join(),
reviewsFuture.join()
); // Всего ~100ms (время самого медленного)
}
5. Проблемы с кешированием
// ❌ Без кеша: каждый раз ходим в БД
public Category getCategory(String name) {
return categoryRepository.findByName(name); // Медленно при частых вызовах
}
// ✅ С Redis кешем
@Cacheable(value = "categories", key = "#name")
public Category getCategory(String name) {
return categoryRepository.findByName(name);
}
@CacheEvict(value = "categories", allEntries = true)
public void updateAllCategories() {
// Очищаем кеш при обновлении
}
// ✅ Для критичных данных — in-memory кеш
public class CategoryCache {
private final ConcurrentHashMap<String, Category> cache = new ConcurrentHashMap<>();
private final CategoryRepository repo;
private static final long TTL_MINUTES = 30;
private final ScheduledExecutorService scheduler;
public Category get(String name) {
return cache.computeIfAbsent(name, key -> {
Category cat = repo.findByName(key);
// Время жизни через scheduled refresh
return cat;
});
}
}
6. Проблемы с garbage collection
// Мониторинг GC pause time
public class LatencyMonitor {
private final MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
private final GarbageCollectorMXBean gcBean =
ManagementFactory.getGarbageCollectorMXBeans().get(0);
public void logGCMetrics() {
long gcTime = gcBean.getCollectionTime();
long gcCount = gcBean.getCollectionCount();
System.out.println("GC Time: " + gcTime + "ms, Count: " + gcCount);
}
}
// ✅ Оптимизация через JVM flags
// -XX:+UseG1GC (для Java 9+)
// -XX:MaxGCPauseMillis=200
// -Xms4G -Xmx4G (явное выделение памяти)
7. Медленные сторонние API
// ✅ Установить timeout
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5)) // Connection timeout
.build();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com"))
.timeout(Duration.ofSeconds(10)) // Request timeout
.build();
// ✅ Retry логика с exponential backoff
public <T> T callWithRetry(Supplier<T> supplier, int maxRetries) {
for (int i = 0; i < maxRetries; i++) {
try {
return supplier.get();
} catch (Exception e) {
if (i == maxRetries - 1) throw e;
long delay = 100 * (long) Math.pow(2, i); // 100ms, 200ms, 400ms...
Thread.sleep(delay);
}
}
return null;
}
Мониторинг и диагностика
// Spring Boot Actuator для мониторинга латентности
@Component
public class LatencyMetricsCollector {
private final MeterRegistry meterRegistry;
public void recordQueryTime(String query, long duration) {
Timer.builder("db.query.time")
.tag("query", query)
.register(meterRegistry)
.record(duration, TimeUnit.MILLISECONDS);
}
}
// Логирование медленных запросов
spring.jpa.properties.hibernate.generate_statistics=true
spring.jpa.properties.hibernate.use_sql_comments=true
logging.level.org.hibernate.stat=DEBUG
Вывод
Мост проблем с латентностью решается через:
- Правильные индексы на БД
- Оптимизация N+1 запросов через JOIN FETCH
- Кеширование горячих данных
- Параллелизм для независимых операций
- Мониторинг и профилирование
- Правильная конфигурация connection pool
Ключевой принцип: всегда измеряй перед оптимизацией!