# 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 정보들을 알 수 있기 때문이다. ## 모니터링 툴 공부하자.