본문 바로가기
프로젝트/Careers

Redis Cache를 이용한 애플리케이션 성능 향상시키는 방법

by 에어컨조아 2021. 8. 11.

캐싱이란 무엇인가

  • 오랜 시간이 걸리는 작업의 결과를 저장해서 향후 동일한 요청 시 시간과 비용을 단축시키는 기법입니다.
  • 고성능 애플리케이션을 만드는데 가장 중요한 포인트 중 하나입니다.

캐싱의 종류 및 비교

캐싱에는 크게 로컬캐싱과 글로벌 캐싱으로 나누어서 생각해볼 수 있습니다.

로컬 캐싱 : WAS 서버에서 사용
글로벌 캐싱 : 별도의 캐시 서버를 둬서 사용

로컬캐싱 vs 글로벌 캐싱

항목 로컬 캐싱 글로벌 캐싱
저장위치 애플리케이션 서버에 저장 별도의 캐시 서버에 저장
데이터간 공유 다른 서버에서 참조하기 어려움 서버간 데이터 공유 쉬움
Memory, Disk 사용 애플리케이션 장비와 공유 별도의 서버를 사용함으로 해당 서버의 리소스를 모두 사용가능
캐시 데이터 변경 시 - 해당 서버를 제외하고 모든 Peer에 변경사항 전달
- All-to-All Replication
- 애플리케이션 서버가 Scale out하게되면 성능저하됨
서비스가 확장될때, 캐시 데이터가 클 수록 효과적임

장단점

구분 장점 단점
로컬 캐시 같은 JVM내에 혹은 같은 장비에서 데이터를 가져오므로 성능이 좋다. 하나의 장비내에 있는 데이터만 동기화가 이루어 진다.
글로벌 캐시 데이터 동기화가 실시간으로 이루어지기 때문에 모든 사용자가 동일한 데이터를 가질수 있다. 네트워크를 통해 데이터를 전달하기 때문에 로컬 캐시에 비해 상대적으로 느리다.

두 캐시를 비교 후 Careers 프로젝트에서는 대용량 트래픽에 대한 고려와 서비스 확장에도 무리없이 소화가능한 글로벌 캐시를 사용하기로 하였습니다.

Cache hit, cache miss 란

캐싱을 반영하기 위해서는 cache hit, cache miss 개념에 대해서 인지해야 합니다.

cache hit : 요청한 데이터가 캐시에 존재할 경우 캐시 히트라고 말합니다.
cache miss : 요청한 데이터가 캐시에 없을 경우 캐시 미스라고 말합니다.

애플리케이션에서 캐시 히트율이 높은 경우에 성능이 좋은 애플리케이션이라 할 수 있습니다.
캐시 미스가 발생되면 DB에 해당 데이터를 요청하고 캐시에 저장 후 클라이언트에 전송됩니다. 해서 추후 동일 요청 시 캐시 히트가 발생하게됩니다.

즉 여러 곳에 반영하는 것이 아니라 서비스를 이용하면서 어떤 부분에 캐싱 처리를 하여 cache hit율을 높일지 고민해야만 했습니다.

그럼 어느 기능에 적용하는게 좋은가?

고려사항

  • 요청에 따른 동일한 결과값
  • 자주 조회되는 데이터
  • 자주 업데이트가 발생하지 않는 데이터

여러 기능 중 업데이트 빈도수가 적으면서, 자주 조회하는 데이터가 무엇일지 고민해보니 Profile 기능이 적합하다고 판단하였습니다. 그래서 이미 Session 스토리지로 사용하고있는 Redis를 이용하여 캐싱을 적용하기로 하였습니다.

Spring Boot cache적용방법

/**
 * @author junehee
 */
@EnableCaching
@SpringBootApplication
public class CareersApplication {

    public static void main(String[] args) {
        SpringApplication.run(CareersApplication.class, args);
    }
}

스프링 부트에서는 @EnableCaching 애노테이션으로 캐싱 기능을 활성화합니다.

캐싱 기능이 활성화가 되면 내부적으로 Proxy 패턴을 사용하여 @Cacheable 애노테이션이 달린 메서드들이 호출될 때마다 캐싱 처리를 하게 됩니다. 물론 처음 호출되면 캐싱 된 데이터가 없기 때문에 DB에서 데이터를 가져온 뒤 이후 요청부터 캐싱 처리하게 됩니다.

@Bean
public CacheManager redisCacheManager(
    @Qualifier("redisCacheConnectionFactory") RedisConnectionFactory redisConnectionFactory) {

    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
        .defaultCacheConfig()
        .serializeKeysWith(RedisSerializationContext.SerializationPair
            .fromSerializer(new StringRedisSerializer()))
        .serializeValuesWith(RedisSerializationContext.SerializationPair
            .fromSerializer(new GenericJackson2JsonRedisSerializer()))
        .entryTtl(Duration.ofDays(cacheTtl));

    return RedisCacheManager.RedisCacheManagerBuilder
        .fromConnectionFactory(redisConnectionFactory)
        .cacheDefaults(redisCacheConfiguration).build();
}

RedisCacheManager를 빈으로 등록하여 사용하도록 하였으며, ttl 설정을 주어 일정 시간이 지나면 데이터를 삭제하도록 설정하였습니다.

@Cacheable(key = "#curatorId", value = "getProfile")
public Profile getProfile(int curatorId) {
    return profileMapper.getUserProfile(curatorId);
}

캐시의 이름은 getProfile로 설정하였으며, 해당 캐시의 key 값은 매개변수로 넘어오는 curatorId값으로 하였습니다. 즉 getProfile요청이 들어오면 getProfile의 캐시의 key값을 통해 값을 확인 후 체크하여 없으면 DB에서 값을 가져오고 있으면 캐시에 데이터를 읽어와 바로 전달하게 됩니다.

Redis 캐싱을 반영 후 성능개선 확인 😎

Redis 캐시 도입 전

Redis 캐시 도입 후

도입 전 722ms, 도입 후 109ms로 약 7배 정도 성능효과를 보았습니다.

Reference

https://zdnet.co.kr/view/?no=20131119174125
https://j0free.tistory.com/3
https://12bme.tistory.com/550
https://dahye-jeong.gitbook.io/spring/spring/2020-04-09-cache#undefined-2
https://meetup.toast.com/posts/225

'프로젝트 > Careers' 카테고리의 다른 글

Logging Framework는 어떤게 좋을까?  (0) 2021.08.16
Jenkins를 사용한 CD 구성  (0) 2021.08.12
Jenkins를 사용한 CI 구성  (0) 2021.08.05
CI/CD 왜 필요한가?  (0) 2021.08.05
Session Storage 선택과정  (0) 2021.07.29

댓글