Redis의 데이터 영속성 (Persistence)

 

Redis는 모든 데이터를 메모리에 저장하기 때문에 빠르지만, 서버가 꺼지면 데이터도 함께 사라집니다. 이 글에서는 이 휘발성 문제를 해결하기 위한 RDB, AOF, 하이브리드 세 가지 영속성 방식을 중심으로, 각각의 동작 원리와 장단점을 정리해 보겠습니다.


왜 영속성이 필요한가?

Redis의 가장 큰 특징은 모든 데이터를 메모리에 저장한다는 점입니다. 디스크가 아닌 메모리에 데이터가 위치해 있기 때문에 마이크로초 단위의 빠른 응답을 제공합니다. 하지만 메모리는 휘발성이라 전원이 꺼지면 저장된 데이터가 모두 사라집니다.

 

서버 재시작이나 장애, 네트워크 문제, 심지어 실수로 프로세스를 종료하는 상황도 발생할 수 있는데요. 이런 경우 데이터를 디스크에 저장해두면 서버가 재시작되어도 다시 로드할 수 있습니다. 마치 게임을 하다가 중간에 저장하고 나중에 다시 로드하는 것과 비슷합니다.

 

다만 모든 상황에서 영속성이 필요한 건 아닙니다. Redis를 어떻게 활용하느냐에 따라 달라지는데요. 단순 캐시로만 사용한다면 영속성 설정 없이도 괜찮습니다. 원본 데이터가 다른 데이터베이스에 있고, Redis는 빠른 접근을 위해 복사본만 저장하는 구조라면 데이터가 사라져도 다시 채우면 되기 때문입니다.

 

하지만 사용자의 로그인 정보나 장바구니 내용, 포인트 잔액처럼 Redis에만 저장되는 데이터가 있다면 이야기가 달라집니다. 이 데이터가 영구히 손실된다는 건 곧 서비스 장애를 의미하고, 사용자 경험이 크게 나빠집니다. 이런 경우엔 영속성 설정이 필요합니다.

 

Redis는 기본적으로 세 가지 영속성 방식을 제공합니다. RDB, AOF, 그리고 둘을 결합한 하이브리드 방식인데요. 각각 장단점이 다르고 상황에 맞게 선택하거나 조합해서 사용할 수 있습니다.

 


RDB (스냅샷)

특징

RDB는 특정 시점의 메모리 데이터 전체를 하나의 바이너리 파일(`dump.rdb`)로 저장하는 방식입니다. 카메라로 사진을 찍는 것과 비슷한데요. 셔터를 누르는 순간의 장면이 이미지 파일로 저장되듯, RDB도 그 순간 Redis 데이터 전체를 파일로 저장합니다.

 

저장되는 파일은 바이너리 형식이라 사람이 읽을 수 있는 텍스트가 아닌, 컴퓨터만 처리할 수 있는 이진 데이터입니다. 그래서 파일 크기가 작고 로딩 속도도 빠릅니다.

 

다만 스냅샷 방식에는 근본적인 한계가 있습니다. 마지막 스냅샷 이후에 발생한 변경 사항은 다음 스냅샷까지 보존되지 않는데요. 예를 들어 5분마다 스냅샷을 만든다면, 최대 5분치 데이터를 잃을 수 있습니다. 스냅샷이 발생한 후 4분 40초쯤에 갑자기 서버가 다운되면, 그 4분 40초 동안의 모든 변경 사항이 사라지게 됩니다.

 

동작 방식

RDB 저장은 기본적으로 백그라운드 방식(`BGSAVE`)으로 동작합니다. 스냅샷을 만드는 동안에도 Redis 서버는 계속 클라이언트 요청을 처리해야 하기 때문입니다. 동작 흐름은 다음과 같습니다.

  1. Redis 서버(부모 프로세스)가 `fork` 시스템 콜로 자식 프로세스를 생성합니다.
  2. 자식 프로세스가 현재 메모리 데이터를 읽어서 디스크에 `dump.rdb` 파일로 씁니다.
  3. 부모 프로세스는 그 사이에도 계속 클라이언트 요청을 처리합니다.

 

사용자 입장에서는 스냅샷이 진행 중인지 알 필요가 없습니다.

 

여기서 핵심은 Copy on Write 기술입니다. `fork` 자체는 리눅스의 메모리 내용을 전부 복사하지 않는데요. fork가 일어난 직후에는 부모와 자식이 같은 메모리 페이지를 공유합니다. 자식 프로세스는 이 메모리를 읽어가면서 디스크에 쓰고, 부모 프로세스가 데이터를 추가로 변경하면 그때서야 해당 페이지만 복사됩니다.

 

그래서 쓰기가 많지 않은 일반적인 상황에서는 메모리 오버헤드가 크지 않습니다. 다만 정말 최악의 경우, 모든 데이터가 변경되는 상황이라면 메모리가 최대 2배까지 필요할 수 있는데요. 예를 들어 10GB 데이터가 있는데 전부 변경된다면 11GB 밖에 없는 환경에서는 위험한 상황이 될 수 있습니다.

 

장단점

장점

  • 복구가 빠릅니다. 바이너리 파일을 메모리에 로드하면 되기 때문입니다.
  • 파일 크기가 작습니다. 압축된 바이너리 형태라 같은 데이터라도 AOF보다 훨씬 작습니다.
  • 백업이 쉽습니다. 단일 파일이므로 AWS S3 같은 오브젝트 스토리지에 복사해두면 관리가 간편하고, 다른 서버로 복사하거나 날짜별로 백업하기도 편합니다.

 

단점

  • 스냅샷 사이의 데이터 손실 가능성이 있습니다.
  • `fork` 시 메모리 사용량이 증가할 수 있습니다. Copy on Write가 있더라도 쓰기가 많으면 메모리가 상당히 늘어납니다.
  • 대규모 데이터에서는 `fork`에 걸리는 시간도 늘어날 수 있어서, 그 동안 Redis에 문제가 발생할 수 있습니다.

AOF (Append Only File)

특징

AOF는 데이터를 변경하는 모든 명령을 로그 파일에 순차적으로 기록하는 방식입니다. RDB가 결과를 저장하는 것이라면, AOF는 과정을 저장한다고 볼 수 있습니다. 은행 거래 내역처럼 모든 변경 사항을 순서대로 기록합니다.

 

클라이언트가 데이터를 변경하는 명령을 실행하면 두 가지 일이 일어나는데요. 먼저 메모리에서 실제로 그 명령을 실행하고, 그 다음 방금 실행한 명령을 AOF 파일 끝에 추가합니다. `SET`이나 `DEL` 같은 쓰기 명령이 계속 이 파일에 기록됩니다. 읽기 명령은 데이터 변경이 없기 때문에 기록하지 않습니다.

 

서버가 재시작되면 AOF 파일을 열어서 처음부터 끝까지 모든 명령을 순서대로 재실행합니다. 일종의 히스토리 파일이라고 볼 수 있습니다.

동기화 전략

AOF 파일에 명령을 작성해도 바로 디스크에 저장되는 건 아닙니다. OS의 메모리 버퍼에 먼저 쌓이고, 이 버퍼를 언제 실제로 디스크에 쓸지를 결정하는 세 가지 전략이 있습니다.

 

Always

  • 모든 명령 실행 후 즉시 디스크에 저장합니다. 가장 안전하지만 그만큼 성능이 떨어지는데요. 금융 거래나 절대 잃어서는 안 되는 중요한 데이터에 적합합니다. 일반적인 웹서비스에서는 잘 사용하지 않습니다.

EverySecond

  • 1초마다 한 번씩 버퍼를 디스크로 저장합니다. 성능과 안전성의 균형이 좋아서 Redis에서도 권장하는 기본 설정입니다. 최대 1초의 데이터 손실 가능성이 있지만, 대부분의 서비스에서 이 정도는 허용 범위입니다.

No

  • OS에 맡기는 방식입니다. 리눅스는 보통 30초마다 저장하는 것으로 알려져 있는데요. 성능이 최우선이라면 데이터 손실을 어느 정도 감수해야 합니다.

 

재작성 (Rewrite)

AOF는 append only이므로 파일이 계속 커집니다. 삭제는 없고 추가만 되기 때문입니다. 시간이 지나면 수십, 수백 기가바이트까지 커질 수 있는데요. 게다가 같은 키에 1,000번 `SET`을 실행하면 파일에는 1,000개 명령이 모두 기록됩니다. 현실적으로는 마지막 명령 하나만 의미가 있음에도 나머지 999개가 함께 남아 있게 됩니다.

 

그래서 AOF에는 재작성(Rewrite) 과정이 있습니다. 현재 메모리 상태를 나타내는 최소한의 명령만 남기고 불필요한 명령을 제거합니다. 동작 흐름은 다음과 같습니다.

 

 

  1. `fork`로 자식 프로세스를 생성합니다.
  2. 재작성이 진행되는 동안, 부모 프로세스는 새로운 쓰기 명령을 기존 AOF 파일(실패 대비)과 재작성 버퍼(메모리 임시 저장) 두 곳에 씁니다.
  3. 자식 프로세스가 현재 메모리 상태를 기반으로 새로운 파일을 작성합니다.
  4. 자식이 작업을 마치면 부모에게 보고하고, 부모는 재작성 버퍼에 쌓인 명령을 자식에게 전달합니다.
  5. 자식이 해당 명령을 새 파일 끝에 추가합니다.
  6. 완성되면 원본 AOF 파일을 새 파일로 교체합니다.

 

재작성은 자동으로도 수행할 수 있습니다. 예를 들어 현재 파일 크기가 이전 대비 2배로 커지면 자동으로 재작성하도록 설정하는 방식입니다.

 

참고로 AOF 파일은 텍스트 형식이라 사람이 읽을 수 있습니다. 실수로 `FLUSHALL`을 실행했더라도 서버를 즉시 정지하고 AOF 파일을 열어서 마지막에 있는 `FLUSHALL` 명령을 삭제하면, 그 직전 상태로 복구할 수 있습니다.


하이브리드 (RDB + AOF)

RDB는 빠르지만 데이터를 잃을 수 있고, AOF는 안전하지만 느립니다. 두 방식의 장점만 취해서 조합한 것이 하이브리드 방식인데요. Redis 4.0부터 도입되었으며, RDB와 AOF를 하나의 파일에 결합합니다.

파일 구조

AOF 파일의 구조 자체가 바뀝니다. 파일 앞부분은 RDB 형식의 바이너리 스냅샷으로 저장되고, 뒷부분은 AOF 형식의 텍스트 명령으로 저장됩니다. 한 파일에 두 형식이 공존하는 구조입니다.

 

이 파일은 AOF의 재작성 과정에서 만들어지는데요. 흐름은 다음과 같습니다.

 

  1. `fork`로 자식 프로세스가 생성됩니다.
  2. 자식 프로세스는 현재 메모리 상태를 스캔해서 모든 키와 값을 RDB 형식(압축된 바이너리)으로 파일에 씁니다. 이것이 파일의 앞부분이 됩니다.
  3. 재작성 중에도 서버는 계속 동작하므로, 부모 프로세스는 새로운 명령을 기존 AOF 파일과 재작성 버퍼 두 곳에 씁니다.
  4. 자식이 RDB 부분을 다 쓰면 부모에게 보고하고, 부모는 버퍼에 쌓인 명령을 자식에게 전달합니다.
  5. 자식이 해당 명령을 AOF 형식으로 파일 끝에 추가합니다.
  6. 완성되면 원본 파일을 새 파일로 교체합니다.

 

복구 흐름

서버가 재시작되면 Redis는 파일을 열고 앞부분을 읽습니다. RDB는 바이너리이므로 Redis가 이를 바로 감지하고, RDB 로더를 사용해서 빠르게 메모리를 로드합니다. RDB 부분이 끝나면 AOF로 전환해서 남은 명령을 순차적으로 재실행하는데요. 이 명령들은 재작성 이후에 발생한 것들이므로 RDB 부분에 비해 매우 적습니다.

 

대부분의 데이터는 빠른 RDB 로딩으로 복구하고, 소수의 최신 명령만 AOF로 복구하는 구조입니다. 그래서 빠르면서도 안전한 복구가 가능합니다.

 

정리

세 방식의 핵심 차이를 비교하면 다음과 같습니다.

  RDB AOF 하이브리드
복구 속도 빠름 (바이너리 로드) 느림 (명령 전체 재실행) 빠름 (RDB 로드 + 소수 AOF 재실행)
데이터 안전성 스냅샷 간격만큼 손실 가능 동기화 전략에 따라 최소화
(EverySecond 기준 최대 1초)
RDB 수준의 속도 + AOF 수준의 안전성
파일 크기 작음 (압축 바이너리) 큼 (명령 히스토리 누적) AOF보다 작음 (앞부분이 압축 RDB)
복구 시 데이터 손실 마지막 스냅샷 이후 전부 EverySecond 기준 최대 1초 EverySecond 기준 최대 1초
도입 버전 - - Redis 4.0+

 

RDB만 사용하면 빠르지만 데이터를 잃을 수 있고, AOF만 사용하면 안전하지만 파일이 크고 복구가 느립니다. 하이브리드 방식은 두 방식의 장점을 결합해 빠른 복구와 데이터 안전성을 동시에 확보합니다. Redis에서도 이 방식을 권장하고 있습니다.

 

마무리

이번 글에서는 Redis의 세 가지 영속성 방식인 RDB, AOF, 하이브리드의 동작 원리와 장단점을 정리해 보았습니다. 단순히 데이터를 디스크에 저장하는 설정 정도로 생각하기 쉽지만, 정리하다 보니 `fork`와 Copy on Write, 동기화 전략, 파일 재작성처럼 운영 환경에서 반드시 이해해야 할 개념들이 생각보다 깊이 얽혀 있었습니다.

 

복구 속도를 높이면 데이터 손실 가능성이 커지고, 안전성을 높이면 파일 크기와 복구 시간이 늘어나는 것처럼 영속성에서도 트레이드오프가 분명하게 존재하는데요. 가장 안전한 옵션을 무조건 선택하기보다, 서비스 특성에 맞는 균형점을 찾아가는 것이 더 중요하다는 걸 느꼈습니다.

 

긴 글 읽어주셔서 감사합니다!