Category: Redis

Redis Technics

[번역]레디스 슬레이브에서 “놓쳐버린” 키들.

이 문서는 다음의 글을 번역한 것입니다.

레디스 슬레이브에서 “놓쳐버린” 키들.

만약 당신이 레디스(Redis)에서 만료중인 키를(expiring key)(“임시적인 키”로 알려진) 사용하고 있다면, 당신은 레디스 마스터에서 새로운 슬레이브(Slave)를 붙였을때에 놀랄 수도 있습니다. 슬레이브에서 키 갯수(key count)가 마스터에서 키 갯수보다 약 25%정도 낮을 것입니다. 이것은 임시적인 키(volatile key)가 아주 많다면 특별하게도 정상적인 것입니다.

Redis Master-Slave

레디스 슬레이브가 키를 놓쳐버린 걸까? 단순하게 데이터를 잃어버린 걸까? 결론부터 말하자면 “아니오” 입니다. 그러나, 왜그런지 이해하길 희망하면서, 레디스 슬레이브가 어떠한 데이터도 잃어버리지도 않았는데도 더 적은 키 갯수를 보고합니다. 이것은 두가지 세세한 구현으로부터 발생됩니다: 어떻게 레디스는 키를 만료시키고 어떻게 레디스 마스터는 새로운 슬레이브에게 데이터셋(dataset)을 보내는지하는 것

어떻게 레디스는 임시적인 키들을 만료시킬까?

레디스는 임시적인 키들(volatile keys)을 만료로 지정한 순간에 메모리로부터 삭제를 하지 않습니다. 대신에, 그들은 두가지 방법중에 하나로 삭제를 합니다.

  1. 레디스 클라이언트가 키에 읽고 쓰기 연산을 수행할때에, 레디스 서버는 첫번재로 키가 존재하고 만료 시간을 가지고 있는지를 체크 합니다. 만약 키가 존재하고 만료시간이 지난 키라면 레디스는 즉시 명령어를 처리하기 전에 메모리로부터 키를 삭제합니다.
  2. 접근이 더 이상 없는 키의 경우 메모리에 임시적인 키가 영구적으로 남아있는 것을 방지하기 위해서 레디스는 만료 키를 위해서 단순한 패시브 알고리즘을 사용합니다: 매 10ms, 100개의 랜덤으로 임시적인 키들을 추출하고 즉각 만료가 지난 모든 키들을 삭제처리합니다. 만약 25% 혹은 그 이상의 키들이 삭제되었다면, 레디스는 즉각 다른 100개의 키를 추출하고 같은 일을 반복합니다.

위의 두번째 방법은 매우 중요한데, 키의 25% 이상이 이미 만료된 임시적인 키(메모리에서 아직 삭제가 되지 않은) 될 수 있다는 것을 의미한다. 레디스는 삭제되때까지 INFO 에 “keys”와 “expires”에 이러한 키들을 계속 포함시킬 것입니다.

어떻게 레디스는 새로운 슬레이브에 데이터셋을 보낼까?

레디스 슬레이브가 레디스 마스터에 붙었을때에, 마스터 서버는 그들의 데이터셋의 RDB 스냅샷을 생성하고 그것을 슬레이브로 보냅니다. 이때 레디스는 RDB 스냅샷을 생성할때에 아직 메모리에 남아있다 할지라도 이미 만료시간이 지난 키들은 포함하지 않습니다.

그래서, 왜 나의 슬레이브의 키 갯수는 마스터의 키 갯수보다 적을까?

슬레이브가 마스터 인스턴스에 붙으면, 슬레이브는 메모리에서 삭제되지는 않았지만 만료된 임시적인 키들을 포함하지 않은 데이터셋을 받습니다. 그리고 25% 이상의 키는 메모리에서 아직 삭제되지 않았지만 만료된 키일 수 있기 때문에 슬레이브는 마스터의 25% 이상 더 적은 키를 보여줄 것입니다. 덧붙여서, 이것은 모든 키가 없어져서 RDB 백업으로 레디스 서버를 복구할때에도 똑같은 일이 벌어집니다.

Redis 운영에 필요한 잡지식들.

Redis 는 In memory 기반으로 동작하기 때문에 메모리 관리가 매우 중요한데, 먼저 리눅스에 메모리 관리를 Redis 운영에 적합하도록 설정하는 것이 좋다.

먼저 Redis 는 Swap 을 사용하지 않고 물리 메모리 내에서만 운영하는 환경이다.

가상 메모리 overcommit 에 대해서 2 로 설정하는 것이 좋다. 2로 설정하게 되면 리눅스가 사용가능한 메모리는 다음과 같이 계산한다.

예를들어 32GB 물리 메모리를 가지고 있고 overcommit_ratio 가 50%라면  16GB 물리 메모리만 사용하게 된다. 그래서 overcommit_ratio 를 99% 로 설정을하면 모든 메모리를 사용하게 된다.

swappiness 는 0 ~ 100 사이에 값을 가진다. 0에 가까우면 메모리의 dirty page 가 있다고 하더라도 최대한 swap 을 하지 않지만 100에 가까우면 조금만 dirty page 가 있으면 swap 을 한다. Redis 를 물리 메모리만 사용하게끔 하고 싶다면 swappiness 를 0 으로 하는게 좋다.

Redis 의 save 옵션&&maxmemory-policy

Redis 는 메모리의 내용을 디스크로 보관하도록 하는 옵션이 여럿 있다. 그중에 save 옵션이 존재하는데, 다음과 같다.

위 두 조건중에 하나만 만족되도 Redis 는 메모리에 내용을 디스크에 쓰게된다. 만약, Redis 를 운영하는동안 위 조건을 하나도 만족하지 못한다면 디스크로 전혀 쓰는 행위가 발생하지 않게되고 maxmemory 에 닫게 되는 상황에서 maxmemory-policy 가 volatie-lru 로 되어 있다면 다음과 같은 상황에 직면하게 된다.

위 오류는 랜덤으로 key-value 를 생성해서 무한 set 을 돌린 결과 이다. maxmemory 는 512MB 였는데, expire 되는 key 가 하나도 없어 메모리는 항상 차게되어 있고 결국에는 더 이상 사용할 메모리가 없자 오류가 난 것이다.

이는 프로그램 운영 상으로도 매우 심각한 문제가 된다. Redis 는 ‘evicted_keys’ 가 존재하는데 보통은 메모리 부족현상으로 발생되는 거라고 생각한다. 그래서 메모리가 부족하면 evicted_key 만 나오고 프로그램은 예외가 발생하지 않을 거라고 생각한다.

evicted_keys 도 증가했지만 예외상황을 맞을수도 있다.

그렇다면 maxmemory-plicy 가 allkeys-lru 되어 있다면 어떻게 될까? 결론부터말하면 프로그램은 예외를 발생할 확률이 줄어든다. 예외를 발생시킬 수도 있지만 안될 확률이 높다. 프로그램은 잘동작하는 것처럼 보이지만 Redis 는 evicted 를 발생시키고 있다. 따라서 evicted_key 가 증가하는 현상이 보인다면 maxmemory 값을 늘려주는게 좋다.

bgsave 는 slave 와 접속을 차단한다. replication 은 SYNC 는 BGSAVE 를 발생시킨다.

먼저, 구분되어야 할게 있다. Master<->Slave 간의 replication 상태에서 데이터는 전송되지 않는다. 무슨 말이냐하면 Master 발생되는 명령어들을 그대로 Slave 에 던지는 방법이다. “set myhello ‘Hello'” 라는 명령어를 Master 에서 했다면 Slave 도 똑같은 명령어가 전달되지 데이터가 전달되지 않는다.

용어의 차이일 수 있는데, SYNC 시에는  slave 에게 데이터를 전송하기에 앞서 disk 에 rdb  파일을 생성한다. replication.c 에 다음과 같은 내용을 볼 수 있다.

rdb 파일은 백그라운드 프로세스를 생성시키고 rdb_filename 파일에 저장한다. 문제는 이러한 일이 벌어지는 동안에 접속은 차단된다.

closeListeningSockets 함수는 TCP/IP 접속 소켓을 닫게 된다.

결국 BGSAVE 가 발생하는동안 slave 접속은 차단된다. 그리고 다시 접속이 이루어지면 Full Sync 가 발생된다. 정리를하면 현재 Replication 이 된 상태라면 Sync 를 위해서 rdb  덤프는 발생되지 않는다. 하지만 새롭게 Replication 이 맺어지면 그때에는 Master 가 rdb 덤프를 하고 그리고 나서 데이터를 전송하게 된다. 실제로 새로운 Replication 이 맺어지면 파일 덤프가 다음과 같이 발생한다.

temp-5594.rdb 임시파일을 생성하고 나서 rdb_filename 으로 변경한다. Replication 상태에서 rdb 검프는 SYNC 에만 발생한다.

그런데, Redis 는 slave 가 잠시동안 접속이 차단될때에 Full Sync 가 발생하지 않도록 repl-backlog-size 에 복제를 위한 데이터를 저장해둔다. 주의해야할 것은 이 크기는 메모리 크기를 말하는 것으로 디스크에 데이터를 저장하지 않는다.

여기서 또 짚고 넘어가야할 것은 repl-backlog-size 가 작아서 runtime 으로 크기를 변경해주면 기존의 데이터는 다 지워지고 새로운 크기의 backlog 가 할당되는 방식을 취한다.

 

Redis 를 운영할때에 Master-Slave 로 운영한다면 모든 것을 메모리로만 운영할 수는 없다. Slave 로 Replication 을 할때마다 BGSAVE 가 발생하고 이는 일시적으로 Slave 와의 접속을 차단하기 때문이다. Slave 와의 접속이 차단된 후에 다시 연결이 되었을 경우에 Full Sync 가 발생할 수 있다.

diskless 를 통한 slave sync

이는 사실상 트릭같다. disk sync 은 Master 가 bgsave 로 메모리에 데이터를 점프한 후에 그것을 slave 로 전송하는 방법이다. bgsave 가 작동하게 되면 slave 와의 연결은 잠정적으로 끊어지게 된다.

하지만 disklee sync 는 bgsave 가 동작하지 않으며 Master 가 아닌 Slave 가 Master 로부터 메모리의 내용을 전송받아 덤프 파일을 생성한 후에 이것을 메모리에 올리는 방식이다. Master 로부터 Slave 가 데이터를 전송받을때, Master 는 “redis-rdb-to-slaves *:6379” 프로세스가 생성돼 전송을 담당한다. Slave 에서는 전송된 데이터를 파일로 저장하기위해서 별도의 프로세스는 작동하지 않는다.

Master의 메모리 내용을 Slave 가 덤프 받는 방식이 diskless sync 이며 수십기가의 메모리 내용을 전송해야하기 때문에 네트워크 상태가 좋아야 한다.

Python redis 테스트 스크립트.

랜덤으로 키를 생성해서 값을 넣도록 만든 Python redis 테스트 스크립트.

 

Redis 설치하기

redis

 

Redis 는 Key-Value 로 데이터를 저장하는 NoSQL 서버 입니다. In Memory 기반으로 동작하기 때문에 빠른 응답속도를 보입니다.

이 문서는 Redis 설치에 관한 문서 입니다.

다운로드

의존성 패키지 설치

CentOS 의 경우에는 다음과 같이 설치를 해줍니다.

Ubuntu 의 경우에는 다음과 같이 설치를 해줍니다.

컴파일

설치

설치는 PREFIX 를 인자로 줘서 설치 디렉토리 위치를 지정해줄 수 있습니다.

Redis 의 설치는 오로지 실행파일들만 복사해 줍니다.

설정

Redis 는 설치 후에 각종 설정을 할 수 있도록 스크립트를 지원 합니다. 이 스크립트를 사용하면 각종 설정파일들을 복사해 줍니다. 이 스크립트는 utils 디렉토리에 존재 합니다.

자세히 보면 initscript 까지 설정을 해주고 실행까지 해줍니다. 설정파일 하나하나 복사해줄 이유가 없습니다.