[발 번역] Redis Sharding at Craigslist

해당 글은 Redis Sharding at Craigslist( http://blog.zawodny.com/2011/02/26/redis-sharding-at-craigslist/ ) 글을 발번역 한 것입니다. 최근에 올린 Staircar: Redis Powered Notification(https://charsyam.wordpress.com/2012/03/20/%EB%B0%9C-%EB%B2%88%EC%97%AD-staircar-redis-powered-notifications/) 에서 발견한 Redis Presharding 라는 글을 읽고 craigslist 의 개발자가 쓴 글입니다. 오역에 주의하시기 바랍니다.(번역하고 나니 -_- 이번 번역은 정말 발번역이네요 T.T 용어 선택과 이해의 어려움이 T.T)

Redis Sharding at Craigslist

Salvatore의 Redis Presharding 이라는 글을 읽은 후에, Craigslist 에서 다중 코어, 다중 호스트, 장애 허용을 처리해주는 샤딩 방법에 대해서 글을 써야되겠다라는 생각이 들었다.

Nodes

Redis 인프라스트럭처의 첫번째 부분은 host:port 의 형태로 구성된 하나의 싱글 Redis Server 노드이다. 각 노드는 호스트나 port등으로 묶이지 않은 유일한 심볼릭 노드 이름을 가지고 있다. 노드의 매핑을 위한 노드 이름은 Redis API를 사용하는 글로벌 설정 파일 안에 저장되어 있다.

Replication

예측 가능한 적은 지연시간을 선호하고, Redis 에  주로 짧은 시간만 데이터를 저장해두면 되어서, 성능 저하를 일으키는 디스크 저장을 사용하지 않습니다. 일반적으로 요청에 1ms 나 그 이하에 완료됩니다. 예외 상황을 처리하기 위해서 많은 요청의 타임아웃은 100ms 로 정해두고 있습니다. 잦은 디스크 쓰기를 피하기 위해서, 모든 노드는 물리적으로 다른 호스트에 동일하게 설정된 Slave 노드로 복제합니다.

Master/Slave 매핑은 Redis API를 사용하는 글로벌 설정 파일안에 저장되어 있습니다.

Configuration

현재 펄을 이용하고 있기 때문에, 설정 파일은 쉽게 사용하기 위해서 펄로 작성되었습니다. 2개의 호스트와 각각 2 개의 코어를 사용하는 최소한의 예제가 있습니다. 총 4개의 Redis 노드를 제공하고, 두 개의 마스터와 두 개의 슬레이브로 구성되어 있습니다. 서버 한대가 죽는다고 하더라도 데이터의 유실이 없을 것입니다.

our $redisConfig = {
    # node names
    'nodes' => {
        redis_1 => { address => '192.168.1.100:63790' },
        redis_2 => { address => '192.168.1.100:63791' },
        redis_3 => { address => '192.168.1.101:63790' },
        redis_4 => { address => '192.168.1.101:63791' },
    },

    # replication information
    'master_of' => {
        '192.168.1.100:63792' => '192.168.1.101:63790',
        '192.168.1.100:63793' => '192.168.1.101:63791',
        '192.168.1.101:63792' => '192.168.1.100:63790',
        '192.168.1.101:63793' => '192.168.1.100:63791',
    },
}

실제로, 클러스터는 4개의 core를 가지는 10개의 물리 호스트로 구성되어 있어서 40개의 Redis 노드를 제공할 수 있습니다.( 20개의 마스터와 20개의 슬레이브)

Hashing

해싱 키를 노드에 저장하는 우리의 API는 조금 다른 접근법을 취합니다. 일반적인 consistent hashing 을 제공하는 CPAN Redis library 와 동일한 인터페이스를 가지는 래핑 라이브러리를 만들었습니다. 그 외에 Connection Timeouts, Command Timeouts, 다운된 서버의 안정적인 제어라든지, 재시도 등등의 기능이 추가되었습니다.

대부분의 명령은 key의 해싱이 자동적으로 이루어집니다.

$redis->get("foo");

자동적으로 foo 라는 이름을 해시 펑션을 통해서 노드 이름(host:port 형태의) 과 매핑시킵니다. 이것이 매우 중요한 부분입니다. 노드(host:port 형태의)에 바로  접근하는 대신에, 노드 이름의 매핑을 이용하면, Consistent Hashing Ring의 재분배 없이도 자유롭게 노드를 재분배 할 수 있습니다.

연관된 키가 같은 서버에 저장될 수 있도록, 유저 별 해시키를 할당할 수 있습니다. 배열 형태로 데이터를 넘긴다면, 첫번째 엘리먼트는 해시 키이고 두번 째는 실제 키입니다.

$redis->get(["userinfo", "foo"]);

“userinfo” 가 해시 키이고 “foo” 는 userinfo라는 redis node에서 가져올 key의 이릅입니다.

Room to Grow

주어진 설정에서, Redis 클러스터의 용량이 2배로 커져야한다면(아까전의 최소 설정에서) 키의 리해싱에 대해서 고민을 해야합니다. 첫번째로,  서버에서 이용 가능한 메모리를 2배로 늘리고, 최대 메모리 설정을 올린다거나, 두번째로, 호스트의 수를 두배로 늘리고, 절반의 서버를 새로운 호스트로 이동하는 것입니다.  새로운 노드(host:port 형식)와의 매핑을 설정 파일에서 업데이트만 하면, Consistent Hashing Ring에는 영향을 주지 않고, 기대했던 대로그대로 동작한다.(설정을 바꾸는 동안의 어쩔수 없는 다운타임은 제외하고)

Real Numbers

앞에서 말했듯이, 현재 10대의 호스트에 각각 4대의 redis 노드(2 마스터/2 슬레이브)를 사용한다. 실제 32GB 장비에서 돌아가고 있고, 메모리 Fragmentation이나 가끔씩 수행하는 SAVE/BGSAVE  또는 slave 로 붙여지는 작업등을 위해서 각 노드는 6GB가 최대 메모리로 설정되어 있습니다. (역자 주: 보통 메모리가 12G가 있다면 OS가 사용하는 영역이나, 다른 배치작업이나 프로세스가 메모리를 사용하므로, 6~70% 수준으로 사용하는 것이 좋습니다. OS도 최소 3GB 이상의 여유 공간이 남도록 설정해야 스왑으로 인한 속도저하가 줄어듭니다.)  이로 인해 용량은 ( 4 * 10 * 6 ) / 2 = 120GB로 계산되고, 리플리케이션으로 되므로 총 240GB의 메모리를 사용하게 됩니다.

만약 10대의 32GB 장비를 20대의 64GB 장비로 변경하고, 40대의 노드를 유지한다고 한다면, 장비당 2대의 노드만 가지게 됩니다. 이것은 즉 한 노드당 최대 메모리 셋팅을 24GB 로 사용 가능하고 (2 * 20 * 24)/2 = 480GB의 메모리를 사용하게 됩니다.(실제로는 960GB). 키의 리해싱이 필요 없습니다.

만약 한번의 리해싱이 발생한다면, Redis 노드의 수를 두 배로 늘릴 수 있습니다. ( 모든 코어를 사용하기 위해서 ), 하나의 장비 마다 12GB로 설정한 4대의 노드를 실행합니다. ( 4 * 20 * 12 ) / 2 = 480GB의 메모리를 사용합니다.(실제로는 960GB ) (역자 주: 장비가 쿼드 코어 머신이고, 레디스는 보통 싱글 스레드로 동작하기 때문에, 하나의 장비당 4개의 노드를 실행시키는 것으로 보입니다. ) 저장 용량은 동일합니다. 다만 CPU 파워를 두배로 이용할 수 있고, 나중에 다시 장비를 두배로 늘리기가 쉽습니다.( 아까 설명했듯이 호스트를 다시 2배로 늘립니다. )

Future Idea: Slave Reads

하고싶은 것 중에 하나는 놀고 있는 Slave가 비싼 “Read”를 처리하는 것입니다. 다음과 같은 “slave_” 형태로 시작하는 콜을 만들어서 처리할 수 있을 듯 합니다.

$redis->slave_zrevrange($key, $start, $stop, 'withscores');

AUTOLOAD 함수는 slave_ prefix만 잘라내서 래핑해서 주어진 키를 슬레이브로 명령을 보냅니다. 이론적으로는 마스터가 응답을 할 수 없거나 장애가 났을 때 자동적으로 읽기를 슬레이브 노드로 보낼 수 있습니다.(아마도 쓰기는 안되겠지만)

Future Idea: Async Operations

블로킹 되는 Redis 클라이언트 대신에  AnyEvent::Redis를 사용하는 래핑 API를 만들었기 때문에  Redis를 비동기적으로 사용이 가능합니다. 자주 호출되는 데이터를 가져오는( 서로 연관성이 없는)  10~50개의 Redis Call 을 가지고 있기 때문에, 이것을 비동기  API로 바꾼다면 꽤 성능이 증가할 것으로 보입니다.

 

좀디 지지고 볶는다면( 연마하고, 정리하고, 테스팅 하면 ) 병렬로 요청하는 것의 장점을 볼 수 있을듯 합니다.

 

Future Idea: Redis Cluster

redis 를 호출하는 API 를 사용하는 코드가 동일하기 때문에, 언젠가 Redis Cluster 로 이동한다면, 지금 사용하는 형태의 해시 키를 redis-cluster 에서 사용하는 {hashkey} 스타일로 바꾸는 것은 매우 쉬울 것입니다. 그렇게 되면 노드를 추가 하는등의 관리 오버헤드가 줄어들고 우리에게 더 많은 융통성을 제공해줄 것입니다.