#Cache #Redis
카카오 계정에서 캐시를 무중단으로 전환한 사례를 공유합니다.
발표자 : leo.kkh 카카오 계정 에서 서버 개발자로 일하고 있는 레오입니다.
য় ҅ நद ജӝ김경호 Leo.kkh카카오Copyright 2022. Kakao Corp. All rights reserved. Redistribution or public display is not permitted without written permission from Kakao.if(kakao)2022
View Slide
카카오 계정 소개캐시(Cache)캐시 시스템 전환 배경캐시 시스템 전환Issue
카카오 계정 소개profile연락처배송지계정 보안
캐시(Cache)Server Cache DB50 만 TPS 1 만 TPS
캐시 시스템 전환 배경
Cent OS 6 EOL- 2020 년 11월 Cent OS 6 EOL (End Of Lifetime)- 보안 이슈로 EOL 서버는 OS 버전업 필요Cache이전 대상캐시 서버 재구축 필요 !
기존 캐시 시스템 (Arcus)- memcached + Zookeeper 로 구성된 캐시 클러스터- Zookeeper 를 이용한 노드 관리- consistent hashing 이용한 데이터 분산- memcached 지원하지 않는 자료구조 지원 (Collection)- Jam2in 에서 제공하는 오픈 소스Server memcachedZookeeper
Arcus 를 유지해야 할까?- Arcus 에 대한 학습 부족- 인프라 파트 기술 지원 어려움- 노드 증설 or 클러스터 재구축이 필요하다면 ?- Arcus 에 이슈가 생기면 ?
Arcus 를 유지해야 할까?- Arcus Java Client- Arcus 에 대한 팀내 학습 부족- 노드 증설 or 클러스터 재구축이 필요하다면 ?- 인프라 파트 기술 지원 어려움- Arcus 에 이슈가 생기면 ?클러스터 재구축 ?
캐시 시스템 전환
ArcusRedisCluster장비 수급 문제 & 일정 이슈
RHA (Redis High Availability)- 사내 지원하는 Redis HA 서비스- 도메인 기반 HA 지원 (Redis Sentinel X)- 캐시, persistent storage, pub/sub 사용 가능- sharding 미지원50만 TPSsharding 필요 !!
Client-side Server-side애플리케이션 데이터 스토어Sharding
Client-side ShardingServerCachefun( key )
Client-side ShardingServerCachefun( key ) Zookeeper ?
Jedis- java 기반의 redis client 라이브러리- 다른 라이브러리에 비해 가벼움- 사용하기 쉬움- thread safe 하지 않아서 connection 공유 불가- multi thread 환경에서 connection pool 필요- 이미 파트내에서 사용중- client-side 샤딩 지원 (ShardedJedis)
Sharded Jedisconsistent hashingkeymurmur hashhash function노드 추가 / 삭제 시Rebalancing 데이터 최소화
MultiKey 문제
Multi key 문제- ShardedJedis 는 single key operation 만 지원- 하지만 계정에서는 bulk API 를 이미 제공중 ...- single key 기반으로 매번 redis 조회는 불가능
Multi key 문제- ShardedJedis 는 single key operation 만 지원- 하지만 계정에서는 bulk API 를 이미 제공중 ...- single key 기반으로 매번 redis 조회는 불가능ShardedJedis 는 key 를 기준으로 노드를 선정자체 구현하자 !
ShardedJedis 분석public class BinaryShardedJedis extends Sharded {public byte[] get(final byte[] key) {Jedis j = getShard(key);return j.get(key);}}public class BinaryJedis {public byte[] get(final byte[] key) {checkIsInMultiOrPipeline();client.get(key);return client.getBinaryBulkReply();}}단일 Redis ClientgetShard 를 통해 Redis Client 조회
public class Sharded> {// ࢤࢿprivate void initialize(List shards)// ֢٘ ӝpublic S getShardInfo(byte[] key)// ֢٘ റ ܻࣗझ ߈ജpublic R getShard(byte[] key)}ShardedJedis 분석
private void initialize(List shards) {resources = new LinkedHashMap, R>();nodes = new TreeMap();for (int i = 0; i != shards.size(); ++i) {final S shardInfo = shards.get(i);int N = 160 * shardInfo.getWeight();for (int n = 0; n < N; n++) {nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);}resources.put(shardInfo, shardInfo.createResource());}}ShardedJedis 분석
private void initialize(List shards) {resources = new LinkedHashMap, R>();nodes = new TreeMap();for (int i = 0; i != shards.size(); ++i) {final S shardInfo = shards.get(i);int N = 160 * shardInfo.getWeight();for (int n = 0; n < N; n++) {nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);}resources.put(shardInfo, shardInfo.createResource());}}ShardedJedis 분석가상 노드 수
ShardedJedis 분석public S getShardInfo(byte[] key) {SortedMap tail = nodes.tailMap(algo.hash(key));if (tail.isEmpty()) {return nodes.get(nodes.firstKey());}return tail.get(tail.firstKey());}가장 가까운 노드
ShardedJedis 분석public S getShardInfo(byte[] key) {SortedMap tail = nodes.tailMap(algo.hash(key));if (tail.isEmpty()) {return nodes.get(nodes.firstKey());}return tail.get(tail.firstKey());}public R getShard(byte[] key) {return resources.get(getShardInfo(key));}getShard 를 이용?
MultiGet 구현public Map multiGet(List keys) {Map> hostMap = keys.stream().collect(Collectors.groupingBy(this::getShardHost));for (List keys: hostMap.values()) {try (ShardedJedis shardedJedis = jedisPool.getResource()) {Jedis jedis = shardedJedis.getShard(serializeKey(keys.get(0))));byte[][] rawKeys = serializeKeys(keys);List values = jedis.mget(rawKeys);// .. ۚ}}}단일 노드 요청 처럼 사용node 별로 grouping
MultiGet 구현Jedis PoolRedisApplicationgetrelease
MultiGet 구현public Map multiGet(List keys) {Map> hostMap = keys.stream().collect(Collectors.groupingBy(this::getShardHost));...}private String getShardHost(String key) {try (ShardedJedis shardedJedis = jedisPool.getResource()) {return shardedJedis.getShardInfo(key).getHost();}}node host 정보 추출connection get / release 반복node 별로 grouping
nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo);MultiGet 구현가상 노드List shardInfos = jedisShardInfoList.stream().map(jedisShardInfo -> {KAShardInfo shardInfo = new KAShardInfo();shardInfo.setHost(jedisShardInfo.getHost());return shardInfo;}).collect(Collectors.toList());sharded = new Sharded<>(shardInfos, Hashing.MURMUR_HASH, null);private String getShardHost(byte[] key) {return sharded.getShard(key);}
트래픽 전환
요구 조건1. 무중단 전환2. Rollback3. 점진적 전환
무중단 전환Account
ServerArcusRedisRedis 전환
ServerArcusRedismissRedis 전환
캐시 데이터 복제ServerArcusRedisgetset
Redis 전환ServerArcusRedis
Redis 전환 후 rollbackServerArcusRedisA=1A=3
ServerArcusRedisgetsetRedis 전환
Arcus 제거모니터링 이후 Arcus 제거
점진적 전환ServersArcusRedis50 만 TPS
ServersArcusRedisCanary Release
Issue
Jedis Connection Errorredis.clients.jedis.exceptions.JedisException: Could not get aresource from the poolconnection 부족 ?pool size 늘리자며칠 뒤 ...redis.clients.jedis.exceptions.JedisException: Could not get aresource from the pool
JMX(Java Management Extensions)JedisPoolConfig poolConfig = new JedisPoolConfig();poolConfig.setJmxNamePrefix(jmxName);Jedis MBean Name 추가- org.apache.commons:commons-pool2 은 thread pool 을 JMX MBean 에 추가- MBean(Management Bean) 이름을 설정하지 않으면 기본 값- 애플리케이션에서 여러 thread pool 이용할 경우 모니터링 어려움
Jedis Connection Monitoringjedis active connectionconnection leak ?
Jedis Connection Leak- connection pool 에서 connection 얻을때 동시성 이슈- 동시성 이슈로 connection 이 pool 에 반환되지 않음- Jedis 3.1.0 에서 SharedJedis Fix
마치며 ...계정 플랫폼 서버 개발자 모집https://careers.kakao.com/jobs/P-12224
참고 문헌1) jam2in, Arcus, https://www.jam2in.com/2) github, Arcus, https://github.com/naver/arcus3) github, jedis, https://github.com/redis/jedis4) github, "ShardedJedis does not have mget?", https://github.com/redis/jedis/issues/1495) github, "JedisPool exhausted is Jedis 2.10.0", https://github.com/redis/jedis/issues/1920