오늘은 Redis 를 운영 단계에서 사용할 때 안정성 있게 사용하기 위한 지표인 '가용성' 관련해서 공부한 내용을 정리한다.
Redis 는 대부분의 서비스에서 중추적인 역할을 담당하기 때문에 장애 상황에 대한 대응책이 꼭 필요하다. 장애 상황을 빠르게 극복하고 정상화하여 최종적으로 서비스의 고 가용성을 확보하기 위해서는 Redundancy(Replication) 과 자동 Failover 가 확보되어야 할 것이다. 즉, Redis 서버를 여러개 복제해서 운용하고 있다가 Primary 노드에 장애가 생기면 Replica 노드가 장애가 발생한 Primary 노드를 대신해 서비스를 이어갈 수 있어야 한다는 것이다.
1. Replication (복제)
Redis 도 다른 데이터베이스와 마찬가지로 DB 자체적으로 복제 기능을 제공한다. 복제 방법은 아주 간단한데 레디스의 Replica 노드에서 REPLICAOF <primary-ip> <primary-port> 명령을 하면 된다. 복제는 두가지 방식으로 이뤄진다.
1) 복제 방식 1. repl-diskless-sync: no (레디스 7 이전에서 기본값)
- Primary 에서 fork() 를 통해 자식 프로세스를 생성
- 자식 프로세스에서 디스크에 스냅샷(RDB)을 쓰기 시작
- 이후 실행한 명령은 복제를 위한 버퍼에 기록
- RDB 스냅샷이 완성되면 Replica 노드로 전송
- Replica 노드에서는 전송된 RDB 스냅샷으로 데이터 복원
- Primary 노드에서 버퍼에 기록한 명령을 Replica 에 전송
방식 1 에서 주의할 점은 fork() 를 통해 생겨난 자식 프로세스가 메모리 공간을 추가적으로 차지할 수 있다는 것이다. 처음 자식 프로세스가 생성되면 부모 프로세스의 메모리 공간을 그대로 사용하기 때문에 추가적인 메모리 할당이 일어나지 않는다. 그러나 이후 자식 프로세스가 스냅샷을 쓰는 동안 부모 프로세스에서 쓰기/수정이 일어나면 이는 공유되는 것이 아니라 자식에도 추가적으로 메모리 페이지를 할당하게 된다. (이를 Copy-On-Write 라고 부름) 때문에 Primary 노드가 쓰기 작업이 매우 활발한 경우 메모리 사용량이 급증하게 될 수 있다.
2) 복제 방식 2. repl-diskless-sync: yes (레디스 7 부터 기본값)
- Primary 노드에서는 자식 프로세스를 생성하지 않고, 소켓 통신으로 Replica 노드에 RDB 파일을 조금씩 송신
- Replica 노드에서는 해당 데이터를 수신하여 디스크로 쓴다.
- 수신이 완료되면 완성된 RDB 파일로 데이터를 복원한다.
- 이 후, 방식 1 과 동일하게 Primary 노드의 Replication buffer 에 쌓인 커멘드를 받아와서 동기화를 진행한다.
방식 2 에서는 이름 그대로 diskless, 디스크 쓰기 작업없이 Replication 이 일어난다는 것이 특징이다. 때문에 방식 1 에서 문제가 되었던 Copy-On-Write 에 의한 메모리 부족에서 자유롭다. 그러나 네트워크가 느린 경우에는 방식 1 보다 더 복제에 오랜 시간이 걸릴 수 있다.
적절한 도식을 찾으려고 Redis 를 웹페이지를 뒤졌는데, Enterprises 버전에서는 순수하게 그대로 메모리를 복제하는 방식이 있다고 한다. 사실 방식 2 도 Replication 노드의 디스크를 경우하므로 순수하게 diskless 는 아니다. 돈 내면 좋은 걸 쓸 수 있다.
2. 자동 Failover (장애 극복)
Replication 을 확보했다면 다음으로는 장애 대응에 대한 실질적 액션이 필요하다. 장애 대응은 다음과 같은 절차를 수반한다.
- Primary 노드에 장애 발생
- Read Only 인 Replication 노드를 Primary 로 승격하여 Primary 노드로 동작할 수 있도록 함.
- 장애 발생 노드로 향하던 클라이언트의 트래픽을 새로운 Primary 노드로 옮겨줌
- 장애 발생 노드를 다시 구동
- 해당 노드는 새로운 Primary 노드에 대한 Replication 을 수행
이러한 과정을 사람이 일일이 할 수도 있겠지만, 다운타임을 최소화하기 위해서는 이 과정을 빠르고, 정확하게 수행해야 해야한다. 이는 Redis Sentinel 이라는 서비스를 통해 간편하게 자동화 될 수있다. Sentinel 은 다음 역할을 대신해준다.
- 모니터링 및 장애 감지
- Replication 승격
- 트래픽 전달
Sentinel 만으로도 장애 발생시 즉각적인 Failover 는 가능하지만 장애가 발생한 노드의 복구는 별도의 자동화 스크립트를 이용하는 등의 보완이 필요하다.
Sentinel 는 최소 3 개의 인스턴스로 구성해야 정상적으로 동작한다. 왜냐하면 Sentinel 이 단독의 인스턴스로 동작한다면 이 자체가 SPOF 가 되어버려서 고가용성 확보라는 목적에 모순되기 때문이다. 동일한 이유로 각각의 Sentinel 은 물리적으로도 영향받지 않는 공간에서 실행되는 것이 권장된다.
Sentinel 은 3개 이상의 인스턴스로 동작함으로써 장애 상황에서도 안정적으로 Failover 를 수행하며, 또한 서로 정보를 주고 받으며 오동작을 방지한다. 특히, 장애탐지를 Quorum 이라는 알고리즘을 적용하여 오탐을 최대한 예방한다. 또한 장애 감지 후 새로 승격될 Replication 을 선발하는 과정에서도 과반수의 동의를 얻는 등 서로 긴밀한 협조를 하게된다.
Replication 이나 Failover 관련해서 간단히 정리하였는데, 사실 실제 운영경험을 통해 최적의 Replication 설정, Failover 팁 등을 다룰 수 있다면 좋을 것 같다. 유튜브에서 몇 개의 영상을 보면서 정리한 사항이 있는데, 내가 제대로 검증하거나 아는 게 아니라서 그냥 짧게 남겨둔다. 생각나는대로 정리할 예정.
- Redis 는 장애상황에 대비하여 주기적으로 데이터를 백업하는 기능을 제공한다. 백업 방식에는 RDB, AOF 두가지 방식이 있다. (이와 관련해서는 새로 게시글을 써야할 정도로 구체적인 설명이 필요할 것 같다.) 기본적으로 정해진 시간 동안 기준 이상의 데이터의 변화량이 있을 때 백업을 시작한다. 백업 과정도 두가지로 분류가 되는데, 하나는 모든 클라이언트의 명령을 차단한 상태로 백업을 빠르게 수행하는 것이고(SAVE) 다른 하나는 fork() 를 통해 프로세스를 생성하여 백그라운드에서 수행하는 것이다. Replication 부분에서 설명 했다시피 fork() 명령어는 상당한 위험(?) 을 내포하고 있기 때문에 웬만해서는 Primary 노드에서는 수행하지 않는 것을 권장한다. 그러나 여전히 Redis 데이터를 어느정도는 영속화 해놓는 편이 장애 상황 대응 등에서 더 안정적인 운영을 가능하게 할 것이다. 때문에 Replication 노드에서는 백업을 일정 주기로 해두자.