NDM

시스템 디자인 재설계로 상품 조회 api를 개선해보자 본문

[프로젝트] 타임딜 API 서버

시스템 디자인 재설계로 상품 조회 api를 개선해보자

ndm.jr 2023. 6. 4. 12:06

기존의 제 이벤트 상품 조회는 다음과 같이 이루어집니다.

 

 

첫 번째로 DB를 조회해서 Cache DB에 적재하고,

그 이후부터 Cache DB에서 데이터를 가져오는 방식입니다.

 

# 왜 이벤트 상품을 캐싱으로 처리하려고 했나 ?

  1. 이벤트 상품에 대한 수정(상품 수정, 이벤트 상품 등록/취소) 작업이 일어나는 횟수가 적을 것으로 예상했고
  2. 타임딜 이벤트 상품들을 찾아보니 사이트마다 달랐지만 한번에 이벤트에 등록되는 상품 수가 많지 않았습니다.

 

# 그럼 무엇이 잘못되었음을 깨닫고 시스템 디자인을 바꾸었는가?

  1. Cache DB를 사용하면 분명 키가 만료되는 순간이 존재한다. 그때마다 RDB를 다시 뒤져야한다.
  2. RDB를 다시 뒤질때마다 부하가 급증할 것이고, 성능도 보장할 수 없다.
  3. 지금은 모놀리식이지만, 마이크로서비스로 변경했을 경우 더 큰 문제가 발생한다.

 

만약 마이크로서비스로 바꾼다면

 

#  캐싱을 적용했는데 다음과 같은 상황이 발생한다면 어떨까?

  1. 캐시의 키가 만료되었다.
  2. 상품이 품절되었을 때 품절처리를 하는것이 아니라 리스트에서 내려야한다.
  3. 기획팀의 요청으로 불가피하게 이벤트 상품이 바뀌어야 한다. (제 프로젝트에서는 이벤트 상품 변경은 불가능합니다.)

 

또 상품부터 시작해서 모든 도메인에 요청을 보내고,

각각의 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

 

프로젝트에 옮기고 나서의 코드 변화는 추후 수정하겠습니다.

 

# 출처

 

https://dev.gmarket.com/32

 

MySQL만 써봤는데... MongoDB 프로젝트에 투입됐다🤯

빅데이터라는 시대의 요구에 맞추어 NoSQL이 등장한 지 십 년이 넘는 세월이 흘렀습니다. 하지만, 아직 RDB에 비해서 스키마 설계를 위한 참고 자료가 부족하다고 생각되는데요. 저 또한 MySQL만 사

dev.gmarket.com