고가용성
가용성이란 일정 기간 동안 서비스를 정상적으로 사용할 수 있는 시간의 비율을 뜻한다.
레디스에서 고가용성을 확보하기 위해서는 다음의 두 가지 기능이 필요하다.
- 복제: 마스터 노드의 데이터를 복제본 노드로 실시간 복사하는 기능이다. 마스터 노드의 서버에 장애가 생겨 데이터가 유실된다 해도 복제본 노드에서 데이터를 확인할 수 있다.
- 자동 페일오버: 마스터 노드에서 발생한 장애를 감지해 레디스로 들어오는 클라이언트 연결을 자동으로 복제본 노드로 리다이렉션 하는 기능이다. 이를 통해 수동으로 레디스의 엔드포인트를 변경할 필요가 없어 빠른 장애 조치가 가능하다.
두 기능 중 어느 하나라도 정상적으로 동작하지 않는다면 고가용성을 확보할 수 없다.
레디스에서 복제 구조
레디스는 멀티 마스터 구조를 지원하지 않으며 마스터는 복제본이 될 수 없다.
복제 구조 구성하기
REPLICAOF <master-ip> <master-port>
레디스에서 마스터에는 여러 개의 복제본이 연결될 수 있으며(수평), 복제본 노드에 새로운 복제본을 추가하는 것도 가능하다(수직). 하지만 한 개의 복제 그룹에서는 항상 한 개의 마스터 노드만 존재한다.
복제 메커니즘
버전 7 이전에서는 repl-diskless-sync
옵션의 기본값은 no
이며, 아래와 같은 순서로 복제 연결이 이루어진다.
REPLICAOF
커맨드로 복제 연결을 시도한다.- 마스터 노드에서는 fork로 자식 프로세스를 새로 만든 뒤 RDB 스냅숏을 생성한다.
- 2번 과정 동안 마스터 노드에서 수행된 모든 데이터셋 변경 작업은 레디스 프로토콜 형태로 마스터의 복제 버퍼에 저장된다.
- RDB 파일이 생성 완료되면 파일은 복제본 노드로 복사된다.
- 복제본 노드에 저장됐던 모든 내용을 삭제한 뒤 RDB 파일을 이용해 데이터를 로딩한다.
- 복제 과정 동안 버퍼링됐던 복제 버퍼의 데이터를 복제본으로 전달해 수행시킨다.
복제 과정에서 복제 속도는 디스크 I/O 처리량에 영향을 받는다. 마스터 노드에서 RDB 파일을 저장하는 시간, 복제본 노드에서 RDB 파일을 읽어오는 과정 모두 디스크 I/O 속도에 영향을 받기 때문이다. 만약 로컬 디스크에 RDB 파일을 쓰는 것이 아니라 NAS와 같은 원격 디스크를 사용한다면 디스크 I/O 속도는 더욱 느려질 것이다.
버전 7이후부터 repl-diskless-sync
옵션의 기본값은 yes
다. 디스크를 사용하지 않는 방식에서는 아래와 같이 복제가 이루어진다.
# redis-cli CONFIG GET repl-diskless-sync
1) "repl-diskless-sync"
2) "yes"
REPLICAOF
커맨드로 복제 연결을 시도한다.- 마스터 노드는 소켓 통신을 이용해 복제본 노드에 바로 연결하며, RDB 파일은 생성됨과 동시에 점진적으로 복제본 소켓에 전송된다.
- 2의 과정 동안 마스터 노드에 수행된 모든 데이터셋 변경 작업은 레디스 프로토콜 형태로 마스터의 복제 버퍼에 저장된다.
- 소켓에서 읽어온 RDB 파일을 복제본의 디스크에 저장한다.
- 복제본 노드에 저장된 모든 데이터를 모두 삭제한 뒤 RDB 파일 내용을 메모리에 로딩한다.
- 복제 버퍼의 데이터를 복제본으로 전달해 수행시킨다.
복제본의 repl-diskless-load
옵션은 기본으로 disabled
이기 때문에 소켓에서 읽어온 RDB 스냅숏 데이터를 바로 메모리에 로드하지 않고, 일단 복제본 노드의 디스크에 저장하는 과정을 거친다. 복제본 노드는 마스터 노드에서 가져온 데이터를 불러오기 전에 자신의 데이터를 모두 삭제하는 과정을 거쳐야 하는데, 이때 소켓 통신으로 받아오는 RDB 데이터가 정상적인지를 미리 확인할 수 없기 때문에 모두 삭제하기 전 자신의 디스크에 데이터를 저장하는 과정을 선행함으로 데이터의 안정성을 확보할 수 있다.
# redis-cli CONFIG GET repl-diskless-load
1) "repl-diskless-load"
2) "disabled"
복제본 노드의 디스크 I/O가 느리고 네트워크가 빠른 경우 디스크를 사용하지 않는 복제 방식을 사용하는 것이 더 빠르게 복제 연결을 완료할 수 있는 방법이다.
기존에 디스크를 사용하는 복제를 사용했을 경우 RDB 파일이 생성되는 도중 다른 노드에서 복제 연결 요청이 들어오면 이 연결은 큐에 저장되며 기존 RDB 파일의 저장이 완료되면 여러 복제본이 한 번에 복제 연결을 시작할 수 있었다. 하지만 디스크를 사용하지 않는 방식에서 이미 하나의 복제본 노드로 복제 연결이 시작된 경우에는 복제 과정이 끝나기 전까지 다른 복제본 노드와의 연결을 수행될 수 없으며, 다른 복제본들은 하나의 복제 연결이 끝날때까지 큐에서 대기해야 한다. 이를 방지하기 위해 repl-diskless-sync-delay
옵션을 사용할 수 있다.
repl-diskless-sync-delay <delay-second>
새로운 복제 연결이 들어오면 일정 시간 기다린 뒤 복제 연결을 시작한다는 의미다. 이 기간 내에 또 다른 복제 연결이 들어오면 마스터 노드는 여러 복제본 노드로 소켓 통신을 연결해 한 번에 여러 개의 복제본 노드에 RDB 파일을 전송할 수 있다. 보통 네트워크가 유실돼 재동기화를 요청할 경우 마스터 노드에는 한 번에 여러 개의 복제본 노드에서 복제 연결이 들어노는 것이 일반적이기 때문에 이 옵션을 활성화하는 것이 좋다.
복제 ID
INFO REPLICATION
모든 레디스 인스턴스는 복제 ID를 가지고 있다. 복제 기능을 사용하지 않는 인스턴스라도 모두 랜덤 스트링 값의 복제 ID를 가지며, 복제 ID는 오프셋과 쌍으로 존재한다. 레디스 내부의 데이터가 수정되는 모든 커맨드를 수행할 때마다 오프셋이 증가한다.
# redis-cli INFO REPLICATION
# Replication
role:slave
master_host:redis-0
master_port:6379
master_link_status:up
master_last_io_seconds_ago:5
master_sync_in_progress:0
slave_read_repl_offset:616
slave_repl_offset:616
slave_priority:100
slave_read_only:1
replica_announced:1
connected_slaves:0
master_failover_state:no-failover
master_replid:922f1fa7a422229e0d6a58ac099001318b8cd210
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:616
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:1
repl_backlog_histlen:616
부분 재동기화
복제 연결이 끊길 때마다 마스터 노드에서 RDB 파일을 새로 내려 복제본에 전달하는 과정을 거친다면 네트워크가 불안정한 상황에서 복제 기능을 사용하는 레디스의 성능은 급격하게 나빠질 것이다. 이를 방지하기 위해 레디스는 부분 재동기화 기능을 사용해 안정적으로 복제 연결을 유지한다.
마스터 노드는 커넥션 유실을 대비해 백로그 버퍼라는 메모리 공간에 복제본에 전달한 커멘드 데이터들을 저장해 둔다. 하나의 복제 그룹에서 replication id
와 오프셋을 사용하면 복제본이 마스터 노드의 어느 시점가지 데이터를 가지고 있는지 파악할 수 있다. 만약 복제 연결이 잠시 끊긴 뒤 재연결되면 복제본 노드는 PSYNC
커맨드를 호출해 자신의 replication id
와 오프셋을 마스터 노드에 전달한다.
만약 오프셋 900의 복제본 노드가 마스터 노드에 재연결을 시도하는 경우라면, 오프셋 901~905의 내용이 마스터의 백로그에 저장돼 있다면 마스터는 RDB 파일을 새로 저장할 필요 없이 백로그에 저장된 내용을 복제본에 전달함으로써 재동기화를 진행할 수 있다. 하지만 마스터의 백로그 버퍼에 원하는 데이터가 남아 있지 않거나, 복제본이 보낸 replication id
가 현재의 마스터 노드와 일치하지 않다면 전체 재동기화를 시도한다.
Secondary 복제 ID
한 개의 복제본 그룹 내의 모든 레디스 노드는 동일한 복제 ID를 갖는다.
마스터 노드와 복제가 끊어짐과 동시에 복제본 노드는 새로운 복제 ID를 갖게 된다. 복제 ID가 동일하다는 것은 동일한 데이터셋을 갖는다는 의미다. 만약 복제가 끊어진 뒤에도 복본 노드가 기존의 복제 ID를 유지하며 마스터로 동작하다가 장애가 해결된 뒤 기존 마스터 노드가 다시 연결된다면 두 노드는 동일한 복제 ID, 동일한 오프셋이 동일한 데이터셋을 갖는다는 사실을 위반할 수 있다.
레디스가 2개의 복제 ID를 갖는 이유는 마스터로 승격되는 복제본 때문이며, 같은 복제 그룹 내에서 페일오버 이후 승격된 새로운 마스터에 연결된 복제본은 전체 재동기화를 수행할 필요가 없을 수 있다.
유효하지 않은 복제본 데이터
복제 구조에서 유효하지 않은 데이터란 복제본 노드의 데이터와 마스터 노드의 데이터가 정확하게 일치하지 않는 경우의 데이터를 의미한다. 레디스에서 복제본 노드가 마스터 노드와 연결이 끊어진 상태, 혹은 복제 연결이 시작된 뒤 아직 완료되지 않았을 경우에 복제본의 데이터가 유효하지 않다고 판단할 수 있다.
복제본의 데이터가 유효하지 않다고 판달될 때 복제본의 동작 방식은 replica-serve-stable-data
파라미터를 이용해 제어할 수 있다. 기본값은 yes
로, 복제본의 데이터가 유효하지 않다고 판단될 때에도 클라이언트로부터 들어오는 모든 읽기 요청에 데이터를 반환한다. 이 값을 no
로 설정한다면 INFO
, CONFIG
, PING
등의 일부 기본 커맨드를 제외한 모든 커맨드에 대해 SYNC with master in progress
라는 오류를 반환한다.
읽기 전용 복제본
복제본 노드는 읽기 전용 모드는 지원하며, replica-read-only
파리미터를 이용해 제어할 수 있고 기본값은 yes
다. 읽기 전용 복제본은 모든 쓰기 명령어를 거부한다. 따라서 실수로 복제본에 데이터를 쓸 수 없도록 보호된다.
레디스의 복제는 마스터 노드에서 발생한 변경 사항을 일반적인 명령어로 복제본에 전달하는 방식이다. 예를 들어 마스터 노드에서 키가 만료되면 복제본에는 DEL
명령어가 전송된다. 복제본에 동일한 키가 다른 상태라면 DEL
, INCR
, RPOP
같은 명령어의 결과가 마스터와 다르게 동작하거나 아예 실패할 수 있다.
레디스의 복제와 만료 키 처리 방식
레디스는 EXPIRE
같은 명령을 통해 TTL 기능을 제공한다. 하지만 마스터 노드와 복제본 노드의 시간이 완벽히 동기화되지 않기 때문에 레디스는 아래 3가지 기술을 사용하여 TTL 키 복제를 처리한다.
복제본 노드는 스스로 키를 만료시키지 않는다.
마스터 노드가 키를 만료시키거나, 해당 키에 대해 DEL
명령을 생성해 복제본에 전파한다. 복제본은 자체적으로 키를 만료시키지 않으며 마스터로부터 DEL
명령이 전달될 때까지 기다린다.
논리적 시계를 사용하여 임시 대응
마스터에서 DEL
명령을 보내지 않더라도 복제본은 논리적으로 이미 만료된 키가 있는 경우 읽기 요청에 대해 '존재하지 않는 키'로 응답한다. 이는 일관성을 해치지 않는 한도 내에서 동작한다.
Lua 스크립트 실행 중에는 키 만료가 일어나지 않음
레디스에서 Lua 스크립트를 실행할 때는, 스크립트 실행 시간 동안 시간이 멈춘 것처럼 처리한다. 즉, 스크립트 실행 도중 데이터셋 일관성 유지를 위해 키가 만료되지 않는다. 이는 스크립트를 복제본에 전달해 동일한 결과를 얻기 위해 반드시 필요한 조건이다.
백업을 사용하지 않는 경우에서의 데이터 복제
레디스에서 복제를 사용하는 경우 마스터 노드와 복제본 노드에서 백업 기능을 사용하는 것이 좋다. 만약 이 기능을 사용하지 않으려면 재부팅 후 레디스가 자동으로 재시작되지 않도록 설정하는 것을 권장한다.
장애 상황 예시
- 백업 기능을 사용하지 않는 마스터 노드와 복제본 노드가 존재한다.
- 마스터가 노드가 장애로 인해 종료됐지만, 레디스 프로세스를 자동 재시작하는 시스템에 의해 노드가 재부팅된다. 이때 메모리의 내용은 초기화된다.
- 복제본 노드에는 데이터가 존재하지만, 마스터 노드로의 복제 연결을 시도한다.
- 마스터 노드에서 복제본 노드로 빈 데이터셋을 전달한다.
만약 백업을 사용했다면 2번 상황에서 레디스가 재부팅될 때 백업 파일을 자동으로 읽어오기 때문에 데이터가 복원되며, 복원된 내용이 복제본으로 전달된다. 자동 재시작 기능을 사용하지 않았다면 복제본 노드에는 데이터가 존재하기 때문에 애플리케이션 연결 설정을 마스터 노드에서 복제본 노드로 변경해 데이터를 계속 사용할 수 있다. 혹은 복제본 노드에서 데이터를 새로 백업받아 마스터 노드에 전달한 뒤 마스터 노드를 시작시키면 복제본 노드에 저장된 내용으로 데이터가 복원될 수 있다. 따라서 데이터 안정성을 위해 복제 기능을 사용할 경우 백업 기능을 사용하는 것이 좋으며, 그렇지 않을 경우 마스터에서는 인스턴스의 자동 재시작을 활성화하지 않는 것을 권장한다.
도커를 활용한 복제 구성
version: '3.8'
services:
redis-0:
image: redis:latest
container_name: redis-0
ports:
- "6379:6379"
redis-slave-01:
image: redis:latest
container_name: redis-slave-01
ports:
- "6380:6379"
command:
- --replicaof redis-0 6379
redis-slave-02:
image: redis:latest
container_name: redis-slave-02
ports:
- "6381:6379"
command:
- --replicaof redis-0 6379
command
에 --replicaof <master-node> <master-node-port>
명령을 추가하는 방법을 통해 레디스 복제를 구성할 수 있다.
- 출처
- 개발자를 위한 레디스
- 레디스 공식 문서: https://redis.io/docs/latest/operate/oss_and_stack/management/replication/
'개발' 카테고리의 다른 글
레디스 클러스터 정리 (1) | 2025.05.12 |
---|---|
레디스 센티널 정리 (0) | 2025.05.09 |
What do you mean by “Event-Driven”? 정리 (0) | 2025.05.07 |
메시지 큐와 이벤트 스트림 (0) | 2025.05.02 |
배치 처리와 스트림 처리 (0) | 2025.04.30 |