Upgrade to Pro — share decks privately, control downloads, hide ads and more …

카카오 계정 캐시 전환기

kakao
December 09, 2022

카카오 계정 캐시 전환기

#Cache #Redis

카카오 계정에서 캐시를 무중단으로 전환한 사례를 공유합니다.

발표자 : leo.kkh
카카오 계정 에서 서버 개발자로 일하고 있는 레오입니다.

kakao

December 09, 2022
Tweet

More Decks by kakao

Other Decks in Programming

Transcript

  1. ஠஠য় ҅੿ நद ੹ജӝ 김경호 Leo.kkh 카카오 Copyright 2022. Kakao

    Corp. All rights reserved. Redistribution or public display is not permitted without written permission from Kakao. if(kakao)2022
  2. Cent OS 6 EOL - 2020 년 11월 Cent OS

    6 EOL (End Of Lifetime) - 보안 이슈로 EOL 서버는 OS 버전업 필요 Cache 이전 대상 캐시 서버 재구축 필요 !
  3. 기존 캐시 시스템 (Arcus) - memcached + Zookeeper 로 구성된

    캐시 클러스터 - Zookeeper 를 이용한 노드 관리 - consistent hashing 이용한 데이터 분산 - memcached 지원하지 않는 자료구조 지원 (Collection) - Jam2in 에서 제공하는 오픈 소스 Server memcached Zookeeper
  4. Arcus 를 유지해야 할까? - Arcus 에 대한 학습 부족

    - 인프라 파트 기술 지원 어려움 - 노드 증설 or 클러스터 재구축이 필요하다면 ? - Arcus 에 이슈가 생기면 ?
  5. Arcus 를 유지해야 할까? - Arcus Java Client - Arcus

    에 대한 팀내 학습 부족 - 노드 증설 or 클러스터 재구축이 필요하다면 ? - 인프라 파트 기술 지원 어려움 - Arcus 에 이슈가 생기면 ? 클러스터 재구축 ?
  6. RHA (Redis High Availability) - 사내 지원하는 Redis HA 서비스

    - 도메인 기반 HA 지원 (Redis Sentinel X) - 캐시, persistent storage, pub/sub 사용 가능 - sharding 미지원 50만 TPS sharding 필요 !!
  7. Jedis - java 기반의 redis client 라이브러리 - 다른 라이브러리에

    비해 가벼움 - 사용하기 쉬움 - thread safe 하지 않아서 connection 공유 불가 - multi thread 환경에서 connection pool 필요 - 이미 파트내에서 사용중 - client - side 샤딩 지원 (ShardedJedis)
  8. Sharded Jedis consistent hashing key murmur hash hash function 노드

    추가 / 삭제 시 Rebalancing 데이터 최소화
  9. Multi key 문제 - ShardedJedis 는 single key operation 만

    지원 - 하지만 계정에서는 bulk API 를 이미 제공중 ... - single key 기반으로 매번 redis 조회는 불가능
  10. Multi key 문제 - ShardedJedis 는 single key operation 만

    지원 - 하지만 계정에서는 bulk API 를 이미 제공중 ... - single key 기반으로 매번 redis 조회는 불가능 ShardedJedis 는 key 를 기준으로 노드를 선정 자체 구현하자 !
  11. ShardedJedis 분석 public class BinaryShardedJedis extends Sharded<Jedis, JedisShardInfo> { 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 Client getShard 를 통해 Redis Client 조회
  12. public class Sharded<R, S extends ShardInfo<R>> { // ࢤࢿ੗ private

    void initialize(List<S> shards) // ֢٘ ଺ӝ public S getShardInfo(byte[] key) // ֢٘ ଺਷ റ ܻࣗझ ߈ജ public R getShard(byte[] key) } ShardedJedis 분석
  13. private void initialize(List<S> shards) { resources = new LinkedHashMap<ShardInfo<R>, R>();

    nodes = new TreeMap<Long, S>(); 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 분석
  14. private void initialize(List<S> shards) { resources = new LinkedHashMap<ShardInfo<R>, R>();

    nodes = new TreeMap<Long, S>(); 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 분석 가상 노드 수
  15. ShardedJedis 분석 public S getShardInfo(byte[] key) { SortedMap<Long, S> tail

    = nodes.tailMap(algo.hash(key)); if (tail.isEmpty()) { return nodes.get(nodes.firstKey()); } return tail.get(tail.firstKey()); } 가장 가까운 노드
  16. ShardedJedis 분석 public S getShardInfo(byte[] key) { SortedMap<Long, S> 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 를 이용?
  17. MultiGet 구현 public Map<String, Object> multiGet(List<String> keys) { Map<String, List<String>>

    hostMap = keys.stream() .collect(Collectors.groupingBy(this::getShardHost)); for (List<String> keys: hostMap.values()) { try (ShardedJedis shardedJedis = jedisPool.getResource()) { Jedis jedis = shardedJedis.getShard(serializeKey(keys.get(0)))); byte[][] rawKeys = serializeKeys(keys); List<byte[]> values = jedis.mget(rawKeys); // .. ઺ۚ } } } 단일 노드 요청 처럼 사용 node 별로 grouping
  18. MultiGet 구현 public Map<String, Object> multiGet(List<String> keys) { Map<String, List<String>>

    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
  19. nodes.put(this.algo.hash("SHARD-" + i + "-NODE-" + n), shardInfo); MultiGet 구현

    가상 노드 List<KAShardInfo> 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); }
  20. Jedis Connection Error redis.clients.jedis.exceptions.Je disException: Could not get a resource

    from the pool connection 부족 ? pool size 늘리자 며칠 뒤 ... redis.clients.jedis.exceptions.Je disException: Could not get a resource from the pool
  21. 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 이용할 경우 모니터링 어려움
  22. Jedis Connection Leak - connection pool 에서 connection 얻을때 동시성

    이슈 - 동시성 이슈로 connection 이 pool 에 반환되지 않음 - Jedis 3.1.0 에서 SharedJedis Fix
  23. 참고 문헌 1) jam2in, Arcus, https:/ /www.jam2in.com/ 2) github, Arcus,

    https:/ /github.com/naver/arcus 3) github, jedis, https:/ /github.com/redis/jedis 4) github, "ShardedJedis does not have mget?", https:/ /github.com/redis/jedis/issues/149 5) github, "JedisPool exhausted is Jedis 2.10.0", https:/ /github.com/redis/jedis/issues/1920