565. 인 메모리 DB 디스크 백업 체크포인트 방식 지연 최소화 아키텍처
⚠️ 이 문서는 모든 데이터를 메인 메모리(RAM)에 올려 초고속으로 처리하는 인 메모리 DB(Redis, Memcached 등)가 전원 차단 시 데이터가 영구 증발하는 치명적 약점을 극복하기 위해, **메모리의 데이터를 주기적으로 하드디스크에 백업(Persistence)하는 과정에서 멈춤(Latency) 현상을 최소화하기 위해 고안된 '체크포인트(Checkpoint) 아키텍처 및 BGSAVE 백그라운드 처리 기법'**을 다룹니다.
핵심 인사이트 (3줄 요약)
- 본질: RAM은 전기가 끊기면 데이터가 날아가는 휘발성(Volatility) 메모리다. 따라서 인 메모리 DB라도 주기적으로 현재의 메모리 상태를 디스크에 사진 찍듯 '스냅샷(Snapshot)'으로 남겨두어야 재부팅 시 복원(RDB/AOF)이 가능하다.
- 가치: "잠깐 백업 좀 할게, 1분만 기다려!"라며 싱글 스레드로 도는 Redis가 고객의 쿼리 요청을 멈추고 100GB짜리 메모리 내용을 디스크로 쓰는 짓(병목)을 하면 장애가 터진다. 이를 백그라운드에서 처리해 고객 체감 지연을 제로에 가깝게 만든다.
- 기술 체계: 메인 프로세스는 계속 고객 쿼리를 받고, 리눅스의
fork()시스템 콜을 통해 쌍둥이 자식 프로세스를 복제해 낸 뒤 이 자식 프로세스가 디스크 저장을 전담하는 'COW (Copy-On-Write)' 기술이 백업 최적화의 심장이다.
Ⅰ. 인 메모리의 저주와 영속성(Persistence) 확보
RAM의 속도와 디스크의 안전성, 두 마리 토끼를 잡아야 한다.
- 인 메모리의 취약점:
- 게임 서버에서 실시간 랭킹 점수를 Redis 메모리에 1만 건 모아두었다. 그런데 서버 파워가 1초 나갔다 켜지면 1만 명의 점수가 영구 소멸한다.
- 영속성(Persistence) 2대 방식:
- RDB (Redis Database / 스냅샷): "매 1시간마다 전체 메모리의 모습을 통째로 사진 찍어 1개의 압축 파일(
.rdb)로 디스크에 저장해!"라는 방식이다. 복구는 빠르지만 1시간 사이의 데이터는 날아간다. - AOF (Append Only File): "데이터가 들어올 때마다 그 명령어(
SET score 100) 자체를 텍스트 파일(.aof)에 꼬박꼬박 추가해서 적어둬!"라는 방식이다. 데이터 유실은 없지만 나중에 복구할 때 수천만 줄의 명령어를 처음부터 다시 실행해야 해서 너무 느리다.
- RDB (Redis Database / 스냅샷): "매 1시간마다 전체 메모리의 모습을 통째로 사진 찍어 1개의 압축 파일(
- 병목의 공포:
- RDB 백업을 켤 때 문제가 생긴다. 100GB의 메모리를 디스크 파일 1개로 쓰는 데는 수십 초가 걸린다. Redis는 메인 스레드가 1개뿐인데, 이 백업을 메인 스레드가 직접 하면 수십 초 동안 어떤 사용자 명령도 받지 못하고 시스템이 마비된다 (Blocking).
📢 섹션 요약 비유: 현금출납부(메모리)를 엑셀로 엄청나게 빠르게 적고 있었는데, 갑자기 컴퓨터가 꺼질까 봐 불안해서 장부 전체를 종이(디스크)에 붓글씨로 베껴 쓰기 시작합니다(RDB 백업). 문제는 붓글씨를 쓰는 10분 동안 손님들(클라이언트)이 계산을 못 하고 줄 서서 마냥 기다려야 하는 엄청난 민폐 상황입니다.
Ⅱ. BGSAVE와 Copy-On-Write (COW)의 기적
멈추지 않으려면 내 분신을 만들어 시켜야 한다.
- 포크 (fork) 시스템 콜:
- Redis는
BGSAVE(Background Save) 명령이 떨어지면, 리눅스 커널의fork()함수를 호출한다. - 이 순간, Redis 메인 프로세스와 100% 완전히 똑같은 기억(메모리)을 가진 자식(Child) 프로세스가 순식간에 복제되어 태어난다.
- Redis는
- COW (Copy-On-Write) 메모리 마법:
- 100GB의 메모리를 복제한다고 진짜 100GB의 물리적 RAM 공간을 두 배로 잡아먹는 것이 아니다! (만약 그렇다면 서버 메모리가 터질 것이다.)
- 리눅스는 부모와 자식이 일단 '하나의 물리적 100GB 메모리 공간'을 같이 쳐다보게 둔다(포인터 공유).
- 자식 프로세스는 이 멈춰있는 과거의 100GB 메모리를 읽으며 느긋하게 디스크에 백업(
.rdb)을 굽는다.
- 수정 순간의 분리 (Write의 순간):
- 자식이 백업하는 와중에 사용자가 새로운 명령을 내려 100GB 중 1바이트를 수정(Write)하려고 한다.
- 이때 리눅스는 그 수정할 부분(Page)만 쏙 복사(Copy)해서 부모 프로세스에게 던져주고 그쪽을 수정하게 둔다.
- 자식 프로세스는 여전히 변경되기 전의 오리지널 100GB 풍경을 보면서 완벽한 정합성으로 백업을 무사히 마칠 수 있다. 부모의 서비스도 단 1초도 멈추지 않는다.
📢 섹션 요약 비유: 화가(메인 프로세스)가 빠르게 그림(데이터)을 그리고 있는데 보존용 복사본(백업)이 필요합니다. 그림 그리기를 멈추는 대신, 조수(자식 프로세스)에게 "지금 그림 상태 그대로를 카메라로 찍어라(fork)"라고 시킵니다. 조수가 셔터를 누르고 사진 현상(디스크 쓰기)을 하는 동안, 화가는 멈추지 않고 캔버스(COW) 위에 계속 덧칠을 해 나갈 수 있는 기적의 분업입니다.
Ⅲ. COW 아키텍처의 한계와 OOM (Out Of Memory) 폭탄
마법에는 반드시 대가가 따른다. 주의하지 않으면 서버가 뻗는다.
- 메모리 폭발 (Memory Double-up 위험):
- 만약 자식 프로세스가 디스크 백업을 굽는 데 5분이 걸린다고 치자.
- 이 5분 동안 블랙프라이데이 폭주가 터져서 메모리의 99% 데이터가 새로운 값으로
UPDATE되어버렸다(Write 폭풍). - 이렇게 되면 리눅스 커널은 수정된 모든 페이지를 복사(Copy-On-Write)해 주어야 하므로, 결국 물리적 메모리 사용량이 원래의 100GB에서 순식간에 2배인 200GB로 팽창해 버린다.
- OOM 킬러의 개입:
- 서버의 총 RAM이 150GB였다면? 200GB를 요구하는 순간 운영체제(OOM Killer)가 "메모리가 모자라 죽겠다!"며 Redis 메인 프로세스나 자식 프로세스의 목을 날려버린다(강제 종료).
- 아키텍처 설계의 결론:
- 인 메모리 DB를 운영할 때는 절대 서버 메모리를 100% 다 쓰면 안 된다. **BGSAVE(COW) 시 터질 메모리 팽창을 대비해 전체 메모리의 최대 50~60%만 사용하도록 제한(Maxmemory 세팅)**하고 나머지는 빈 공간으로 비워두는 것이 백업 지연을 최소화하면서 서버가 살아남는 절대 철칙이다.
📢 섹션 요약 비유: 복사기(COW)가 돌아가는 동안 화가가 물감을 한 방울 튀기는 정도는 감당할 수 있지만, 화가가 미쳐서 아예 캔버스 전체를 새 물감으로 다 덮어버리면(Write 폭풍), 조수는 옛날 그림과 새 그림 두 개를 동시에 들고 있어야 해서 팔이 부러지고 맙니다. 그래서 화실(서버 RAM)의 공간은 항상 원래 그림의 2배 크기를 비워두고 여유 있게 작업해야 안전합니다.