# Redis 운영
###### tags: `Etc`
## Redis 특징
1. Key-value 스토어
- 단순 String에 대한 key-value 지원
2. Collection 자료 구조 지원
- List, Set, Sorted Set, Hash 등의 자료 구조 지원
- 분산 서버 환경에서 처리하기 편함
3. Pub/Sub 지원
- Pub/Sub 모델 지원
- 서버 간에 통지가 필요할 때 유용
4. Persistent Layer(디스크 저장)
- 메모리 상태를 디스크로 저장하는 RDB 기능
- 현재까지의 업데이트 관련 명령을 저장하는 AOF 기능
5. Replication
- Primary-Secondary 구조
- 장애 발생에 대한 failover 대비
- 읽기 처리에 대한 부하 분산
6. 빠른 속도
- In-Memory로 빠른 성능을 보장
## Redis VS Memcached
### Memcached는 Cache Solution!
- cache는 빠른 속도를 위해 사용했던 것, 혹은 사용할 데이터를 저장해두는 것
- 하지만 빠른 성능을 위한 optional한 Solution이기 때문에 데이터가 사라져도 큰 문제가 없다.
### Redis는 Cache + Store Solution!
- 데이터를 유지해야하는 특성이 있기 때문에 Memcached보다 좀 더 민감하게 다뤄야한다.
- 따라서 디스크에 저장하는 RDB, AOF 기능이 추가되어 있다.
- 또한, Collection 기능이 있어 개발의 생산성면에서 효과적이다.
- 하지만 Memcached보다 응답 속도의 균일성이 떨어진다.
- jemalloc이란 메모리 할당 구조로, 매번 malloc과 free를 통해 메모리 할당이 이루어진다.
- 하지만 Memcached는 slab 할당자를 사용해 내부적으로 메모리 할당을 다시 하지 않고 관리하는 형태를 사용한다.
- 즉, Redis는 메모리를 직접적으로 사용하기 때문에 Memory Fragment가 발생해 할당 비용때문에 응답 속도가 느려진다.
## Redis 운영 관리 팁
### Redis가 싱글 스레드임을 인지하자.
- 싱글 스레드임을 인지하지 않으면 의도하지 않은 장애나 성능 저하가 발생한다.
- 싱글 스레드 특징으로 생긴 원인은 긴 명령을 호출하면 다른 명령을 처리하지 못하는 것이다.
- 밑에서 설명하는 팁들도 다 이 특징을 고려한 것들이다.
### Keys 명령을 사용하지 마라
- keys 명령어는 현재 서버에 저장된 key 목록들을 조회하는 명령이다.
- 즉, key를 모두 갖고 오는 과정에서 다른 명령을 처리하지 못한다.
- keys 명령을 사용해야 한다면, `list`, `set`, `sorted set`을 사용해 `userlist_20200406` 같은 이름으로 데이터를 각 Collection에 묶어서 저장한다.
- 그렇게 되면 Collection의 `get`, `hget` 명령을 이용해 묶어진 범위내의 Keys들만 조회하면서 갖고 올 수 있다.
### flushall, flushdb 명령을 조심하자
- Redis는 db라는 공간으로 분리해 데이터를 따로 저장할 수 있다.
- select 명령으로 db를 이동할 수 있다.
- select 명령을 사용하지 않았다면 기본적으로 0번 db를 사용한다.
```shell=
select 0
set name "1234"
select 1
set name "4321"
get name
==> "4321"
select 0
get name
==> "1234"
```
- 모든 데이터를 삭제, 특정 db내의 모든 데이터 삭제하는 명령어
- flushall 명령은 모든 Keys를 조회해서 삭제하기 때문에 O(n) 시간이 걸려, key 수가 많으면 상당히 오래걸린다.
- Memcached의 flushall은 상당히 빠르다.
- 그 이유는 데이터를 삭제하지 않고 명령어를 실행한 시간을 기록하기만 하기 때문이다.
- 이전에 저장된 key들은 get 접근할 때 기록했던 시간을 비교해 지운다.
## Persistent
- Memcached는 서버 장애시, 모든 데이터가 사라진다.
- 하지만 Redis는 디스크에 저장된 데이터를 기반으로 복구할 수 있다.
- 그래서 저장소로 Redis를 사용할 때 필수 기능이지만 장애의 주 원인이 될 수 있다.
### RDB
- 현재 메모리에 대한 덤프를 생성하는 기능
- Redis의 메모리 스냅샷을 파일로 저장한 파일의 확장자명이다.
- 사용하기 위해서 redis.conf에 내용을 추가하거나 config set/get 명령으로 변경할 수 있다.
```
dbfilename dump.rdb
```
```
config set dbfilename test.rdb
config get dbfilename
===> 1) "dbfilename"
===> 2) "test.rdb"
```
- RDB 저장 명령으로 `SAVE`와 `BGSAVE`가 있다.
- SAVE는 현재 작업을 멈추고 덤프를 생성한다.
- 기본적으로 RDB는 사용하는 옵션으로 되어있으며, SAVE 명령으로 동작한다.
- `BGSAVE`는 덤프를 저장하는 동안 다른 작업을 할 수 있게 프로세스를 fork해서 자식 프로세스로 백그라운드 실행할 수 있게 도와준다.
- 현재 메모리 상태를 복제한 것을 기반으로 데이터를 저장한다.
- 하지만 Fork하는데 문제가 발생한다.
- OS가 자식 프로세스를 생성하면 부모 프로세스의 메모리를 모두 복사해야 한다.
- 따라서 부모 프로세스가 10GB 메모리를 사용하면 자식 프로세스에 복사할 메모리도 10GB의 메모리가 필요하다.
- Copy On Write 기술을 적용해도 부모 프로세스 메모리에서 실제 변경이 발생한 부분만 복사해도 Redis는 대부분 Write가 많아 메모리가 많이 필요하다.
- Fork 문제 정리
- 자식 프로세스가 생성 시, 메모리 사용을 추가하지 않고 부모 프로세스의 메모리를 공유 받는다.
- 자식 프로세스에 Write 요청이 있으면 자식 프로세스에도 데이터가 복사된다.
- 여기서, Write 작업이 많아지면 결국엔, 자식 프로세스도 부모 프로세스가 사용하는 메모리 양만큼 데이터가 차게 된다.
- 메모리 할당법
- Redis는 싱글 스레드이기 때문에 자원 활용을 위해서 CPU Core수 만큼 서버를 생성하는 것이 좋다.
- 그리고 Fork를 해 RDB를 저장하는 용도의 프로세스를 고려해야 한다.
- 예로 들어, CPU Core가 4개이고 메모리를 32GB 사용하는 컴퓨터라고 생가갛자.
- Core 4개를 다 사용하기 위해 4개의 Redis 서버가 필요해 4개의 프로세스를 사용해야 한다.
- 여기서 RDB를 BGSAVE할 Fork 프로세스까지 +1 해서 5개가 필요하다.
- 그래서 32GB/5 = 6GB를 각 서버에 할당하는 것이 좋다.
### AOF
- Append Only File
- 기존의 DBMS에서 제공하는 Write Ahead LOg와 비슷한 기능
- 과정
1. 데이터 저장전에 AOF 파일에 현재 수행해야 할 명령을 저장
2. 파일 쓰기 완료
3. 실제 명령어를 메모리 내용에 반영
- AOF 파일은 Redis 프로토콜 그대로 저장
- redis.conf에 appendonly가 yes로 설정, appendfilename으로 지정한 이름으로 저장됨
- appendfilename을 지정하지 않으면 기본 값인 appendonly.aof로 저장됨
### RDB VS AOF 우선순위
- 최신 데이터를 더 많이 가진 파일을 읽어야 됨
- AOF는 항상 메모리에 반영하기 직전에 Write한 파일이기 때문에 AOF를 우선순위를 크게 두고 사용한다.
- RDB는 특정 시간 단위로 저장하기 때문에 다음에 저장하는 명령 이전의 데이터는 적용이 안되 데이터가 유실되기 때문이다.
- 하지만 AOF에서 appendfsync 값을 설정할 때 주의해야한다.
- `appendfsync everysec`: AOF 값을 추가할 때마다 호출해 디스크에 쓴다.
- `everysec`: 매초마다 fsync를 호출해 디스크에 쓴다.
- `no`: OS가 실제 sync할 때까지 따로 설정하지 않는다.
## Read는 되지만 Write가 실패하는 상황
- Heartbeat 체크는 이상이 없지만 RDB 저장이 실패해 Write가 동작하지 않는 현상이 발생할 수 있다.
- 이 경우는 Hearbeat 체크가 Read 관련 명령을 사용하기 때문에 이상이 없다고 말한다.
- Write가 실패하는 경우
- RDB를 저장할 디스크 여유 용량이 없는 경우
- 디스크가 고장난 경우
- 메모리 부족으로 자식 프로세스를 생성하지 못하는 경우
- 자식 프로세스가 강제로 종료된 경우
- 즉, RDB에 이상있으면, 실제 메모리에 Write도 동작하지 않는다.
- RDB 이상시에도 Write 허용 방법
1. 이미 운영 중인 Redis의 적용
```
config set stop-writes-on-bgsave-error no
```
2. redis.conf에 미리 등록
```
stop-writes-on-bgsave-error no
```
## Replication
- Redis는 Primary-Secondary 형태의 복제 모델을 제공한다.
- Primary 변경 사항이 Secondary들에게 전파되는 구조이다.
- Secondary는 무조건 하나의 Primary만을 가질 수 있다.
- Secondary도 Primary로써 동작할 수 있다.
- 운영 중인 장비에서 Primary 대상을 선택하는 명령
```
slaveof ip port
```
```
pc 1) redis-server -port 6379
pc 2) redis-server -port 6380
redis-cli -p 6380
slaveof 127.0.0.1 6379
```
- 위의 경우는 6379 포트의 Redis server가 Primary, 6380 포트의 Redis-server가 Secondary가 된다.
- redis.confg 수정
```
slaveof 127.0.0.1 6379
- info 명령어로 관계를 확인할 수 있다.
- 우선 위의 redis server 추가 생성은 할 수 없다.
- redis을 설치하고 난 뒤, utils 폴더에 있는 `install_server.sh`를 실행해 새로운 포트의 인스턴스를 만들어야 한다.
### Replication 과정
1. slave of 명령어로 마스터 서버를 설정
2. main -> initServer -> serverCron -> replicationCron
3. replicationCron에서 현 상태에 따라 connectWithMaster를 호출
4. Primary는 Replication을 위해 RDB를 생성하고 Secondary에게 전송
5. Secondary는 RDB를 로드하고 차이에 따른 명령을 마스터에게 전달받아 Replication을 완료
### Redis Replication 주의 사항
1. slaveof no one
- Secondary는 Primary와 연결이 끊기면 재접속을 시도
- 이 과정에서 Primary에서 전체 데이터를 다시 가져와 Primary의 최종 상태로 자신의 데이터를 변경한다.
- 하지만 Primary가 장애로 데이터가 날라가면 Secondary의 데이터도 모두 사라진다.
- 따라서 Primary에 장애가 일어났음을 인지했다면 Secondary에게 `slaveof no one` 명령을 줘서 Replication 관계를 끊는다.
2. Replication을 사용하면 RDB가 BGSAVE로 동작한다.
- 사용자 설정과 무관하게 무조건 Secondary에게 전달할 RDB를 만들기 위해 프로세스를 fork해서 RDB를 생성한다.
## Redis Migration 방법
1. 새로운 Redis 인스턴스를 생성해 실행한다.
2. 만든 Redis 인스턴스를 기존 마스터의 Secondary로 지정한다.
3. 이 장비에 `slave-read-only` 설정을 꺼, 업데이트가 가능하게 설정한다.
```
config set slave-read-only no
```
4. Redis 클라이언트들을 새로운 Redis Server를 Primary로 인식하게 설정을 바꾼다.
5. `slaveof no one` 명령으로 기존에 있던 Redis server와의 Replication 관계를 끊는다.
6. 기존 Redis 장비를 제거한다.
## Sentinel
- High Availability(고 가용성)을 위해선 Primary가 죽었을 경우 Secondary를 승격시키는 시스템이 존재해야 한다.
- 장애 판별 -> Secondary를 Primary로 승격 -> 이 작업을 Client에게 통보
- 위 같은 과정은 `Sentinel`이란 데몬을 사용해 쉽게 작업을 처리할 수 있다.
- 하지만 Sentinel은 Client을 알 수 없으므로, Pub/Sub으로 Sentinel에 등록해야 한다.
### 장애 판별
- Sentinel은 Primary, Secondary에 장애가 발생했음을 기본적으로 PING 명령의 응답으로 판단한다.
- 하지만 응답이 없다고 바로 서버가 장애라고 판단하지 않는다.
- PING 명령은 `sentinelPingReplyCallback` 함수에서 응답 시간을 기록한다.
- Sentinel은 `SDOWN`, `ODOWN`이란 두가지 상태로 장애를 인식한다.
- SDOWN: 주관적인 장애 상태
- PING이 오지 않는 시간과 현재 시간의 간격의 차이를 보고 결정
- ODOWN: 객관적인 다운 상태
- Sentinel 설정에 있는 Failover를 위한 정족수 설정 이상의 Sentinel 서버가 장애라 판단하면 ODOWN 상태가 된다.
### Secondary 승격
- SDOWN, ODOWN, DISCONNECT된 Secondary는 제외
- last_avail_time이 info_validity_time보다 작으면 제외
- info_refresh가 info_validity_time보다 작으면 제외
- master_link_down_time이 max_master_down_time 보다 크면 제외
- 남은 후보 중 slave_priority가 높은 Secondary를 우선적으로 선택됨
- priority가 같으면 runid를 비교해 가장 큰 값을 Primary로 선택
- 백업 전용 서버처럼 승격하면 안되는 Secondary는 redis.conf에서 slave_priority를 0으로 설정한다.
### sentinel.conf
- sentinel monitor 클러스터명 PrimaryIP PrimaryPort 정족수
- sentinel down-after-milliseconds 클러스터명 시간밀리세컨드
- 다운으로 인식하는 시간 설정
- SDOWN을 인식하는 Period
- sentinel failover-timeout 클러스터명 시간밀리세컨드
- failover 시에 사용하는 timeout
- failover가 위에서 설정한 시간이 지나도 수행되지 않으면 포기한다.
- sentinel can-failover 클러스터명 yes
- yes로 설정해야만 Sentinel이 failover를 진행할 수 있다.
- sentinel parallel-syncs 클러스터명 sync할Secondary수
- 새 Primary 승격 후 몇 개의 Secondary가 sync되야 실제 Client에게 알려줄 지 설정
### Sentinel은 Primary 정보만 알아도 된다.
- sentinel monitor 설정에 Secondary 서버 주소를 설정해도 Primary 주소로 자동으로 변경된다.
- 그 이유는 Primary에서 info 명령어로 Secondary 정보들을 알 수 있기 때문이다.
## 모니터링 툴 공부하자.