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

Какие знаешь пути уменьшения времени ответа на запрос?

3.0 Senior🔥 201 комментариев
#REST API и микросервисы#Кэширование и NoSQL

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

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

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

Уменьшение времени ответа на запрос

Время ответа (latency) — это критичный метрика для пользовательского опыта и масштабируемости. Я применяю множество стратегий для его оптимизации на разных уровнях архитектуры.

1. Оптимизация запросов к БД

N+1 Problem — одна из самых распространённых проблем. Нужно загружать данные эффективно.

// ❌ Плохо — N+1 запрос
public List<Author> getAllAuthorsWithBooks() {
    List<Author> authors = entityManager.createQuery(
        "SELECT a FROM Author a", Author.class
    ).getResultList();
    
    for (Author author : authors) {
        author.getBooks().size(); // N дополнительных запросов
    }
    return authors;
}

// ✅ Хорошо — одного запроса с JOIN
public List<Author> getAllAuthorsWithBooks() {
    return entityManager.createQuery(
        "SELECT DISTINCT a FROM Author a " +
        "JOIN FETCH a.books",
        Author.class
    ).getResultList();
}

Использование индексов — один из самых эффективных способов.

@Entity
@Table(name = "users", indexes = {
    @Index(name = "idx_username", columnList = "username", unique = true),
    @Index(name = "idx_email", columnList = "email"),
    @Index(name = "idx_created_at", columnList = "created_at")
})
public class User {
    // ...
}

Пакетная обработка (Batch Processing)

@Transactional
public void saveManyUsers(List<User> users) {
    for (int i = 0; i < users.size(); i++) {
        entityManager.persist(users.get(i));
        
        if (i % 100 == 0) {
            entityManager.flush();
            entityManager.clear(); // очищаем кэш
        }
    }
}

2. Кэширование

Распределённое кэширование на уровне приложения.

@Service
public class UserService {
    @Cacheable("users") // результат кэшируется
    public User getUserById(UUID id) {
        return userRepository.findById(id)
            .orElseThrow(() -> new NotFoundException("User not found"));
    }
    
    @CacheEvict("users", key = "#result.id") // кэш инвалидируется
    public User updateUser(User user) {
        return userRepository.save(user);
    }
    
    @CachePut("users", key = "#result.id") // кэш обновляется
    public User createUser(User user) {
        return userRepository.save(user);
    }
}

Redis для сессий и кэша

@Configuration
@EnableCaching
public class CacheConfig {
    @Bean
    public LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory();
    }
    
    @Bean
    public RedisCacheManager cacheManager(LettuceConnectionFactory cf) {
        return RedisCacheManager.create(cf);
    }
}

3. Асинхронная обработка

@Async для длительных операций

@Service
public class EmailService {
    @Async
    public CompletableFuture<Void> sendEmail(Email email) {
        try {
            mailSender.send(email);
            return CompletableFuture.completedFuture(null);
        } catch (Exception e) {
            return CompletableFuture.failedFuture(e);
        }
    }
}

@Service
public class OrderService {
    @Autowired
    private EmailService emailService;
    
    @Transactional
    public Order createOrder(Order order) {
        Order saved = orderRepository.save(order);
        emailService.sendEmail(buildConfirmationEmail(saved));
        return saved; // ответ отправится быстро
    }
}

Message Queue (RabbitMQ, Kafka)

@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Transactional
    public Order createOrder(Order order) {
        Order saved = orderRepository.save(order);
        
        // отправляем сообщение асинхронно
        rabbitTemplate.convertAndSend(
            "order.created",
            new OrderCreatedEvent(saved.getId())
        );
        
        return saved;
    }
}

4. Оптимизация объёма данных

Projection — выбираем только нужные поля

public interface UserSummary {
    UUID getId();
    String getUsername();
    String getEmail();
}

public interface UserRepository extends JpaRepository<User, UUID> {
    @Query("SELECT new map(u.id as id, u.username as username, u.email as email) " +
            "FROM User u WHERE u.active = true")
    List<UserSummary> findActiveSummaries();
}

Pagination — загружаем страницами

public interface UserRepository extends JpaRepository<User, UUID> {
    Page<User> findByStatusOrderByCreatedAtDesc(
        UserStatus status,
        Pageable pageable
    );
}

@GetMapping("/users")
public Page<UserDTO> getUsers(@PageableDefault(size = 20) Pageable pageable) {
    return userRepository.findByStatusOrderByCreatedAtDesc(ACTIVE, pageable)
        .map(UserDTO::fromEntity);
}

5. Connection Pool

HikariCP — оптимальное управление подключениями к БД

spring.datasource.hikari.maximum-pool-size=10
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=20000
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.max-lifetime=1200000

6. Параллелизм

CompletableFuture для параллельных вызовов

@Service
public class ProductService {
    @Autowired
    private RecommendationService recommendations;
    
    @Autowired
    private ReviewService reviews;
    
    public ProductDetailDTO getProductDetail(UUID productId) {
        Product product = productRepository.findById(productId).orElseThrow();
        
        return CompletableFuture.supplyAsync(() -> product)
            .thenApplyAsync(p -> enrichWithRecommendations(p))
            .thenApplyAsync(p -> enrichWithReviews(p))
            .thenApply(ProductDetailDTO::fromEntity)
            .join();
    }
}

7. Compression

GZIP компрессия для HTTP responses

server.compression.enabled=true
server.compression.min-response-size=1024
server.compression.mime-types=application/json,application/xml,text/html,text/xml,text/plain

8. Lazy Loading и Streaming

Потоковая обработка больших объёмов

@Service
public class DataExportService {
    @Autowired
    private UserRepository userRepository;
    
    public void exportUsers(OutputStream output) {
        try (Stream<User> stream = userRepository.streamAll()) {
            stream
                .map(UserDTO::fromEntity)
                .map(JSON::toJson)
                .forEach(json -> output.write(json.getBytes()));
        }
    }
}

9. Query Optimization

Использование статистики and execution plans

@Repository
public interface UserRepository extends JpaRepository<User, UUID> {
    // ❌ Медленно — полное сканирование
    List<User> findByEmailContaining(String email);
    
    // ✅ Быстро — с индексом
    @Query("SELECT u FROM User u WHERE u.email = :email")
    Optional<User> findByExactEmail(@Param("email") String email);
}

10. CDN и Static Assets

Кэширование статических ресурсов

@Configuration
weblic class WebConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**")
            .addResourceLocations("classpath:/static/")
            .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)
                .cachePublic());
    }
}

Профилирование и мониторинг

Micrometer + Spring Boot Actuator

@Service
public class UserService {
    private final MeterRegistry meterRegistry;
    
    public User getUserById(UUID id) {
        Timer.Sample sample = Timer.start(meterRegistry);
        try {
            return userRepository.findById(id).orElseThrow();
        } finally {
            sample.stop(Timer.builder("user.get.latency")
                .description("Latency of getting user")
                .register(meterRegistry));
        }
    }
}

Заключение

Уменьшение времени ответа — это система мер, а не одна волшебная пуля. Нужно профилировать, измерять и оптимизировать на основе реальных данных. Начинаю с наиболее узких мест и двигаюсь оттуда.