redis에서 며칠에 한 번씩 쓰기가 MISCONF로 막힙니다
세션/캐시 겸 일부 랭킹 데이터를 Redis에 올려두고 쓰는데, 며칠에 한 번씩 갑자기 쓰기 명령이 전부 실패합니다. 애플리케이션(Spring Data Redis) 쪽에서 SET 계열이 아래 에러로 떨어집니다.
MISCONF Redis is configured to save RDB snapshots, but it is currently
not able to persist on disk. Commands that may modify the data set are
disabled, because this instance is configured to report errors during
writes if RDB snapshotting fails (stop-writes-on-bgsave-error option).redis 로그를 보면 그 시점에 이런 라인이 찍혀 있습니다.
* Background saving started by pid 14122
# Can't save in background: fork: Cannot allocate memory
# Background saving error기동 로그에는 처음부터 이 경고도 있었습니다.
# WARNING overcommit_memory is set to 0! Background save may fail
under low memory condition. To fix this issue add
'vm.overcommit_memory = 1' to /etc/sysctl.conf ...환경은 Ubuntu 22.04, Redis 7.0, EC2 t3.small(메모리 2GB, 스왑 없음)입니다. 이상한 건 에러가 날 때 free로 보면 여유 메모리가 남아 있고, df -h로 봐도 디스크는 넉넉하고, dump.rdb가 있는 디렉터리 권한도 redis 소유로 정상입니다. redis-cli config get save 값은 기본값 그대로입니다. redis를 재시작하면 한동안은 멀쩡하다가 다시 같은 증상이 반복됩니다.
메모리도 디스크도 남아 있는데 왜 fork에서 Cannot allocate memory가 나고 저 MISCONF 상태로 빠지는 건지, 어디부터 봐야 할지 감이 안 잡힙니다. 짚어주실 분 계실까요.
증상만 보면 디스크나 권한 문제로 오해하기 쉬운데, 지금 사례는 RDB 스냅샷을 쓰기 위한 fork()가 커널의 오버커밋 판정에 막혀서 실패하는 것이 근본 원인으로 보입니다. 로그의 세 줄이 그대로 인과관계입니다. fork에서 Cannot allocate memory가 나서 BGSAVE가 실패하고, 기본값인 stop-writes-on-bgsave-error yes 때문에 그 다음부터 데이터 변경 명령이 전부 막히면서 MISCONF로 떨어지는 겁니다.
왜 free로 메모리가 남아 보이는데도 fork가 실패하냐가 핵심인데요. Redis 공식 FAQ와 관리 문서에 나오는 동작입니다. Redis는 백그라운드 저장을 fork()로 자식 프로세스를 만들어 copy-on-write로 처리합니다. 이론상 저장 중에 부모의 모든 페이지가 바뀔 수 있기 때문에, 리눅스는 자식이 최대 얼마나 쓸지 미리 알 수 없습니다. 이때 vm.overcommit_memory 값이 0이면 커널은 "부모 메모리를 통째로 복제해도 될 만큼 여유 RAM(+스왑)이 있는가"를 휴리스틱으로 판정하고, 그 조건을 못 채우면 실제로는 복사되지 않을 페이지가 대부분이라도 fork 자체를 ENOMEM으로 거부합니다. 그래서 free의 여유값과 상관없이 실패합니다. 기동 로그에 처음부터 뜬 "overcommit_memory is set to 0" 경고가 바로 이 얘기입니다.
며칠에 한 번씩, 재시작하면 한동안 멀쩡한 점도 같은 원리로 설명됩니다. 재시작 직후에는 Redis RSS가 작아서 오버커밋 판정을 통과하지만, 시간이 지나며 데이터와 단편화로 RSS가 커지면 t3.small(2GB, 스왑 없음) 기준으로 "현재 RSS를 통째로 복제할 여유"를 못 만들게 되고, 그 순간부터 BGSAVE가 계속 실패하는 것으로 보입니다. 스왑이 없어서 판정이 더 빡빡해지는 것도 한몫합니다.
해결은 공식 관리 문서가 권고하는 대로 오버커밋을 1로 바꾸는 것입니다. 영구 적용과 즉시 적용을 같이 해두세요.
# 즉시 적용
sudo sysctl vm.overcommit_memory=1
# 영구 적용
echo 'vm.overcommit_memory = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# 확인 (1이어야 함)
cat /proc/sys/vm/overcommit_memory같은 문서 Memory 항목에서 스왑을 켜두라고 권고합니다. 2GB에 스왑이 없으면 fork 실패뿐 아니라 OOM killer로 Redis가 통째로 죽을 위험도 있으니, 이참에 스왑 파일도 잡아두시길 권합니다.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# 부팅 유지
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab추가로 maxmemory를 명시해 두시는 걸 권합니다. 문서에서 데이터 외 오버헤드와 단편화를 감안해 실제 가용 메모리보다 낮게(예: 여유가 10GB면 8~9GB로) 잡으라고 안내합니다. 게다가 쓰기가 많은 상황에서 RDB 저장 중에는 Redis가 평소의 최대 2배 메모리를 쓸 수 있다고 명시돼 있으니, 2GB 인스턴스라면 maxmemory를 넉넉히 낮춰 잡아 fork 시점 여유를 확보하는 편이 안전합니다.
# 예시 값이며 워크로드에 맞게 조정하세요
redis-cli config set maxmemory 1200mb
redis-cli config set maxmemory-policy allkeypolicy_확인_후_설정
# redis.conf 에도 동일하게 반영MISCONF 상태 자체는 BGSAVE가 한 번 성공하면 자동으로 풀립니다. 오버커밋을 1로 바꾼 뒤 아래로 강제 저장을 한 번 태워보시면 곧바로 쓰기가 다시 열리는지 확인할 수 있습니다.
redis-cli bgsave
# 잠시 후 상태 확인 (rdb_last_bgsave_status:ok 여야 정상)
redis-cli info persistence | grep -E 'rdb_last_bgsave_status|rdb_bgsave_in_progress'참고로 stop-writes-on-bgsave-error no 로 꺼서 에러를 덮는 방법은 권하지 않습니다. 그렇게 하면 MISCONF는 안 뜨지만 스냅샷이 계속 실패하는 상태로 조용히 데이터 유실 위험만 커집니다. 원인인 fork를 고치는 게 맞습니다. 만약 이 인스턴스에 애초에 디스크 영속화가 필요 없다면 save를 비우는 선택지도 있지만, 랭킹 데이터가 있으시니 오버커밋+스왑 조정 쪽이 맞아 보입니다.
확인 순서는 (1) /proc/sys/vm/overcommit_memory 가 1인지, (2) redis-cli info memory 로 used_memory_rss와 maxmemory 대비 여유, (3) 재현될 때 dmesg에서 fork 관련 로그 정도만 보시면 됩니다. 관련 공식 문서는 아래를 참고하세요.
🔗 https://redis.io/docs/latest/operate/oss_and_stack/management/admin/
로그인 후 답글을 남길 수 있습니다.