jongkwan.dev
개발 · Essay №064

Redis 영속성과 복제

한 노드를 되살리는 영속성(RDB·AOF)과 사본을 만드는 복제를 분리해, 데이터 손실 한계까지 정리합니다.

이종관2026년 3월 16일14 min read
Contents

영속성은 한 노드를 디스크로 복구하고, 복제는 여러 노드에 사본을 둡니다. 두 축은 풀어야 할 문제가 다릅니다.

영속성과 복제는 다른 문제

Redis는 데이터를 메모리에 두는 인메모리 저장소입니다. 프로세스가 죽으면 메모리도 사라지므로, 살아남기 위한 길이 두 갈래로 나뉩니다.

영속성(persistence)은 한 노드의 데이터를 디스크에 적어 재시작 후 되살리는 장치입니다. RDB와 AOF가 여기 속합니다. 복제(replication)는 같은 데이터를 다른 노드에 복사해, 한 노드가 죽어도 다른 노드가 이어받게 합니다.

둘을 가르는 잣대는 복구 목표 시점(RPO, Recovery Point Objective)입니다. RPO는 장애 때 허용하는 데이터 손실의 크기를 뜻합니다. 영속성은 디스크 fsync 주기로, 복제는 레플리카 지연(lag)으로 RPO가 정해집니다.

RDB 스냅샷

RDB(Redis Database Snapshot)는 특정 시점의 메모리 전체를 한 파일로 저장하는 방식입니다. 사진 한 장처럼 그 순간의 상태만 남깁니다.

저장 시점은 save 규칙으로 정합니다. save 900 1은 900초(15분) 안에 키가 한 번 이상 바뀌면 백그라운드 저장을 시작한다는 뜻입니다.

conf
save 900 1
dbfilename dump.rdb
dir ./

저장은 fork()로 자식 프로세스를 띄워 처리합니다. 자식이 메모리 스냅샷을 dump.rdb로 쓰는 동안, 부모는 그대로 클라이언트 요청을 받습니다. 덕분에 스냅샷 중에도 서비스가 멈추지 않습니다.

RDB는 복구가 빠릅니다. 단일 파일이라 백업과 복사가 쉽고, 대용량이라도 수 초 안에 다시 적재됩니다(학습 노트 기준 추정). 약점은 손실 구간입니다. save 900 1만 두면 최악의 경우 15분치 쓰기가 통째로 사라집니다.

비용도 있습니다. fork()는 큰 데이터셋에서 수 ms에서 1초까지 지연 스파이크를 만듭니다(추정). fork 뒤 기록 시 복사(copy-on-write)로 쓰기가 몰리면 메모리가 한때 두 배 가까이 튑니다.

text
메모리 피크 = 원본 + 쓰기량 × 스냅샷 시간
예) 8GB + (10MB/s × 10초) = 약 8.1GB

AOF 로그

AOF(Append-Only File)는 데이터를 바꾸는 모든 쓰기 명령을 순서대로 로그에 덧붙이는 방식입니다. 스냅샷이 결과를 저장한다면, AOF는 그 결과에 이른 과정을 저장합니다.

디스크 반영 시점은 appendfsync로 정합니다. 세 값이 내구성과 성능을 맞바꿉니다.

appendfsyncfsync 시점최대 손실특징
always명령마다거의 없음가장 느림
everysec1초마다최대 1초균형, 권장값
no운영체제에 위임버퍼 비울 때까지가장 빠름

표의 가운데 줄이 보통의 선택입니다. 프로덕션에서는 보통 everysec를 기본으로 잡습니다.

everysec는 1초마다 fsync를 호출하고, 그 순간 디스크 입출력 대기로 지연이 튑니다. 느린 HDD는 10100ms, 빠른 NVMe는 15ms 수준입니다(학습 노트 기준 추정). 손실을 1초로 줄이는 대가로 초당 한 번의 지연 스파이크를 받는 셈입니다.

로그는 계속 덧붙으니 파일이 수백 MB까지 커집니다. BGREWRITEAOF는 현재 데이터셋을 최소한의 명령 시퀀스로 다시 써서 파일을 줄입니다. 자식 프로세스가 새 파일을 만들고, Redis 7.0+는 이를 원자적으로 교체합니다. aof-load-truncated yes면 비정상 종료로 잘린 말미를 자동으로 떼어내고 적재합니다.

단점은 크기와 속도입니다. AOF 파일은 RDB의 5~10배까지 커지고(추정), 복구할 때 모든 명령을 재생하므로 RDB보다 느립니다.

RDB와 AOF 하이브리드

Redis 4.0부터 두 방식을 한 파일에 합칠 수 있습니다. aof-use-rdb-preamble yes 한 줄이면 AOF 리라이트가 만든 새 파일의 앞부분이 RDB 형식이 됩니다.

새 AOF 파일은 두 구역으로 나뉩니다. 앞쪽 RDB 프리앰블이 리라이트 시점의 전체 상태를 압축해 담고, 뒤쪽 AOF 명령들이 그 이후 변경만 기록합니다. 복구는 RDB를 빠르게 적재한 뒤, 짧은 AOF 꼬리만 재생하면 끝납니다.

conf
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes
방식최대 손실(RPO)복구 속도파일 크기
RDB스냅샷 간격(예: 15분)빠름작음
AOF(everysec)최대 1초느림
하이브리드최대 1초빠름중간

하이브리드는 RDB의 복구 속도와 AOF의 1초 손실 한계를 한 파일에서 얻습니다. 그래서 위 설정이 프로덕션 기본값으로 권장됩니다.

마스터-레플리카 복제

복제는 마스터 한 대의 쓰기를 레플리카(replica) 여러 대로 흘려보내 사본을 유지합니다. 읽기를 레플리카로 분산하고, 마스터가 죽으면 레플리카를 승격해 고가용성(HA)을 만듭니다.

새 레플리카가 처음 붙으면 전체 동기화로 시작합니다.

마스터는 스냅샷을 만들어 레플리카에 넘기고, 그 시점 이후의 쓰기 명령을 계속 스트림으로 보냅니다. 레플리카는 스냅샷을 적재한 뒤 명령을 따라 같은 상태를 유지합니다.

핵심은 이 전파가 기본적으로 비동기라는 점입니다. 마스터는 쓰기를 받자마자 클라이언트에 성공을 반환하고, 레플리카 반영은 그 뒤에 일어납니다. 그래서 마스터가 응답한 쓰기가 레플리카에 닿기 전 장애가 나면, 그 쓰기는 사라질 수 있습니다.

안전장치로 마스터에 조건을 걸 수 있습니다.

conf
min-replicas-to-write 1
min-replicas-max-lag 10

연결돼 있고 지연이 10초 이내인 레플리카가 1대 미만이면 마스터가 쓰기를 거부합니다. 사본 없는 쓰기를 막아 손실 위험을 줄입니다.

PSYNC와 복제 백로그

연결이 끊겼다 붙을 때마다 전체 동기화를 다시 하면 비쌉니다. PSYNC(부분 재동기화)는 끊긴 동안의 변경분만 다시 보내 이 비용을 피합니다.

마스터는 최근 명령을 복제 백로그(replication backlog)라는 원형 버퍼에 쌓아 둡니다. 레플리카가 곧 돌아오면 백로그에 남은 명령만 재전송합니다. 단절이 길어 백로그를 벗어났으면 전체 동기화로 떨어집니다.

conf
repl-backlog-size 256mb
repl-backlog-ttl 3600
구분트리거전송량소요
전체 동기화첫 연결·장기 단절RDB 전체 + 이후 명령수 분까지(추정)
부분 재동기화짧은 단절백로그 잔여 명령초 단위(추정)

백로그가 클수록, ttl이 길수록 부분 재동기화로 버틸 수 있는 단절 시간이 늘어납니다.

전체 동기화는 보통 RDB 파일을 디스크에 쓴 뒤 전송합니다. 디스크리스 복제는 이 파일 단계를 건너뛰고 메모리에서 소켓으로 바로 흘려보냅니다.

conf
repl-diskless-sync yes
repl-diskless-sync-delay 5

느린 디스크에서 유리하고, delay 동안 모인 여러 레플리카를 한 번에 동기화합니다. 다만 전송 중 네트워크가 끊기면 처음부터 다시 해야 합니다.

WAIT로 손실 줄이기

비동기 복제의 손실 창을 좁히고 싶을 때 WAIT 명령을 씁니다. WAIT numreplicas timeout은 쓰기가 지정한 수의 레플리카에 닿을 때까지, 또는 타임아웃까지 블록합니다.

python
await redis.set("important_key", "value")
acked = await redis.execute_command("WAIT", 2, 1000)
if acked < 2:
    # 1초 안에 2개 레플리카가 확인하지 못함
    ...

WAIT 2 1000은 두 레플리카가 확인할 때까지 최대 1초 기다리고, 실제로 확인한 레플리카 수를 돌려줍니다.

다만 WAIT도 강한 일관성을 보장하지 않습니다. 한 레플리카만 확인한 순간 마스터가 죽고, 확인하지 못한 레플리카가 승격되면 그 쓰기는 새 마스터에 없습니다. WAIT는 손실 가능성을 낮출 뿐, 없애지는 못합니다. 손실이 곤란한 데이터라면 Redis 단독 대신 합의 기반 저장소를 함께 둬야 합니다.

운영 기준과 모니터링

복제 상태는 INFO replication으로 확인합니다.

bash
redis-cli INFO replication
text
role:master
connected_slaves:2
slave0:ip=10.0.0.2,port=6379,state=online,offset=1000000,lag=0
repl_backlog_active:1
repl_backlog_size:268435456

role로 마스터와 레플리카를 구분하고, slave 줄의 lag으로 각 레플리카가 얼마나 뒤처졌는지 봅니다. lag이 꾸준히 크면 복제 손실 창이 그만큼 넓다는 신호입니다.

요구 수준에 맞춰 조합을 고르면 됩니다. 손실이 무관한 캐시 전용이면 영속성을 꺼도 됩니다. 재시작 복구만 필요하면 하이브리드(RDB 프리앰블 + everysec)로 충분합니다. 노드 장애까지 견뎌야 하면 하이브리드에 레플리카와 min-replicas 보호를 더합니다.

정리

영속성과 복제는 같은 내구성 문제의 다른 축입니다. 영속성은 한 노드를 디스크로 되살리고, 복제는 여러 노드에 사본을 둬 노드 장애를 견딥니다.

RDB는 빠른 복구와 작은 파일을 주지만 스냅샷 사이를 잃고, AOF는 everysec로 손실을 1초까지 줄이는 대신 파일이 큽니다. 하이브리드는 RDB 프리앰블로 두 장점을 한 파일에 모읍니다. 복제는 기본이 비동기라 응답한 쓰기도 사라질 수 있고, WAIT와 min-replicas는 그 창을 좁힐 뿐 없애지 못합니다. 손실 허용치(RPO)를 먼저 정하고, 그 수치에 맞춰 fsync 주기와 레플리카 보호를 고르면 설계가 흔들리지 않습니다.