들어가며
Redis가 무엇인지 물었을 때 제일 먼저 나오는 말은 “In-Memory Cache” 이나 성능을 올리기 위해 캐시 용도로 사용할 뿐, Redis 도 DB 와 같은 “Cache Store”(데이터 저장소) 이다.
때문에 Redis 는 데이터 Persistence(영속성)을 위해 프로세스가 내려갔을 경우의 유실에 대비하여 주기적으로 디스크에 데이터를 백업하고 재기동 시 복구하는 메커니즘을 가지고 있다.
백업 데이터 생성은 백그라운드로 이루어지기 때문에 문제가 생기면 놓치기 쉬운데, 이 때문에 운영하다가 다음과 같은 곤란한 상황을 맞게 되는 경우가 발생한다.
- 재기동 시 지옥의 로딩 시간
- 백업 데이터가 너무 커져서 메모리에 올리는 과정이 길어지는 상황 (적게는 수 초면 끝나지만 사이즈가 커지면 10분이 넘어가기도 한다.)
- 백업 데이터 생성 시 메모리 부족 또는 Disk I/O 병목
- 백업 데이터 생성을 위한 자식 프로세스 fork() 하면서 메모리 사용량이 최대 2배까지 급증할 수 있음
- 명령어를 백업하는 방식을 택했을 경우 데이터를 너무 자주 기록하여 Disk I/O 병목으로 전체적인 처리량 급감할 수 있다.
- 데이터 정합성 문제 (Partial Loss)
- 그럼에도 불구하고 백업 정책이 완전하지 않아 장애가 발생한 최근 상태는 유실 될 수 있다. (5분 주기로 백업하도록 설정되어 있는데 4분 59초에 장애가 발생했다면 최근 5분간의 데이터는 결국 유실됨)
이 리포트에서는 이런 상황들을 마주하지 않도록 Redis Persistence 관련 설정들을 살펴보고 데이터 백업 자체가 불필요한 경우 이를 끄고 메모리 캐시로서 사용하는 방법을 알아보고자 한다.
Redis Persistence
Redis는 데이터 유실을 방지하기 위해 메모리 상의 데이터를 주기적으로 디스크에 저장하며 저장 및 데이터 복구 방식은 다음과 같다.

AOF (Append Only File): 데이터가 변경되는 커맨드가 실행될 때 마다 (ex. WRITE/ UPDATE/ DELETE) AOF 파일에 기록하고 서버 재시작 시 AOF 파일을 처음부터 실행해서 데이터를 복구한다.

RDB (Redis Database)
별도 설정을 하지 않았다면 Redis 는 디폴트로 RDB 스냅샷을 생성하고 이를 통해 데이터를 복구한다. RDB 는 바이너리 포맷으로 저장하기 때문에 백업 데이터의 사이즈가 AOF 대비 상대적으로 작고 재기동이 빠르다.
RDB 관련 설정은 어느 시점에 스냅샷을 생성할 것이며 RDB 파일을 어떻게 저장할 것인가로 나뉜다.
1. RDB 스냅샵 생성 조건 설정
다음과 같이 RDB 스냅샷 생성 시점을 정의한다. 복수 개 지정이 가능하며 조건 중 하나라도 만족하면 RDB 스냅샷을 생성하기 시작한다.
| save <초> <key 변경 횟수> |

2. RDB 파일 속성 및 동작 설정

RDB 스냅샷 생성 조건을 하나라도 충족할 경우 혹은 명시적으로 BGSAVE 커맨드 실행 시 RDB 스냅샷 생성을 위한 자식 프로세스가 fork() 된다.
자식 프로세스가 생성되고 RDB 파일을 쓰는 동안 WRITE 요청이 오면 부모 프로세스는 기존의 memory page 를 copy 한 다음 해당 영역에 write 가 이루어지게 된다. (Copy-On-Write)

WRITE 요청이 많아지면 “Copy of page 3”와 같은 카피본이 점점 늘어나 메모리가 부족해지고 OOM(Out Of Memory)으로 인해 OS 에 의해 Kill 될 수 있다.
최악의 경우엔 redis 가 사용하는 maxmemory의 최대 2배까지도 메모리를 사용할 수 있어 RDB 파일 사용 시에는 maxmemory 값을 실제 가용 메모리의 45~50% 수준으로 설정하는 것을 권장하고 있다.
SAVE 커맨드를 통해 명시적으로 RDB 스냅샷 생성도 가능하다. 이 경우에는 부모 프로세스가 직접 RDB 를 생성하므로 COW 로 인한 메모리 문제는 피할 수 있지만 RDB 파일을 생성하는 동안에는 다른 명령은 처리하지 못한다.

RDB 파일은 먼저 임시 파일 (*.temp)에 기록 하고 완료 시 rename 한다. 기존에 존재하는 rdb.dump 파일을 건드리지 않아 안정적이지만 kill 등의 명령어로 redis 프로세스를 강제 종료했을 경우 임시 파일이 삭제되지 않고 남아있어 디스크 용량을 차지할 수 있으므로 SHUTDOWN 커맨드로 정상 종료 시키는 것이 바람직하다.
그 외에 디스크 부족 등의 이유로 RDB생성이 실패 했을 경우에도 임시 파일이 남아 있을 수 있는데 주기적으로 점검하여 임시 파일이 남아있지 않도록 관리가 필요하다.
이렇게 만들어진 RDB 스냅샷을 가지고 Redis 재기동 시 데이터를 복원하는데, 다만 이는 마지막 쓰기 성공한 RDB 파일 기준이며 RDB 파일 생성이 실패한 상태에서 재기동 시에는 가장 최근 데이터는 유실될 수 있다.

이러한 문제를 보완하기 위해 AOF 사용을 권장하고 있는데, AOF 는 데이터 변경이 이루어지는 커맨드 마다 AOF 파일에 로그를 남긴다. 1초마다 디스크에 동기화 되며 데이터 유실 가능성이 거의 없다.
AOF(Append Only File)
AOF를 사용하기 위해서는 설정을 활성화하고 AOF 파일을 어떻게 쓸 것인지 설정이 필요하다. 관련 설정은 다음과 같다.
1. AOF 활성화

2. AOF 파일 쓰기 속성 및 최적화 설정
AOF 파일을 쓰는 시점과 AOF 파일이 너무 커지는 것을 방지하기 위해 데이터를 최적화하는 REWRITE 동작에 관한 설정이다.

3. 데이터 복구 관련 설정
AOF를 사용한 복구 작업 관련 설정 내역은 다음과 같다.

AOF는 명령어를 기록하고 이를 순차적으로 실행하여 데이터를 복구한다. 때문에 AOF 파일 자체가 RDB 대비 크고 복구 시 느리다는 단점이 있다.
또한 하나의 key에 대해 100번의 업데이트가 일어났을 경우 최신 상태만 가져올 수 있는 RDB 방식 대비 100번의 업데이트를 일일이 수행해야 한다.
이런 단점을 보완하기 위해 Redis7 버전부터는 Multi-part AOF를 도입, RDB와 AOF 파일을 혼용하여 사용하고 있다.
AOF가 계속 커지는 것을 막기 위해 AOF REWRITE 가 이뤄지는데 이 때 상태를 스냅샷을 떠서 RDB 로 저장한다. 스냅샷을 뜬 이후 변경 분에 대해서만 AOF 에 기록한다. RDB 작성 중에도 AOF 파일에 변경 내역이 저장되므로 데이터 유실이 거의 없다.
AOF 기능을 활성화 하게 되면 지정한 AOF 디렉토리에는 3개의 파일이 생성된다.

- *.base.rdb: AOF REWRITE 시점의 스냅샷을 RDB로 저장 (또는 BGREWRITEOF 명령어로 명시적으로 스냅샷 생성)
- *.incr.aof: base.rdb 이후 변경 요청을 AOF 로 저장
- *.manifest: 최신 버전 관리를 위한 메타 파일
Multi-part AOF 도입으로 로딩 시 부하를 줄이고 데이터 유실도 최소화할 수 있지만 AOF/RDB 를 병행하여 사용할 경우 드물게 base.rdb 파일 REWRITE 시점과 RDB BGWRITE 시점이 겹치는 경우가 생길 수 있다. 이 때 둘 중 하나는 지연되거나 실패하는데 이러한 타이밍 이슈로 작업이 실패하면 데이터 유실이 될 수도 있고 백업 메커니즘이 정상화 되는 데 오랜 시간이 소요될 수 있어 설정에 주의를 필요로 한다.
지금까지 Redis 가 데이터 유실을 막기 위한 백업/ 복구 메커니즘과 설정 내역에 대해 살펴보았는데, Redis를 캐시 용도로만 사용할 경우엔 이런 백업 정책이 되려 부하로 다가오는 것이 사실이다.
Redis에 저장하는 데이터가 일회성이고 데이터 유지보다 빠른 속도가 더 중요한 상황이라면 Persistence 기능 자체를 비활성화 하는 것이 나을 수 있다.
이제 Redis를 캐시용도로만 사용할 경우에 어떻게 설정해야 하는지, 아울러 캐시 효율을 높이기 위한 설정은 어떤 것이 있는지 알아보고 마무리 하고자 한다.
Redis Persistence 설정을 끄고 캐시로만 사용하려면
다음과 같이 Persistence 관련 설정을 모두 비활성화 시킨다.
1. RDB 스냅샷 생성 이벤트 비활성화 : save 설정에 “”(빈문자열)을 설정하여 RDB 생성 이벤트가 발생하지 않도록 한다.
| save “” |
2. AOF 비활성화 : appendonly 설정을 no 로 지정한다.
| appendonly no |
Persistence 관련 설정이 모두 비활성화된 상태에서는 *.aof, *.rdb 파일이 생기지않고 redis 프로세스가 내려갔다가 다시 시작 되더라도 병목 없이 빠르게 기동 됨을 확인할 수 있다.
또한 RDB 파일을 생성하지 않기 때문에 maxmemory를 가용 메모리의 45% 수준으로 설정하던 것을 60~70%까지 상향 설정할 수 있다.
Replication 구성 시 유의 사항
Persistence 설정을 모두 비활성화 하더라도 Replication 구성을 했을 경우 Redis 는 데이터 복제를 위해 RDB를 생성한다. 복제 시 네트워크로 바로 데이터를 전송하도록 설정하여 불필요한 Disk I/O를 줄 일 수 있다.

캐시 효율적으로 사용하기
이제 불필요한 Disk I/O 없이 Redis가 In-Memory Cache로서 역할을 수행할 기반 작업이 끝났다. 속도도 향상되고 가용 메모리도 늘어났다.
그런데 Redis OOM Error: Memory Limit Reached 에러가 보인다.
Persistence 관련 설정을 끄고 가용 메모리(maxmemory 설정)를 늘렸다면 그 다음엔 메모리 관리 정책 (maxmemory-policy) 설정을 면밀히 살펴보아야 한다.
maxmemorypolicy 설정
maxmemorypolicy는 메모리에 데이터가 찼을 경우 key 삭제 정책에 관한 설정이다. 기본값은 noeviction으로 key를 삭제하지 않고 메모리가 꽉 차서 더 이상 쓸 수 없다는 에러를 발생시킨다. 데이터 유실을 방지하기 위함인데 캐시 용도로 사용하는 경우에는 오래된 데이터는 삭제하여 메모리를 확보하는 것이 더 효율적이다.
key 삭제 정책은 크게 LRU(Least Recently Used – 최근 미사용), LFU(Least Frequently Used- 최소 빈도 사용), Random(무작위), TTL(만료 시간) 기준으로 나뉘며 설정할 수 있는 값은 다음과 같다.

volatile-* 정책 지정 시 주의할 점은 key 설정 시 TTL(만료시간)을 설정해야 한다는 것이다. TTL 설정이 없으면 삭제 대상을 찾지못해 noeviction 과 똑같이 동작하게 된다. 다만 사용자가 일일이 만료 시간을 지정하기 번거롭기 때문에 allkeys-lru 설정이 가장 많이 쓰인다.
마치며
Redis는 기존 DB를 사용한 서비스의 병목을 개선하기 위하여 데이터를 메모리 상에서 처리하고 기존 메모리 캐시 시스템인 memcached 보다 다양한 자료 구조를 지원하기 위해 개발되었다. (방문자의 활동을 실시간으로 보여주어야 하는 서비스를 개발하는데 방문자의 활동 이력을 DB에 쓰고 정렬 조건과 함께 읽어 들이는 과정이 너무 느렸다.)
캐싱 용도로 개발되긴 했지만 서론에서 언급했다시피 Redis 는 단순 “Cache“가 아닌 “Cache Store” 이다.
세션 정보와 같은 실시간 데이터들을 만약 메모리 상에서만 가지고 있다면 노드가 재시작 될 때마다 해당 노드에 세션 데이터를 저장하고 있던 모든 사용자가 로그아웃 되어버릴 것이다.
또한 대규모 서비스에서 캐시 서버인 redis 가 재시작되어 DB의 원본 데이터를 읽어 들여야 한다면 전체 시스템 기동 시간을 늘어나고 DB에 부하를 줄 수 있다. (Cache Stampede 현상)
하지만 다음과 같이 DB와 무관하고 일회성 데이터 저장 용도로 Redis를 사용하는 경우에는 Persistence 설정을 끄는 것이 더 유리할 수 있다.
- 실시간 데이터 스트리밍 데이터를 전송하기 전 큐잉
- LLM 학습 파이프라인에서 중간 데이터 저장 혹은 상태 관리
- 재기동 시 DB에서 데이터를 일괄로 읽어오지 않아도 되는 경우 (순서/ 정렬 필요없이 1회 조회)
- 분산 환경 테스트 및 개발
Redis 에 저장할 데이터의 성격에 따라 Persistence 설정은 유용할 수도 부담일 수 있다. 상황에 맞는 설정으로 Redis를 효율적으로 사용하길 바란다.
# References
https://redis.io/docs/latest/operate/oss_and_stack/management/persistence/
https://redisgate.kr/redis/configuration/copy-on-write.php
https://redisgate.kr/redis/configuration/param_appendfsync.php
https://redis.io/docs/latest/develop/reference/eviction/
이주연 프로
오픈소스사업부 오픈소스기술팀
다년간의 플랫폼 개발 및 시스템 프로그래밍 경험을 바탕으로 현재 OSS 기술서비스 및 아키텍처를 담당하고 있습니다.
Register for Download Contents
- 이메일 주소를 제출해 주시면 콘텐츠를 다운로드 받을 수 있으며, 자동으로 뉴스레터 신청 서비스에 가입됩니다.
개인정보 수닙 및 활용에 동의하지 않으실 경우 콘텐츠 다운로드 서비스가 제한될 수 있습니다.
파일 다운로드가 되지 않을 경우 s-core@samsung.com으로 문의 주십시오.



