추가적인 요청 부하 증가(요청 스레드 3배 증가)시 DB에서 병목 발생
- Article 조회 기준
1. 240TPS
2. 응답 시간: 5.5s- Article 참여, 취소 기준
1. 22TPS
2. 응답 시간: 5.5s
RDBMS sharding이 힘든이유
1. 구현의 복잡성
2. 일단, Sharding을 할 경우 UnSharding으로 되돌리는 것이 힘들다.
3. 해싱 알고리즘에 따라 특정 shard로 몰릴 수 있다.
4. 다른 shard에 있는 node를 같이 조회해야하는 쿼리는 성능이 안 좋을 수 밖에 없다.
캐시를 통해 조회 부하를 분산하여 해결하기로 결정하였음.
이 과정에서 캐시 전략및 트레이드오프를 고려하여 적절한 선택을 하였음.
https://www.imaginarycloud.com/blog/redis-vs-memcached/
DB에만 쓰고 Cache에는 쓰지 않는 전략을 선택.
Article 목록으로 받아들여온 경우 데이터 특성상 write-through를 통해 DB와 데이터 정합성을 맞추기 위해서 큰 비용이 발생함.
TTL을 통해 적절한 만료시간을 주어 잘못된 데이터가 사용자에게 오래 조회 되지 않도록 함.
사용자는 Article 목록 -> Article 단건 상세 -> Article 참여, 취소 등으로 대부분 행동할 것이기 때문에 목록에서 잘못된 데이터가 잠시 보여도 단건에서는 실제 데이터가 보이게 할 수 있음.
사용자들이 요청하는 목록이 가장 자주 요청되는 경우 20%가 전체 요청의 80에 해당한다고 가정하였다. 이를 위해 Gaussian Distribution을 활용하여 PageNumber의 값을 정하도록 Jmeter를 설정하였다.
평균 50, 표준편차 8인 경우 40 ~ 60이 나올 경우가 80%가 된다.
cache를 제외한 나머지 조건은 cache가 없던 이전 포스트 상황과 동일.
https://velog.io/@xogml951/Scalability-Test-Thread-Pool-DBCP-%EC%A0%81%EC%A0%95-%EC%84%A4%EC%A0%95%EA%B0%92-%EC%B0%BE%EA%B8%B0
private static final Integer DEFAULT_EXPIRE_SECOND = 60 * 5;
private static final String ARTICLES_CACHE_NAME = "articles";
private static final Integer ARTICLES_EXPIRE_SECOND = 5;
/**
* Redis Cache를 사용하기 위한 cache manager 등록.<br>
* 커스텀 설정을 적용하기 위해 RedisCacheConfiguration을 먼저 생성한다.<br>
* 이후 RadisCacheManager를 생성할 때 cacheDefaults의 인자로 configuration을 주면 해당 설정이 적용된다.<br>
* RedisCacheConfiguration 설정<br>
* disableCachingNullValues - null값이 캐싱될 수 없도록 설정한다. null값 캐싱이 시도될 경우 에러를 발생시킨다.<br>
* entryTtl - 캐시의 TTL(Time To Live)를 설정한다. Duraction class로 설정할 수 있다.<br>
* serializeKeysWith - 캐시 Key를 직렬화-역직렬화 하는데 사용하는 Pair를 지정한다.<br>
* serializeValuesWith - 캐시 Value를 직렬화-역직렬화 하는데 사용하는 Pair를 지정한다. -> 가시성이 중요하지 않기 때문에 JDKSerializer 사용<br>
* Value는 다양한 자료구조가 올 수 있기 때문에 GenericJackson2JsonRedisSerializer를 사용한다.
*
* @param redisConnectionFactory Redis와의 연결을 담당한다.
* @return
*/
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory,
ObjectMapper objectMapper) {
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
.disableCachingNullValues()
.entryTtl(Duration.ofSeconds(DEFAULT_EXPIRE_SECOND))
.serializeKeysWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()));
HashMap<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();
cacheConfigurations.put(ARTICLES_CACHE_NAME, RedisCacheConfiguration.defaultCacheConfig()
.disableCachingNullValues()
.entryTtl(Duration.ofSeconds(ARTICLES_EXPIRE_SECOND))
.serializeKeysWith(
RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer())));
return RedisCacheManager.RedisCacheManagerBuilder
.fromConnectionFactory(redisConnectionFactory)
.cacheDefaults(configuration)
.withInitialCacheConfigurations(cacheConfigurations)
.build();
}
@Cacheable(value = "articles", key = "#pageable.pageNumber + '-' + #pageable.pageSize + '-' + #pageable.sort"
+ " + '-' + #condition.anonymity + '-' + #condition.contentCategory +'-'+ #condition.isComplete")
public SliceImpl<ArticleReadResponse>readAllArticle(Pageable pageable,
ArticleSearch condition) {
Slice<Article> articleSlices = articleRepository.findSliceByCondition(pageable,
condition);
https://www.alibabacloud.com/tech-news/redis/1qi-how-to-set-eviction-policy-in-redis
CONFIG SET maxmemory-policy allkeys-lru
AWS Elasticache 에서는 CONFIG 명령이 제한된다. 따라서 AWS Console을 통해 설정해야한다.
수정이 발생할때 기존처럼 DB에만 쓰고 Redis캐시 내용은 수정하지 않으면 Write-Around를 만족한다.
부하 테스트 조건은 4의 내용과(pageNumber정규분포 조회)와 이전 포스트 내용 조건과 동일
article 조회 api
article 참여, 취소 api
Article 조회 기준
1. 240 -> 600TPS 2.5배 상승
2. 응답 시간: 5.5s -> 2.5sArticle 참여, 취소 기준
1. 22 -> 48TPS 2.2배 상승
2. 응답 시간: 5.5s -> 0.8sRDS CPU사용률
100% -> 23%