Redis
1. Redis ? (Remote dictionary server)(외부 hash-map. key-value 서버)
대규모 서비스를 운영하기 위해선 데이터를 안전하고 신속하게 Read / Write하는 기술이 필요.
이러한 Redis를 캐싱 솔루션 또는 NoSQL 의 Key-Value 저장소라고 부르기도 함.
2. Redis 의 주요 특징
항목내용
Key-Value 스토어
-
- 기본적으로 Redis 는 Key-Value 스토어로 빠르고 간단하게 데이터를 가지고 올 수 있다.
- 컬렉션 지원
- Redis 는 여러가지 자료구조를 지원하므로 서비스에 맞는 설계를 할 때 유용하게 사용될 수 있다. (List, Set, Sorted Set, Hash)
- 디스크 저장
- Redis 의 특징 중 하나는 현재 메모리의 상태를 디스크에 스냅샷 형태로 남길 수 있는 RDB 방식이 있다. 그러므로 Redis 를 restart 할 때 스냅샷에 있는 내용을 가지고 쉽게 복구할 수 있다. 그리고 AOF(Append Only File) 형태로 Redis 에 변경 내역들을 모두 남기는게 가능해서 Write Behind 같은 기능을 지원하는 것도 가능하다.
- 복제
- Redis 는 Master/Slave 구조로 이용할 수 있어서 Master 의 읽기 부하를 Slave 로 나누는게 가능하다.
- 빠른 성능
- 초당 50,000 ~ 60,000 이상의 처리 속도.
3. Redis 의 내부 스레딩 모델 - 싱글 스레드
Redis 를 사용하면 의도하지 않게 성능이 안나오거나 장애가 발생하는 경우가 많다.
Redis 는 싱글 스레드 기반이므로 하나의 명령이 오래걸린다면 이는 적합하지 않다.
일단 실무에서 흔히 사용하는 실수의 사례를 살펴보자.
- 서버에서는 Keys 명령을 사용하지 말자.
- keys *와 같은 모든 key 조회시 데이터가 적다면 별 문제가 없겠지만 몇백만 개, 몇천만 개가 넘어가면 많은 시간을 소모하게 된다. (redis는 싱글스레드임을 항상 잊지 말자)
- 레디스 자체 성능 떄문에 keys * 같은 명령어 대신 scan 0으로 대체 하는 것이 좋다. "재귀적"으로 key를 순차 호출 가능하다.
- flushall/flushdb 명령을 주의.
- Redis 에서는 모든 데이터를 삭제하는 flushall/flushdb 명령이 있다. 그리고 Redis 는 db 라는 가상의 공간을 분리할 수 있는 개념을 제공해주고 select 명령으로 이런 db 로 이동할 수 있다. 이를 통해 같은 Key 값이라도 db 가 다르면 다른 value 가 나오게 할 수 있다. 이런 db 하나의 내용을 모두 지우는게 flushdb 이고 모든 db 의 내용을 모두 지울 수 있는게 flushall 이다.
- Redis 의 flushall 은 지우는데 keys 명령어와 같이 많은 시간이 걸린다. 실제로 존재하는 모든 데이터를 지우므로 O(n) 이 걸리고 싱글 스레드이기 때문에 많은 시간이 걸린다.
4. Redis Persistent
Redis 같은 경우는 디스크에 저장된 데이터를 기반으로 다시 복구하는게 가능하다.
이런 Persistent 기능은 데이터 스토어로서의 장점이 있지만 이 기능 때문에 장애의 주 원인이 되기도 한다. 그러므로 이 기능을 잘 알아보고 사용하는게 중요하다.
먼저 Redis 에서 제공하는 RDB, AOF 등의 기능에 대해 알아보고 여기서 발생할 수 있는 장애와 이를 피하는 방법에 대해서 알아보자.
4-1. RDB
RDB 는 RDBMS 라는 오해를 많이 받지만 RDB 는 단순히 메모리의 스냅샷을 파일 형태로 저장할 때 쓰는 파일의 확장자명이다.
RDB 를 사용해 현재 메모리에 대한 스냅샷을 뜬다면 Redis 에서는 싱글 스레드 기반이므로 많은 시간이 걸릴 수 있겠다고 생각할 수 있다. RDB 를 하는 방식은 두 가지가 있고 SAVE 방식은 이처럼 모든 작업을 멈추고 현재 메모리 상태에 대한 RDB 파일을 생성한다. 실제로 SAVE 옵션으로 50GB 의 메모리 상태를 저장한다면 7 ~ 8분 정도 소요된다고 한다. BGSAVE 는 백그라운드 SAVE 라는 의미로 fork() 명령을 통해서 부모 프로세스로부터 자식 프로세스를 생성하고 디스크에 영속화할 수 있다. 현재 가지고 있는 메모리의 상태가 복제된 상태에서 데이터를 저장하도록 한다.
RDB 를 사용하려면 Redis 설정 파일인 redis.conf 파일에 다음과 같은 내용을 추가해야한다.
# dump.rdb 대신에 다른 파일 이름을 사용해도 괜찮다.
dbfilename dump.rdb
기본적으로 Redis 에서는 RDB 사용 옵션이 설정되어 있고 save 명령에 따라서 실행이 가능하다.
명령어 : save 900 100 -> 900초안에 10번의 변경이 있을때 RDB 를 사용해 디스크에 스냅샷을 저장한다는 뜻.
4-2. AOF
AOF 는 "Append Only File" 의 약어로 데이터를 저장하기 전에 AOF 파일에 현재 수행할 명령어를 저장해놓고 장애가 발생하면 AOF 를 기반으로 복구한다.
즉 다음과 같은 순서로 데이터가 저장된다.
- 클라이언트가 Redis 에 업데이트 관련 명령을 요청한다.
- Redis 는 해당 명령을 AOF 에 저장한다.
- 파일쓰기가 완료되면 실제로 해당 명령을 수행해서 Redis 메모리에 내용을 변경한다.
AOF 는 Redis 설정 파일인 redis.conf 에 기본적으로 사용되지 않으므로 AOF 를 사용하려면 다음과 같이 변경해야한다.
# appendonly 는 기본적으로 no 로 설정되어 있다.
appendonly yes
appendfilename appendonly.aof
appenfdsync everysec
- 주의해야 할 설정은 appendfsync 값이다. AOF 는 파일에 저장할 때 파일을 버퍼 캐시에 저장하고 적절한 시점에 이 데이터를 디스크로 저장하는데 appendfsync 는 디스크와 동기화를 얼마나 자주 할 것인지에 대해 설정하는 값으로 다음과 같이 3가지가 있다.
- always: AOF 값을 추가할 때마다 fsync 를 호출해서 디스크에 실제 쓰기를 한다.
- everysec: 매초마다 fsync 를 호출해서 디스크에 실제 쓰기를 한다.
- no: OS 가 실제 sync 를 할 때까지 따로 설정하지 않는다.
4-3. AOF 와 RDB 의 우선순위
AOF 와 RDB 모두 Persistent 를 구현하는 방법 중 하나인데 두 개의 파일이 모두 있다면 어느 파일을 읽을까?
RDB 는 주기적으로 스냅샷을 띄므로 가장 최신의 데이터를 가지진 못하지만 AOF 는 메모리에 수정사항을 반영하기 전에 항상 디스크에 추가하기 떄문에 둘 다 있다면 AOF 를 기준으로 읽는다.
4-4. Redis 가 메모리를 두 배로 사용하는 문제
Redis 가 운영되는 중에 장애를 일으키는 가장 큰 원인은 RDB 를 저장하는 Persistent 기능으로 BGSAVE 방식에서 fork() 를 사용하기 떄문이다.
현대 운영체제에서 fork() 를 사용해서 자식 프로세스를 생성하면 COW(Copy On Write) 라는 기술을 이용해서 부모 프로세스의 메모리에서 실제로 변경이 필요한 부분을 복사한다. 그러므로 해당 부분이 두 개가 존재하게 되므로 메모리를 두 배로 사용하는 경우가 발생할 수 있다.
즉 Redis 에 write 가 발생할 때 마다 그 부분에 해당하는 메모리는 두 배가 될 수 있다. 이로인해 메모리가 부족해서 문제가 생길 수 있다.
Redis 를 사용한다면 실제 Physical 메모리를 모두 사용하지 말고 약간의 여분을 두고 RDB 를 사용한다면 이 경우도 생각해서 메모리의 여분을 남겨두는게 좋다.
4-5. Redis 장애: Read 는 가능한데 Write 가 실패하는 경우
Redis 를 운영시 RDB를 사용하는 경우 자주 발생하는 장애가 있다.
Redis 서버는 동작하지 않는데, 정기적인 Heartbeat 체크는 이상이 없는 경우다.
이러한 문제는 RDB 저장이 실패할 때 기본 설정상 Write 명령이 동작하지 않기 때문에 발생한다.
Redis 는 RDB 저장이 실패하면 해당 장비가 이상이 있다고 생각해서 Write 명령을 더는 처리하지 않고 데이터가 변경되지 않도록 관리한다.
그렇다면 어떠한 이유로 RDB 생성에 실패할까?
- RDB 를 저장할 수 없을 정도로 디스크에 공간이 없는 경우
- 실제 디스크가 고장난 경우
- 메모리 부족으로 자식 프로세스를 생성하지 못하는 경우
- 누군가 강제적으로 자식 프로세스를 종료시킨 경우
위의 이유 등으로 RDB 저장을 실패하면 Redis 내부의 lastbgsave_status 라는 변수가 REDIS_ERR 로 설정된다. 그리고 Write 관련 요청은 모두 무시하게 된다.
그렇다면 이 문제는 어떻게 해결해야 할까?
먼저 해당 상황이 맞는지 확인해야 한다. 그 상황을 체크하기 위해선 두 가지 방법이 있다.
첫번째론 Write 명령을 날려보는 방법이 있다.
redis 127.0.0.1:6379 > set a 123
(error) MISCONF Redis is configured to save RDB snapshots. but is currently not able to persist on disk.
두 번째 방법은 info 명령을 이용해 다음 값을 체크하는 것이다.
rdb_last_bgsave_status: ok
5. Redis 복제
Redis 의 주요 특징 중 하나는 DBMS 가 제공하는 것과 같이 복제(Replication) 기능을 제공하는 것이다.
복제 기능은 장애 발생 시 빠른 서버 교체를 하거나 장비 교체를 할 수 있다. 복제 자체에 대해서는 아는 사람이 많을 것이라 생각하지만 Redis 의 복제가 일반적인 DBMS 의 복제와 어떻게 다른지 유의해서 살펴보면 좋다.
5-1. Redis 복제 모델
Redis 는 Master/Slave 형태의 복제 모델을 지원한다. 이를 통해서 Master 의 변경은 Slave 를 타고 전파된다. 그리고 Slave 는 오로지 한 대의 Master 만 가질 수 있다는 것을 기억해두자.
그리고 Slave 가 다른 장비의 Master 가 될 수도 있다.
운영 중인 장비를 슬레이브로 바꾸는 방법은 다음과 같다.
5-1.1. slaveof 명령을 통해 현재 Redis 를 다른 Redis 의 Slave 로 변경하기
다음과 같이 두 대의 Redis 를 실행해보자.
$ ./redis-server -port 6379
$ ./redis-server -port 6380
6380 포트를 가진 Redis 를 6379 포트 Redis 의 Slave 로 변경해보자.
$./redis-cli -p 6380
redis 127.0.0.1:6380> slaveof 127.0.0.1 6379
OK
5-1.2. redis.conf 를 통해서 현재 Redis 를 다른 Redis 의 Slave 로 변경하기
Redis 설정 파일인 redis.conf 를 보면 slaveof 이라는 항목이 있다. 여기에 누구의 Slave 로 될건지 명시해주면 된다.
# redis.conf 파일에 다음과 같이 입력하자.
slaveof 127.0.0.1 6378
5-1.3. Redis 복제 과정
Redis 의 복제 과정을 간략하게 정리하면 다음과 같다.
- Slave 에서 slaveof 명령을 이용해서 Master 를 설정한다.
- Master 가 설정되면 replicationCron 에서 현재 상태에 따라 connectWithMaster 를 호출한다.
- Master 는 복제를 위해 RDB 를 생성한 후 Slave 에게 전송한다.
- Slave 는 RDB 를 로드하고 나머지 차이에 대한 명령을 Master 에게 전달받아서 복제를 완료한다.
5-1.4.Redis 복제 사용 시 주의 사항
< slaveof no one >
slaveof no one 이 명령은 현재 Slave 를 Master 로 승격시킬 때 사용한다.
Redis 의 복제에서 주의할 점은 Slave 와 Master 의 연결이 끊어지고 다시 연결되면 Master 의 최종 결과로 sync 한다는 점인데 Master 에서 장애가 나서 데이터가 하나도 없다면 Slave 도 내용이 모두 사라지는 문제가 발생할 수 있다.
그래서 slaveof no one 명령을 통해서 Slave 를 Master 로 승격시키는게 중요하다. 이를 통해서 Slave 는 데이터를 계속해서 유지시키고 업데이트 시켜 나갈 수 있기 때문이다.
< 복제를 쓰는 경우에는 무조건 백그라운드로 RDB를 생성한다는 점을 주의 >
일반적으로 RDB 를 사용하면 메모리를 두배로 사용할 가능성이 있기 때문에 RDB 를 사용하지 않는 경우도 있다.
반면 복제를 한다면 사용자의 설정과 무관하게 무조건 Slave 로 전달할 RDB 를 만들어야 하기 때문에 fork 를 해서 RDB 를 백그라운드로 생성한다.
즉 이러한 과정에서 장애가 발생할 경우가 많기 때문에 하나의 프로세스에서 메모리를 많이 사용하지 않도록 나누는 과정이 필요하다.
6. Redis 복제를 이용한 실시간 마이그레이션
Redis 서비스를 운영하다 보면 현재의 데이터를 더 좋은 장비로 이동해야 하는 경우가 생긴다.
가능한 다운 타임 없이 바로 데이터를 옮기고 싶어하는 경우가 많은데 이때 Redis 의 복제 기능을 이용하면 다운 타임 없이 쉽게 데이터를 이전하는게 가능하다.
기존 Master 장비를 127.0.0.1:6379 라 하고 새로운 장비를 127.0.0.1:6380 이라고 하자.
다음과 같은 순서로 작업하면 된자.
- 데이터 이전을 위해 새로운 장비를 Redis 인스턴스로 실행시킨다.
- 새로운 장비를 기존 장비의 Slave 로 등록 시킨다. 이를 통해 Master 와 Slave 데이터를 동기화 시킨다.
- 새로운 장비의 옵션에 slave-read-only 옵션을 끈다. 이를 통해 새로운 장비의 데이터를 계속해서 업데이트 시킨다. 다만 이 설정을 끈다고 Slave 의 변경이 Master 로 옮겨가진 않는다.
- 기존의 클라이언트들이 새로운 장비를 Master 로 인식하게끔 설정을 바꾼다.
- slaveof no one 명령을 통해서 새로운 장비를 이제 Master 로 승격시키고 기존 Master 를 종료시킨다.
출처 : https://devbksheen.tistory.com/entry/3-Redis-%EC%9A%B4%EC%98%81%EA%B3%BC-%EA%B4%80%EB%A6%AC