캐싱 적용해도 된다고 생각하는 이유 어차피 변경은 상폐아니면 비상폐만 다루면된다.
1. Redis 설정 (Config)
RedisTemplate<String, String>(JWT용)은 유지하면서, @Cacheable이 JSON 형식으로 데이터를 저장할 수 있도록 CacheManager를 등록합니다.
Java
@Configuration
@EnableCaching
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(24))
.disableCachingNullValues()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}
2. 데이터 전송 객체 (DTO)
엔티티 전체 대신 필요한 필드만 담아 메모리를 아낍니다. (Getter와 기본 생성자 필수)
Java
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class StocksCacheDto {
private Long id;
private String stockCode;
private String name;
public static StocksCacheDto from(Stock stock) {
return new StocksCacheDto(stock.getStockId(), stock.getStockCode(), stock.getName());
}
}
3. 서비스 레이어 (Service)
@Cacheable로 자동 조회/저장을 구현하고, @CacheEvict로 캐시 삭제 기능을 만듭니다.
Java
@Service
@RequiredArgsConstructor
public class StockServiceImpl implements StockService {
private final StockMapper stockMapper;
@Override
@Cacheable(value = "stocks", key = "'all'")
public List<StocksCacheDto> getStockList() {
List<Stock> stockList = stockMapper.selectStockList();
if(stockList == null || stockList.isEmpty()) {
throw new GlobalException(GlobalExceptionMessage.STOCK_NOT_FOUND);
}
return stockList.stream()
.map(StocksCacheDto::from)
.collect(Collectors.toList());
}
@Override
@CacheEvict(value = "stocks", allEntries = true)
public void clearStockCache() {
// 캐시 삭제용 (본문 비어있음)
}
}
4. 스케줄러 (Scheduler)
오전 9시와 오후 5시에 맞춰 캐시를 갱신(삭제 후 재조회)합니다.
Java
@Component
@RequiredArgsConstructor
public class StockScheduler {
private final StockService stockService;
// 초 분 시 일 월 요일 (09:00:00, 17:00:00)
@Scheduled(cron = "0 0 9,17 * * *")
public void refreshStockCache() {
// 1. 기존 캐시 삭제
stockService.clearStockCache();
// 2. 캐시 미리 채우기 (Warm-up)
stockService.getStockList();
System.out.println("주식 캐시 업데이트 완료");
}
}
5. 데이터 흐름 요약
- API 호출: 사용자가 /stocks 요청.
- Redis 확인: stocks::all 키가 있는지 확인.
- 결과 반환: * 있으면(Hit): DB 안 거치고 레디스에서 바로 반환 (속도 향상).
- 없으면(Miss): DB 조회 → DTO 변환 → 레디스 저장 → 반환.
- 자동 업데이트: 스케줄러가 지정된 시간에 레디스 데이터를 삭제하여 최신화를 유지.
- 호출 시도: 컨트롤러에서 stockService.getStockList()를 호출합니다.
- 캐시 확인: 스프링 프록시가 레디스에 가서 **stocks::all**이라는 키가 있는지 확인합니다.
- 결과 처리:
- Cache Hit (데이터 있음): 레디스에서 JSON 문자열을 가져와 List<StocksCacheDto>로 역직렬화한 뒤 메서드 본문을 실행하지 않고 즉시 반환합니다.
- Cache Miss (데이터 없음): 실제 서비스 메서드 안의 DB 조회 로직을 실행합니다.
- 자동 저장: 메서드가 반환한 List를 레디스에 stocks::all 키값으로 저장합니다.
Redis캐싱이 효과가 좋은 경우
① DB 커넥션 풀(Connection Pool) 고갈 상황
스프링 부트의 기본 HikariCP 커넥션 풀은 보통 10개입니다.
- 캐싱 없을 때: 500명이 동시에 오면, 10명이 DB를 쓰는 동안 나머지 490명은 줄 서서 대기해야 합니다. 이때 대기 시간이 2~3초를 훌쩍 넘기게 됩니다.
- 캐싱 있을 때: Redis는 커넥션 하나로도 초당 수만 건을 처리할 수 있고, DB 커넥션을 점유하지 않습니다. 대기 시간 없이 500명 모두 즉시 응답을 받습니다.
② 데이터 가공 로직 추가
단순 조회가 아니라, 자바 코드 내에서 2,600건의 데이터를 정렬하고, 그룹화하고, 특정 조건으로 필터링하는 로직이 있다고 가정해 보세요.
- 캐싱 없을 때: 500번의 요청마다 매번 CPU가 2,600번 루프를 돌며 계산해야 합니다.
- 캐싱 있을 때: 이미 계산이 완료된 결과값(JSON 등)을 그대로 가져오기만 하면 됩니다.
③ 외부 API 호출이 섞여 있을 때
만약 주식 목록을 DB가 아니라 외부 증권사 API에서 가져온다면?
- 500명이 동시에 외부 API를 때리면 호출 제한(Rate Limit)에 걸리거나 응답이 매우 느려집니다. 캐시를 쓰면 최초 1명만 API를 호출하고 나머지는 0.01초 만에 응답을 받습니다.
'Archive(완료된 내용) > 포트폴리오 강화' 카테고리의 다른 글
| [order101] 최적화 및 시나리오 - 12일차 (0) | 2026.02.05 |
|---|---|
| [stock101] 성능 개선 해보기 - 11일차 (0) | 2026.02.03 |
| [Stock101] gemini api를 쓰자. - 10일차 (0) | 2026.02.03 |
| [Stock101] 프로젝트 기획 마무리 정리 - 10일차 (0) | 2026.02.03 |
| [Stock101] 프로젝트를 정리하자.- 9일차 (0) | 2026.02.01 |