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

카카오 계정 캐시 전환기

kakao
PRO
December 09, 2022

카카오 계정 캐시 전환기

#Cache #Redis

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

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

kakao
PRO

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

    View Slide

  2. 카카오 계정 소개


    캐시(Cache)


    캐시 시스템 전환 배경


    캐시 시스템 전환


    Issue

    View Slide

  3. 카카오 계정 소개
    pro
    fi
    le
    연락처
    배송지
    계정 보안

    View Slide

  4. 캐시(Cache)
    Server Cache DB
    50 만 TPS 1 만 TPS

    View Slide

  5. 캐시 시스템 전환 배경

    View Slide

  6. Cent OS 6 EOL
    - 2020 년 11월 Cent OS 6 EOL (End Of Lifetime)


    - 보안 이슈로 EOL 서버는 OS 버전업 필요
    Cache
    이전 대상
    캐시 서버 재구축 필요 !

    View Slide

  7. 기존 캐시 시스템 (Arcus)
    - memcached + Zookeeper 로 구성된 캐시 클러스터


    - Zookeeper 를 이용한 노드 관리


    - consistent hashing 이용한 데이터 분산


    - memcached 지원하지 않는 자료구조 지원 (Collection)


    - Jam2in 에서 제공하는 오픈 소스
    Server memcached
    Zookeeper

    View Slide

  8. Arcus 를 유지해야 할까?
    - Arcus 에 대한 학습 부족


    - 인프라 파트 기술 지원 어려움


    - 노드 증설 or 클러스터 재구축이 필요하다면 ?


    - Arcus 에 이슈가 생기면 ?

    View Slide

  9. Arcus 를 유지해야 할까?
    - Arcus Java Client


    - Arcus 에 대한 팀내 학습 부족


    - 노드 증설 or 클러스터 재구축이 필요하다면 ?


    - 인프라 파트 기술 지원 어려움


    - Arcus 에 이슈가 생기면 ?
    클러스터 재구축 ?

    View Slide

  10. 캐시 시스템 전환

    View Slide

  11. Arcus
    Redis


    Cluster
    장비 수급 문제 & 일정 이슈

    View Slide

  12. RHA (Redis High Availability)
    - 사내 지원하는 Redis HA 서비스


    - 도메인 기반 HA 지원 (Redis Sentinel X)


    - 캐시, persistent storage, pub/sub 사용 가능


    - sharding 미지원
    50만 TPS


    sharding 필요 !!

    View Slide

  13. Client
    -
    side Server
    -
    side
    애플리케이션 데이터 스토어
    Sharding

    View Slide

  14. Client
    -
    side Sharding
    Server
    Cache
    fun( key )

    View Slide

  15. Client
    -
    side Sharding
    Server
    Cache
    fun( key ) Zookeeper ?

    View Slide

  16. Jedis
    - java 기반의 redis client 라이브러리


    - 다른 라이브러리에 비해 가벼움


    - 사용하기 쉬움


    - thread safe 하지 않아서 connection 공유 불가


    - multi thread 환경에서 connection pool 필요


    - 이미 파트내에서 사용중


    - client
    -
    side 샤딩 지원 (ShardedJedis)

    View Slide

  17. Sharded Jedis
    consistent hashing
    key
    murmur hash
    hash function
    노드 추가 / 삭제 시


    Rebalancing 데이터 최소화

    View Slide

  18. MultiKey 문제

    View Slide

  19. Multi key 문제
    - ShardedJedis 는 single key operation 만 지원


    - 하지만 계정에서는 bulk API 를 이미 제공중 ...


    - single key 기반으로 매번 redis 조회는 불가능

    View Slide

  20. Multi key 문제
    - ShardedJedis 는 single key operation 만 지원


    - 하지만 계정에서는 bulk API 를 이미 제공중 ...


    - single key 기반으로 매번 redis 조회는 불가능
    ShardedJedis 는 key 를 기준으로 노드를 선정


    자체 구현하자 !

    View Slide

  21. 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 Client
    getShard 를 통해 Redis Client 조회

    View Slide

  22. public class Sharded> {


    // ࢤࢿ੗


    private void initialize(List shards)


    // ֢٘ ଺ӝ


    public S getShardInfo(byte[] key)


    // ֢٘ ଺਷ റ ܻࣗझ ߈ജ


    public R getShard(byte[] key)


    }


    ShardedJedis 분석

    View Slide

  23. 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 분석

    View Slide

  24. 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 분석
    가상 노드 수

    View Slide

  25. 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());


    }
    가장 가까운 노드

    View Slide

  26. 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 를 이용?

    View Slide

  27. 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

    View Slide

  28. MultiGet 구현
    Jedis Pool
    Redis
    Application
    get
    release

    View Slide

  29. 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

    View Slide

  30. 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);


    }

    View Slide

  31. 트래픽 전환

    View Slide

  32. 요구 조건
    1. 무중단 전환


    2. Rollback


    3. 점진적 전환

    View Slide

  33. 요구 조건
    1. 무중단 전환


    2. Rollback


    3. 점진적 전환

    View Slide

  34. 무중단 전환
    Account

    View Slide

  35. Server
    Arcus
    Redis
    Redis 전환

    View Slide

  36. Server
    Arcus
    Redis
    miss
    Redis 전환

    View Slide

  37. 캐시 데이터 복제
    Server
    Arcus
    Redis
    get
    set

    View Slide

  38. Redis 전환
    Server
    Arcus
    Redis

    View Slide

  39. 요구 조건
    1. 무중단 전환


    2. Rollback


    3. 점진적 전환

    View Slide

  40. Redis 전환 후 rollback
    Server
    Arcus
    Redis
    A=1
    A=3

    View Slide

  41. Server
    Arcus
    Redis
    get
    set
    Redis 전환

    View Slide

  42. Arcus 제거
    모니터링 이후 Arcus 제거

    View Slide

  43. 요구 조건
    1. 무중단 전환


    2. Rollback


    3. 점진적 전환

    View Slide

  44. 점진적 전환
    Servers
    Arcus
    Redis
    50 만 TPS

    View Slide

  45. Servers
    Arcus
    Redis
    Canary Release

    View Slide

  46. Issue

    View Slide

  47. 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


    View Slide

  48. 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 이용할 경우 모니터링 어려움

    View Slide

  49. Jedis Connection Monitoring
    jedis active connection
    connection leak ?

    View Slide

  50. Jedis Connection Leak
    - connection pool 에서 connection 얻을때 동시성 이슈


    - 동시성 이슈로 connection 이 pool 에 반환되지 않음


    - Jedis 3.1.0 에서 SharedJedis Fix

    View Slide

  51. 마치며 ...
    계정 플랫폼 서버 개발자 모집


    https:/
    /careers.kakao.com/jobs/P-12224

    View Slide

  52. 참고 문헌
    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

    View Slide