Redis
Redis는 많은 사용자를 가지고 있는 In-Memory Cache 솔루션 중 하나입니다. 사용처가 다양한 만큼 이슈도 많이 발견이 되고 있는데요, 이번에는 너무 높은 operation으로 인해 발생한 이슈를 해결했던 과정에 대해 공유하려고 합니다.
redis-cli의 stats 명령을 통해 가져올 수 있는 항목 중 instantaneous_ops_per_sec라는 항목이 있습니다. 이 항목은 Redis 서버가 초당 처리하고 있는 operation의 수를 의미합니다. 싱글 스레드로 동작하기 때문에 CPU MHz에 따라 다르겠지만 보통은 8만~10만 정도 되면 많은 편에 속합니다. 서비스에 장애가 나던 당시에는 이 값이 거의 20만에 육박했습니다. 20만이 되는 operation을 견디면서 Redis의 응답 시간이 조금씩 느려졌고 이는 서비스 전체의 응답 속도 저하를 가져왔습니다.
MONITOR를 이용한 operation의 분석
문제를 해결하기 위해서 Redis 서버에서 어떤 연산들이 이루어지는지를 확인할 필요가 있었습니다. MONITOR 명령을 이용해서 분석해 보면, WRITE와 관련된 SET 종류의 연산이 10%가 채 안되었고, 나머지는 EXISTS, GET 종류의 READ 연산이 차지하고 있었습니다. 즉, READ 연산 만이라도 분리를 할 수 있다면 operation으로 인한 응답 저하 현상을 피할 수 있다는 결론을 얻었습니다. 그렇다면 READ 연산을 어떻게 분리하는 게 좋을까요?
해결하기 위해 만든 시스템 구성은 아래와 같습니다.
WRITE는 RHA를 적용한 M-S 세트를 바라보게 설정합니다. (RHA에 대한 자세한 설명은 https://brunch.co.kr/@alden/23 이곳을 참고하시면 됩니다.)
그리고 READ를 하기 위한 Redis 서버들을 L4 로드 밸런서 밑에 scale-out 하게 늘릴 수 있는 구조로 붙인 후 RHA 도메인의 slave로 붙입니다.
slaveof rhd.domain 6379
RHA의 Master 서버가 아닌 RHA 도메인을 설정 함으로써 Master 서버가 죽어도 READ 용 slave 서버들의 config를 수정하지 않을 수 있게 됩니다. slave 서버들이 바라보던 RHA 도메인의 IP 주소가 RHA Slave Redis 서버로 넘어가기 때문입니다. 이렇게 하면 사용량이 늘어나도 상대적으로 많은 비율을 차지하는 READ 연산은 slave 서버를 증설함으로써 scale-out 하게 증설할 수 있어서 대응도 빠르게 할 수 있습니다.
이전 글에서도 이야기했지만 TCP 기반의 서비스를 로드 밸런서에 적용할 때는 garbage 커넥션을 주의해야 합니다. (TCP Keepalive와 관련된 https://brunch.co.kr/@alden/9 이곳을 참고하시면 됩니다.) 클라이언트와 READ 용 slave 서버들 간에 반드시 tcp_keepalive를 이용해서 연결 유지를 하도록 설정해야 합니다. 그래야 garbage 커넥션이 생기지 않습니다. 대부분의 클라이언트들은 기본으로 tcp_keepalive를 사용하도록 되어 있지만 그렇지 않은 클라이언트들도 많기 때문에 반드시 확인하셔서 사용해야 합니다.
혹시라도 Redis의 operation 처리 성능 한계 때문에 어려움을 겪으신 분들에게 도움이 되었으면 좋겠습니다.