과릿 서버 에러 메시지 중 경험한 적 없던 에러를 발견하여, 발견부터 해결까지의 과정을 정리해보려고 한다.
현재 과릿은 아래와 같은 구조로 에러를 모니터링하고 있다.
슬랙을 통해 서버 에러 발생을 확인했으며, 해당 에러명이 RedisSystemException임을 통해 Redis 관련 오류가 발생했다고 판단했다.
Sentry에 접속해서, 자세한 에러 정보를 조회하니 아래와 같은 정보를 확인할 수 있었다.
처음보는 에러이기에, 중요도가 높은지/바로 해결이 가능한지를 판단하던 중 아래와 같은 카카오톡 채널 연락을 받게 되었다.
사용자분이 상세하게 캡쳐와 함께 문제 상황을 설명해주셨다.
별도로 문제가 발생한 상황을 파악하기 위한 코스트가 거의 없었다. 정말 감사하다🙏
사용자분이 겪은 상황을 앞뒤를 유추하여 정리하면 아래와 같다.
먼저 사용자분의 원활한 사용을 위해 자체적으로 임시 비밀번호를 초기화하여 드리고자 했으나, 초기화를 진행한다고 해도 사용자분이 서비스 사용이 불가능하다는 것을 판단해 오류를 확인해 임시 비밀번호 발급 및 업데이트를 진행하겠다고 전달드린 뒤 최대한 빠른 속도로 수정 배포를 진행하는 것으로 결정했다.
사용자분이 임시 비밀번호를 전달드려도 서비스 사용이 불가능한 이유는 우리의 로그인 로직과, 비밀번호 초기화 로직을 보면 알 수 있다.
로그인 로직
비밀번호 초기화 로직
위 두 이미지를 보면 두 로직 모두 Redis를 활용하여 값을 저장하고 있다. 즉, Redis의 오류가 해결되지 않으면 해당 로직은 정상적인 기능을 할 수 없다는 의미이다.
현재 과릿은 CoolSMS를 메세지 발송 서드파티로 이용하고 있다. CoolSMS 도입과정은 이 글을 통해서 확인할 수 있다.
정확한 에러는 아래와 같다.
RedisSystemException
Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to 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). Please check the Redis logs for details about the RDB error.
에러를 살펴보면, MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk.
라는 문장을 볼 수 있다. 이 문장을 직역하면, MISCONF Redis가 RDB 스냅샷을 저장하도록 구성되어 있지만, 현재 디스크에 지속적으로 저장할 수 없다.
는 의미다.
이 문장을 이해하기 위해서는 Redis의 백업본 저장 방식을 먼저 알아야 한다.
Redis는 메모리에 데이터를 저장함을 통해 빠른 속도의 읽기 및 쓰기 연산을 지원하는 장점이 있다. 다만 이 방식의 가장 큰 단점은 데이터의 휘발가능성이다. Redis는 이러한 단점을 커버하기 위해 메모리에 있는 데이터를 디스크에 백업하는 기능을 제공한다. 백업 방식은 크게 두 가지로 나뉜다.
Redis가 데이터의 영속성을 위하여 사용하는 방식에 대한 자세한 내용은 공식 문서를 통해서 더 확인할 수 있다.
1. RDB(Redis Database)
이 방식은 메모리에 있는 전체 데이터에 대한 스냅샷을 작성하고, 이를 디스크에 저장하는 방식이다.
장점
단점
스냅샷 저장 기준 (설정을 변경하지 않았을 경우)
스냅샷 저장 위치
2. AOF(Append Only File)
이 방식은 데이터가 변경되는 이벤트가 발생하면, 모두 다 로그에 저장하는 방식이다.
장점
단점
과릿의 Redis는 현재 RDB 방식으로 데이터의 영속성을 관리하고 있다. Redis의 RDB 스냅샷 방식은 RDB 스냅 샷이 실패 할 경우, write 중에 오류를 보고하도록 구성되어 있어서 데이터 수정 작업이 불가능해지는데, 디스크 용량 초과로 인하여 RDB 스냅 샷 저장에 실패하여 데이터 생성/수정이 불가능해진 것이다.
해결하기 위한 방법을 찾아보니, 아래의 명령어를 redis-cli에서 적용하는 것을 일반적으로 활용하는 것을 알아내었다.
config set stop-writes-on-bgsave-error no
위 명령어의 의미는 Redis 구성 매개변수인 stop-writes-on-bgsave-error를 no로 설정하는 것인데, 해당 매개변수는 Redis가 백그라운드에서 RDB 스냅샷을 만드는 과정 중 오류가 발생했을 때의 동작을 제어하는 매개변수다.
해당 변수가 yes(기본값)일 때는 오류가 발생하면, Redis는 더 이상 데이터 쓰기 작업을 진행할 수 없다. 그러나 no로 설정한다면, 오류가 발생해도 Redis는 계속해서 데이터 쓰기 작업을 진행할 수 있다.
해당 매개변수를 no로 변경하였을 때 발생할 수 있는 장/단점은 아래와 같다.
장점
단점
위와 같은 데이터 손실 위험이 있는 이유는 Redis 서버가 데이터 영속성을 관리하는 방식으로 RDB를 선택했을 경우의 동작 방식에 관련이 있다.
Redis 서버는 서버 재시작 시, 남아 있는 RDB 스냅샷 정보를 기반으로 데이터를 불러오게 된다.
RDB 스냅샷에 오류가 발생한 이후에 저장된 데이터들은 스냅샷에 기록되어 있지 않기 때문에 데이터 손실이 발생할 수 있는 것이다.
과릿 Redis에서는 (1)인증번호 (2)로그아웃된 블랙리스트 토큰 (3)Refresh Token 등의 정보를 저장하고 있는데, 해당 3종류의 데이터 모두 휘발성 데이터임과 더불어 Redis 서버 재시작 가능성이 현저하게 낮기 때문에, 해당 변수를 no로 설정하기로 결정했다.
과릿은 서버 비용 경감을 위해 Free Tier의 EC2 한 대 안에 API Server와 Redis Server가 Docker를 활용해 구동하고 있다.
그렇기에, ssh를 통해 EC2 접속하여 docker exec 명령어를 활용해 redis-cli에 접근하고자 했다.
그런데, 아래와 같은 오류가 발생했다.
docker no space left on device
근본적인 원인을 위 오류를 통해서 알 수 있게 된 것이다. Spring Boot Server의 로그가 굉장히 많이 쌓여 저장 공간이 부족해진 것이였고, 이에 Redis Server가 영향을 받은 것이었다.
그래서, Spring Boot Server 오래된 로그 파일을 지웠으며, Redis의 stop-writes-on-bgsave-error 또한 no로 변경을 진행했다.
해당 게시글을 작성하면서 추가적인 정보를 파악하던 중, logrotate와 crontab 기반으로 주기적으로 컨테이너 로그를 지우는 방법을 알게 되었다.
그래서, 다음 글로 logrotate와 crontab을 이용해 오래된 컨테이너 로그를 지우는 과정을 정리하면서 stop-writes-on-bgsave-error의 값을 다시 yes로 돌려 안정성을 높이는 과정을 정리했다.
레퍼런스
처음 보는 이슈에요! 우와 신기해요! 유용한 내용 감사해요 : )