Ответ
Контекст: В микросервисе на Spring Boot, отвечающем за каталог товаров, критический эндпоинт GET /products/{id} выполнял тяжелые JOIN-запросы и расчеты, что при высокой нагрузке приводило к задержкам >500 мс и нагрузке на БД.
Предложенное решение: Внедрение двухуровневого кэширования:
- Локальный кэш в памяти (Caffeine) для сверхбыстрого доступа к самым популярным товарам.
- Распределенный кэш (Redis) как единый источник истины для всех экземпляров сервиса, обеспечивающий консистентность данных.
Реализация (упрощенный код):
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository repository;
private final CacheManager cacheManager;
@Cacheable(value = "products", cacheManager = "redisCacheManager")
public ProductDto getProduct(Long id) {
// Дорогой запрос в БД и преобразование
Product entity = repository.findWithDetailsById(id);
return heavyMappingLogic(entity);
}
@CacheEvict(value = "products", key = "#id")
public void updateProduct(Long id, ProductUpdateDto dto) {
// Инвалидация кэша при обновлении
repository.update(id, dto);
}
}
// Конфигурация двухуровневого кэша
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager caffeineManager = new CaffeineCacheManager();
caffeineManager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES));
return new CompositeCacheManager(caffeineManager, redisCacheManager());
}
}
Результаты:
- Среднее время ответа API упало с 500 мс до 15 мс для кэшированных товаров.
- Нагрузка на БД снизилась на ~60% для read-операций.
- Решение было задокументировано и стало шаблоном для других сервисов команды.
Ответ 18+ 🔞
А, слушай, вот это реально смешная история, как мы тут один сервис с каталогом отъебали от медленной смерти. Представь себе: микросервис на Spring Boot, который товары отдаёт. И там есть эндпоинт GET /products/{id}, который по идее должен просто плюнуть тебе данные в ответ.
А на деле, сука, он там такие запросы в базу городил, с JOIN'ами на три таблицы, да ещё и расчёты какие-то ебаные в коде делал после этого. В общем, под нагрузкой он начинал тупить так, что 500 миллисекунд — это был не предел, а так, средненький результат. База просто лежала и стонала, как последняя шлюха под ордой матросов.
И что делать? Ну, классика жанра, блядь — кэширование. Но мы решили не просто хуй в пальто воткнуть, а сделать по-взрослому, с двухэтажным размахом.
Первый этаж — это локальный кэш в памяти, на Caffeine. Быстрее него только мысль о том, что пора домой. Он ловит самые популярные товары, те, по которым все лупят. Второй этаж — Redis, распределённый, чтоб у всех инстансов сервиса одна правда была. Если в одном месте данные обновили, чтоб в другом не выдали вчерашние помои.
Вот смотри, как это в коде выглядело, упрощённо, конечно:
@Service
@RequiredArgsConstructor
public class ProductService {
private final ProductRepository repository;
private final CacheManager cacheManager;
@Cacheable(value = "products", cacheManager = "redisCacheManager")
public ProductDto getProduct(Long id) {
// Вот этот кусок — тот самый дорогой запрос, который всех ебал
Product entity = repository.findWithDetailsById(id);
return heavyMappingLogic(entity);
}
@CacheEvict(value = "products", key = "#id")
public void updateProduct(Long id, ProductUpdateDto dto) {
// А тут при обновлении кэш чистим, чтоб не застаивался
repository.update(id, dto);
}
}
// А это конфиг, где мы два кэша в одну кучу-малу собрали
@Configuration
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager caffeineManager = new CaffeineCacheManager();
caffeineManager.setCaffeine(Caffeine.newBuilder().expireAfterWrite(5, TimeUnit.MINUTES));
return new CompositeCacheManager(caffeineManager, redisCacheManager());
}
}
И что ты думаешь, ёпта? После этого цирка с конями среднее время ответа для кэшированных товаров упало с 500 мс до 15, блядь, миллисекунд. Это как вместо того, чтобы ждать, пока бабка через дорогу переползёт, тебе в рот берунчик с мороженым сразу залетает. Нагрузка на базу просела на 60%, она теперь не пыхтит, как паровоз, а так, посвистывает себе тихонько.
И самое охуенное — задокументировали мы это дело, и теперь вся команда по этому шаблону как по маслу катает. Красота, ебать мои старые костыли!