• [ 배경 ]
    • 중고거래 사이트 프로젝트를 진행하면서 멀티 서버 환경에서 대용량 트래픽을 처리하기 위해
      캐싱에 대해 고민해보려고 합니다.

  • [ 과정 ]
    • 캐시는 언제 써야 할까?
      • 반복적으로 동일한 결과를 리턴해야 하는 작업
      • 서버 자원을 많이 사용하는 작업 또는 시간이 오래 걸리는 작업 (API 호출, 데이터베이스 조회 쿼리,...)
      • 자주 조회되는 데이터
      • 입력값과 출력 값이 일정한 데이터
        • 캐싱된 데이터는 데이터 갱신으로 인해 DB와 불일치가 발생할 수 있습니다.
          Update가 잦거나 데이터 불일치 시 비즈니스 로직 상 문제가 발생할 수 있는 경우에는 캐싱 대상으로 부적합합니다.
        • Used-Market-Server에서 캐싱 대상은 '게시글 리스트와 게시글'입니다.
          자주 조회되는 데이터로 DB서버에 부하를 줄일 수 있습니다.
          중고거래 서비스 특징상 최신 게시글이 5초 정도 늦게 올라온다고
          서비스에 큰 무리가 가지 않는다고 판단하여서 중고물품 캐싱 만료시간을 5초로 설정하였습니다.
          (만약 실시간성 중요 데이터를 업데이트할 경우 별도로 처리할 예정입니다.)

  • Scale-Up vs Scale-Out 환경별 캐싱 전략 특징
    • Scale-Up 환경에선 Local Cache 적용
      • Scale-Up 환경에선 서버가 1개이어서 Local Cache로 데이터 처리가 가능합니다.
      • 캐시 된 데이터가 서버 사이에 일관되지 않아도 됩니다.
        즉, 서버 간 캐싱 데이터를 참조할 필요가 없습니다.
    • Scale-Out 환경에선 Global Cache 적용
      • Scale-Out 환경에선 여러 서버에서 캐시 서버를 참조해야 합니다.
      • 서버 간 데이터를 공유해야 하는 환경입니다.
        즉, 캐싱된 데이터가 일관되려면 서버 간 캐싱 데이터를 참조해야 합니다.
  • Used-Market-Server는 Scale-Out 환경이어서 Global Cache 전략을 선택하였습니다.
  • 세션 저장을 위한 Redis 서버를 사용하고 있어서 글로벌 캐싱도 Redis를 사용하여 자원을 효율적으로 사용할 수 있다고 생각하였습니다.

 

Global Cache 적용 구상도

  • 캐싱 적용을 위한 @EnableCaching 적용
    • Spring boot를 실행하는 상위 클래스에 @EnableCaching 어노테이션을 적용해야 합니다.
      해당 어노테이션을 적용하면 Spring에서 Cache에 관련한 어노테이션을 스캔하여 캐싱 적용을 진행합니다.
  • SpringCacheManager에 RedisCacheManager 주입
    • redisTemplate을 json 형식으로 데이터를 받을 때 값이 일정하도록 직렬화 합니다.
      저장할 클래스가 여러 개일 경우 범용적으로 저장할 수 있게 GenericJackson2JsonRedisSerializer를 이용합니다.
    • RedisCacheManager는 SpringFramework의 CacheManager를 구현합니다.
      Spring에서는 RedisCacheManager를 Bean으로 등록하면 기본 CacheManager를 RedisCacheManager로 사용합니다.
  • Redis를 캐시 저장소로 사용하기 위한 설정입니다. 중고 물품 만료시간을 5초로 설정하였습니다.
  • ProductDao 클래스에선 RedisTemplate을 이용하여 자주 사용되는 중고물품을 @PostConstruct 가 있는 메서드에서 bean 등록 시에 DEFAULT_PRODUCT_SEARCH_CACHE_KEY 에 최대 2000개 저장합니다.
    • 물품 삭제시 물품 Key값을 이용해  RedisTemplate에 등록되어있는 value의 index를 찾아 삭제합니다.
    • 물품 조회시 DEFAULT_PRODUCT_SEARCH_CACHE_KEY에 있는 values를 조회합니다.
    • 물품 등록 시 Product의 insert 시의 auto-Increment id를 가지고  RedisTemplate의 value에 등록합니다.
  • ProductService 에선 물품 등록 시에 ProductDao에서 정의한 물품 등록 시 RedisTemplate에 Key와 value를 저장합니다.
  • 물품 삭제 시에는 현재 물품이 레디스 values 중 몇 번째 인덱스인지 찾은 후 RedisTempalte에서 삭제되게 ProductDao에서 정의한 deleteByProductIdAndIndex 메서드를 호출합니다.
  • ProductSearchService 에선 검색 시 캐시 key값을 이용해 게시글을 검색하게끔 ProductDao에서 정의한 findAllProductsByCacheId 메서드를 호출합니다.
  • ProductSearchController 에선 중고물품 검색시 캐싱되어있는 값을 가져올 수 있게 ProductSearchService에서findAllProductsByCacheId 메서드를 호출합니다.
    만약 캐싱 데이터가 없다면 DB에서 직접 조회하게끔 하였습니다.
  • Redis Cli 키 결과

  • Swagger API 화면


  • [ 결과 ]
    • 자주 조회되는 중고물품을 만료시간 5초를 기준으로 캐싱하여 조회 시 DB서버에 부하를 줄였습니다.
    • 캐싱된 데이터가 업데이트되었을 시 캐시 데이터를 삭제해주지 않으면 데이터의 정합성이 없어지기 때문에 게시글 중 새로운 글을 등록, 수정할 때 캐시 정보를 갱신했습니다.

  • [ 성과 ]
    • 자주 조회되며 서버 자원을 많이 사용하는 데이터인 중고 물품 게시글들을 WAS가 들고 데이터를 처리하므로 조회 성능이 향상되었습니다.

 

junshock5/used-market-server

중고거래 프로젝트. Contribute to junshock5/used-market-server development by creating an account on GitHub.

github.com

+ Recent posts