경기 목록 조회 기능은 접근 빈도가 높은 데이터에 대해 반복적으로 DB를 요청한다.
특히, 특정 시간대에 사용자가 몰리며 서버 부하가 증가하고, DB 접근 횟수가 급격히 늘어나 평균 응답 시간이 느려지는 문제가 있다.
이를 해결하기 위해 캐시를 도입해 DB 접근 빈도를 줄이고 조회 속도를 개선하고자 한다. 특히 경기 목록은 특정 시간(당일 티켓팅 오픈 시간)에 집중적으로 조회되고, 데이터에 변경이 없기 때문에 캐시 적용 시 성능 개선 효과가 클 것이라고 판단했다.
캐시란?
복잡한 연산이나 자주 사용되는 데이터를 속도가 빠른 임시 저장소에 저장해두고, 요청 시 빠르게 제공하는 방식이다. 반복적인 동일 데이터 요청이 많거나, 읽기 성능 개선이 필요한 경우, DB 부하를 줄여야 하는 상황에서 유용하다.
ngrinder 을 이용해 부하 테스트를 해보자.
현재 평균 응답 시간이 365.03ms이고, TPS는 272.1이다.
현재는 100명을 기준으로 테스트하였지만, 티켓 시스템 특성상 특정 시간대에 트래픽이 집중되면 성능 저하가 더욱 심화될 가능성이 있다. 이를 해결하기 위해 캐시 도입이 필요하다고 판단했다.
구현
Redis는 인메모리 데이터 저장소로, 빠른 읽기, 쓰기 속도를 제공하고, TTL(Time To Live)을 활용해 캐시 데이터의 유효 기간을 관리할 수 있다. 그리고 Spring 프레임워크에서 제공하는 @Cacheable 애노테이션을 활용해 캐시 저장 및 조회 과정을 간단하게 구현할 수 있다.
먼저 Redis를 캐시 저장소로 사용하기 위해 RedisCacheManager를 정의했다. 이를 통해 Spring Cache와 Redis를 연결하고, TTL 설정으로 캐시의 유효 기간을 30분으로 설정했다.
RedisConfig
@Bean
public RedisCacheManager redisCacheManager() {
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofMinutes(30L));
return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(redisConnectionFactory())
.cacheDefaults(redisCacheConfiguration).build();
}
Spring은 기본적으로 SimpleCacheManager를 사용하는데, 이때 캐시 데이터는 메모리에 저장된다.
Redis를 캐시 저장소로 사용하기 위해서는 RedisCacheManager를 정의하여 Spring Cache와 Redis를 연결해야 한다. 이를 통해 @Cacheable이 동작할 때 Redis를 통해 캐시 데이터를 저장하거나 조회할 수 있다.
캐시가 적용될 부분
@Cacheable(value="gamelist", key="#gameKey")
public GameSearchResponse getGameList(GameSearchRequest gameSearchRequest, String gameKey, String cursorId, int limit) {
List<GameDetailResponse> games = gameRepository.getByCursor(gameSearchRequest, cursorId, limit);
boolean hasNext = false;
if (games.size() > limit) {
hasNext = true;
}
return GameSearchResponse.builder()
.games(games)
.hasNext(hasNext)
.build();
}
특정 캐시 gamelist에 데이터를 저장하고, 다음 요청 시 #gameKey로 캐시된 데이터를 가져온다.
@Cacheable는 캐시에 데이터가 없는 경우에는 기존의 로직을 실행한 후에 캐시에 데이터를 추가하고, 캐시에 데이터가 있으면 그대로 데이터를 반환한다. value값은 캐시 이름으로 Redis에서 데이터가 저장될 영역을 나타내며, key는 캐시에 저장할 데이터의 키를 의미한다.
개선
캐시 도입 후 아래와 같은 개선 효과를 확인할 수 있었다.
- 평균 응답 시간: 365.03ms에서 116.99ms로 약 68% 감소.
- TPS(초당 트랜잭션 수): 272.1에서 833.1로 약 3배 증가.
- DB 부하 감소: DB 쿼리 요청 횟수 감소.
결과적으로, 캐시 도입을 통해 평균 응답 시간을 300ms에서 100ms 미만으로 단축되었고, TPS(초당 처리량)는 약 3배 증가한 것을 확인할 수 있었다. 이를 통해 사용자 경험과 시스템 안정성을 동시에 개선할 수 있었다.
'Project' 카테고리의 다른 글
CreatedDate, LastModifiedDate 이슈 (0) | 2025.01.07 |
---|---|
[KBOTicket] 도커로 애플리케이션 배포하기 (0) | 2025.01.07 |
[KBOTicket] CI/CD의 개념과 구현 (0) | 2025.01.07 |
[KBOTicket] Kafka를 이용한 티켓팅 대기열 시스템 구현 및 순번 처리 (2) (0) | 2025.01.07 |
[KBOTicket] Kafka를 이용한 티켓팅 대기열 시스템 구현 및 순번 처리 (1) (0) | 2025.01.06 |