일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 지연로딩
- CaffeineCache
- springboot
- lazyloading
- Jenkins
- method area
- Metaspace
- B+TREE
- JDK14
- 트랜잭션
- 주문
- 공변
- 카카오 화재
- Redis
- 재고 시스템
- ci/cd
- nonclustered index
- java
- 동시성
- 부하테스트
- JAVA8
- GithubActions
- 제네릭
- 상태패턴
- 웹캐시
- backend
- Spring Data Redis
- JPA
- Ehcache
- 리팩터링
- Today
- Total
NDM
시스템 디자인 재설계로 상품 조회 api를 개선해보자 본문
기존의 제 이벤트 상품 조회는 다음과 같이 이루어집니다.
첫 번째로 DB를 조회해서 Cache DB에 적재하고,
그 이후부터 Cache DB에서 데이터를 가져오는 방식입니다.
# 왜 이벤트 상품을 캐싱으로 처리하려고 했나 ?
- 이벤트 상품에 대한 수정(상품 수정, 이벤트 상품 등록/취소) 작업이 일어나는 횟수가 적을 것으로 예상했고
- 타임딜 이벤트 상품들을 찾아보니 사이트마다 달랐지만 한번에 이벤트에 등록되는 상품 수가 많지 않았습니다.
# 그럼 무엇이 잘못되었음을 깨닫고 시스템 디자인을 바꾸었는가?
- Cache DB를 사용하면 분명 키가 만료되는 순간이 존재한다. 그때마다 RDB를 다시 뒤져야한다.
- RDB를 다시 뒤질때마다 부하가 급증할 것이고, 성능도 보장할 수 없다.
- 지금은 모놀리식이지만, 마이크로서비스로 변경했을 경우 더 큰 문제가 발생한다.
# 캐싱을 적용했는데 다음과 같은 상황이 발생한다면 어떨까?
- 캐시의 키가 만료되었다.
- 상품이 품절되었을 때 품절처리를 하는것이 아니라 리스트에서 내려야한다.
- 기획팀의 요청으로 불가피하게 이벤트 상품이 바뀌어야 한다. (제 프로젝트에서는 이벤트 상품 변경은 불가능합니다.)
또 상품부터 시작해서 모든 도메인에 요청을 보내고,
각각의 DB를 다시 뒤져 데이터 포맷을 만들고, 다시 캐시에 담아 뿌려야 합니다.
때문에 단순 캐시를 태우는 것은 해결책이 아니라고 생각했습니다.
## MongoDB
1. 여러 도메인의 데이터로 포맷을 구성해야 하기에 정형화된 데이터 양식보다는 비정형 데이터를 선택
2. 많은 양의 데이터를 데이터 포맷을 신경쓰지 않고 담을 수 있어야 한다. (사실 이벤트 상품 조회나 일반 상품 조회나 같은 로직으로 진행 될 것으로 생각합니다. 이벤트 도메인 데이터를 하나 더 조인해서 가져오느냐 마느냐의 차이기 떄문에..)
## 어떻게 바뀌나
이렇게 바뀔것으로 생각하였습니다. 프로젝트에 옮기고 나서의 코드 변화는 추후 수정하겠습니다.
# 기존에는 ?
기존 로직은 앞서 말씀드렸던 대로 캐싱을 이용하고 있었고, Spring에서는 @Cacheable을 이용해 손쉽게 캐싱을 구현할 수 있습니다. 먼저, @EnableCaching 어노테이션을 붙여줍니다.
@EnableCaching
@SpringBootApplication
public class TimedealApplication {
public static void main(String[] args) {
SpringApplication.run(TimedealApplication.class, args);
}
}
이후, Caching을 어떻게 할 건지 설정해주는 Config 파일을 작성해줘야합니다. 저의 경우는 Redis를 이미 글로벌 캐시 저장소로 사용하고 있었기 때문에 그대로 사용하였습니다.
@Configuration
public class CacheConfig {
private final RedisConnectionFactory redisConnectionFactory;
public CacheConfig(RedisConnectionFactory redisConnectionFactory) {
this.redisConnectionFactory = redisConnectionFactory;
}
@Bean
public CacheManager redisCacheManager() {
RedisCacheConfiguration redisCachingConfiguration = RedisCacheConfiguration
.defaultCacheConfig()
.disableCachingNullValues()
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new StringRedisSerializer()
)
)
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer(objectMapper())
)
)
.entryTtl(Duration.ofHours(3));
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory)
.cacheDefaults(redisCachingConfiguration)
.build();
}
private ObjectMapper objectMapper() {
PolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator
.builder()
.allowIfSubType(Object.class)
.build();
ObjectMapper mapper = new ObjectMapper();
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
mapper.registerModule(new JavaTimeModule());
mapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL);
return mapper;
}
}
이제 다 끝났습니다. 캐싱을 적용할 데이터를 가져오는 메서드로 가서, @Cacheable을 적용해주면 됩니다.
@Cacheable(key = "#eventCode+':'+#pageable.pageNumber",
cacheManager = "redisCacheManager",
value = "eventProduct")
저같은 경우는 [이벤트 코드 + 페이지 넘버] 조합으로 키를 설정하여 구현했었습니다.
# MongoDB
프로젝트에 옮기고 나서의 코드 변화는 추후 수정하겠습니다.
# 출처
'[프로젝트] 타임딜 API 서버' 카테고리의 다른 글
재고 처리 로직 동시성 이슈 해결 일지 2 (0) | 2023.06.05 |
---|---|
상품 조회 api와 주문 api 설계하기 (0) | 2023.06.03 |
Jmeter를 이용한 이벤트 상품 조회 api 부하 테스트와 개선일지 (0) | 2023.06.01 |
6. 프로젝트의 클라우드 아키텍처를 설계해보자 ( feat. Naver Cloud ) (0) | 2023.02.28 |
확장성을 고려한 로그인 세션 관리하기 (0) | 2023.02.27 |