- [ 배경 ]
- 중고거래 사이트 프로젝트를 진행하면서 멀티 서버 환경에서 대용량 트래픽을 처리하기 위해
캐싱에 대해 고민해보려고 합니다.
- 중고거래 사이트 프로젝트를 진행하면서 멀티 서버 환경에서 대용량 트래픽을 처리하기 위해
- [ 과정 ]
- 캐시는 언제 써야 할까?
- 반복적으로 동일한 결과를 리턴해야 하는 작업
- 서버 자원을 많이 사용하는 작업 또는 시간이 오래 걸리는 작업 (API 호출, 데이터베이스 조회 쿼리,...)
- 자주 조회되는 데이터
- 입력값과 출력 값이 일정한 데이터
- 캐싱된 데이터는 데이터 갱신으로 인해 DB와 불일치가 발생할 수 있습니다.
Update가 잦거나 데이터 불일치 시 비즈니스 로직 상 문제가 발생할 수 있는 경우에는 캐싱 대상으로 부적합합니다. - Used-Market-Server에서 캐싱 대상은 '게시글 리스트와 게시글'입니다.
자주 조회되는 데이터로 DB서버에 부하를 줄일 수 있습니다.
중고거래 서비스 특징상 최신 게시글이 5초 정도 늦게 올라온다고
서비스에 큰 무리가 가지 않는다고 판단하여서 중고물품 캐싱 만료시간을 5초로 설정하였습니다.
(만약 실시간성 중요 데이터를 업데이트할 경우 별도로 처리할 예정입니다.)
- 캐싱된 데이터는 데이터 갱신으로 인해 DB와 불일치가 발생할 수 있습니다.
- 캐시는 언제 써야 할까?
- Scale-Up vs Scale-Out 환경별 캐싱 전략 특징
- Scale-Up 환경에선 Local Cache 적용
- Scale-Up 환경에선 서버가 1개이어서 Local Cache로 데이터 처리가 가능합니다.
- 캐시 된 데이터가 서버 사이에 일관되지 않아도 됩니다.
즉, 서버 간 캐싱 데이터를 참조할 필요가 없습니다.
- Scale-Out 환경에선 Global Cache 적용
- Scale-Out 환경에선 여러 서버에서 캐시 서버를 참조해야 합니다.
- 서버 간 데이터를 공유해야 하는 환경입니다.
즉, 캐싱된 데이터가 일관되려면 서버 간 캐싱 데이터를 참조해야 합니다.
- Scale-Up 환경에선 Local Cache 적용
- Used-Market-Server는 Scale-Out 환경이어서 Global Cache 전략을 선택하였습니다.
- 세션 저장을 위한 Redis 서버를 사용하고 있어서 글로벌 캐싱도 Redis를 사용하여 자원을 효율적으로 사용할 수 있다고 생각하였습니다.
- 캐싱 적용을 위한 @EnableCaching 적용
- Spring boot를 실행하는 상위 클래스에 @EnableCaching 어노테이션을 적용해야 합니다.
해당 어노테이션을 적용하면 Spring에서 Cache에 관련한 어노테이션을 스캔하여 캐싱 적용을 진행합니다.
- Spring boot를 실행하는 상위 클래스에 @EnableCaching 어노테이션을 적용해야 합니다.
- SpringCacheManager에 RedisCacheManager 주입
- redisTemplate을 json 형식으로 데이터를 받을 때 값이 일정하도록 직렬화 합니다.
저장할 클래스가 여러 개일 경우 범용적으로 저장할 수 있게 GenericJackson2JsonRedisSerializer를 이용합니다. - RedisCacheManager는 SpringFramework의 CacheManager를 구현합니다.
Spring에서는 RedisCacheManager를 Bean으로 등록하면 기본 CacheManager를 RedisCacheManager로 사용합니다.
- redisTemplate을 json 형식으로 데이터를 받을 때 값이 일정하도록 직렬화 합니다.
- 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가 들고 데이터를 처리하므로 조회 성능이 향상되었습니다.
- used-market-server 중고거래 프로젝트 코드는 아래에서 확인하실 수 있습니다.
- [ 참고 ]
- cache 사용법: coding-start.tistory.com/271
- spring-boot에서 cache 사용: yonguri.tistory.com/82
- 캐시 사용법: jeong-pro.tistory.com/170
- Caching: http://dveamer.github.io/backend/SpringCacheable.html
- ApectJ: http://dveamer.github.io/java/SpringAsyncAspectJ.html
'used-market-server Project' 카테고리의 다른 글
Log4j2 Logback Log4j 차이 및 적용 (0) | 2020.09.09 |
---|---|
젠킨스 CI 적용하기 (ubuntu linux 18.04) (0) | 2020.09.09 |
Junit을 이용한 테스트 코드 작성하기 (0) | 2020.09.09 |
로그인 상태 정보인 세션 객체를 분산 서버환경에서 트러블 슈팅 (0) | 2020.09.02 |
Tomcat, Jetty 에서 세션 클러스터링 방법 (0) | 2020.08.11 |