Incident Library

IL-0453 Hyperconnect · 2022-12-12 (게시일)databaseaws

하쿠나의 API latency 가 매일 ElastiCache Redis 백업 시간마다 치솟았다

하쿠나 서비스의 API latency 가 매일 특정 시간에 치솟았다. APM 으로 보니 병목은 Redis 였다. 배포나 트래픽 변화는 없었지만 스파이크 시간이 AWS ElastiCache 백업 시간과 맞았다. ElastiCache 는 가용 메모리가 충분하지 않으면 forkless 기반의 백업을 쓰고, 이 백업은 요청의 latency 와 throughput 을 나쁘게 만든다. Redis 메모리 사용량이 꾸준히 늘어 가용 메모리가 부족해진 것이 이때 와서 문제가 된 이유였다. Auto-failover 를 쓰는 클러스터에서 secondary 노드는 서비스 요청을 받지 않고 복제만 하므로 대부분 idle 상태다. 백엔드 팀은 백업을 이 secondary 노드로 옮겼다. 복제 지연 때문에 백업 데이터가 조금 어긋날 수 있지만, 하루 한 번 하는 백업에는 원래 그런 어긋남이 있으므로 감수할 수 있다고 판단했다.

메모리가 늘어난 원인을 찾으려고 팀은 scan 으로 모든 key 를 훑으며 key, TTL, 데이터 타입, 메모리 사용량을 파일에 적는 스크립트를 실행했다. 그 결과 domain_class 패턴의 set 과 domain_class:ID:phantom 패턴의 set 이 10GB 넘는 메모리를 쓰고 있었다. 하쿠나는 CrudRepository 를 쓰는 Spring Data Redis Repository 로 도메인 데이터를 다뤘다. 이 Repository 는 count 와 필드 값 조회를 빠르게 하려고, 데이터를 저장할 때마다 key 만 모은 set 과 @Indexed 필드의 index 를 따로 만든다. @TimeToLive 나 @RedisHash 의 timeToLive 로 TTL 을 걸면 데이터는 만료돼 지워지지만, key 를 모은 set 의 원소는 지워지지 않는다. 이 원소까지 지우려면 @EnableRedisRepositories(enableKeyspaceEvents = ON_STARTUP) 로 expired 이벤트를 받아야 한다. 이 설정을 켜면 저장할 때마다 phantom 사본도 함께 쓴다.

팀은 쓰지도 않는 CrudRepository 기능 때문에 복잡도를 높일 필요가 없다고 보았다. save 연산에 의도하지 않은 오버헤드가 있다는 점도 들어, 데이터를 RedisTemplate 으로 직접 다루기로 했다. 먼저 TTL 없이 저장되던 데이터에 TTL 을 걸었다. 이어서 Redis Hash CrudRepository 를 쓰던 곳을 RedisTemplate 기반의 CustomRepository 로 다시 구현했고, 쌓여 있던 필요 없는 데이터를 지우는 스크립트를 실행했다. 데이터베이스 3대에서 모두 합쳐 58GB 의 가용 메모리를 되찾았고, 메모리 사용량은 더 늘지 않았다.

심각도
–
영향 범위
Hakuna service API latency spiked during the daily ElastiCache backup
영향 사용자
–
지역
–
감지까지
–
복구까지
–

증상

하쿠나 서비스의 API latency 가 특정 시간에 주기적으로 치솟았다. APM 의 Dependency 별 Latency 분석에서 병목은 Redis 였다.

그 무렵 Redis 사용에 영향을 줄 만한 배포도 없었고 트래픽 패턴도 바뀌지 않아, 백엔드 팀은 처음에 근본 원인을 찾기 어려웠다. 그러다 latency 스파이크가 나는 시간이 AWS ElastiCache 백업 시간과 일치한다는 것을 알아냈다. 백업은 이전부터 주기적으로 돌고 있었는데 이때 와서야 문제가 생기기 시작했다.

추가로 모니터링해 보니 Redis 메모리 사용량이 이전부터 꾸준히 늘어 가용 메모리가 부족해져 있었다. ElastiCache 는 가용 메모리가 충분하지 않으면 forkless 기반의 백업을 고른다. 이 백업은 사용자 요청의 latency 와 throughput 을 나쁘게 만든다.

증상 지연 시간 증가메모리 압박

원인

촉발
가용 메모리가 부족한 상태에서, 하루 한 번 primary 노드에서 도는 ElastiCache 백업이 forkless 방식으로 실행됐다.

감지

특정 시간에 주기적으로 치솟는 API latency 를 그래프에서 보았고, APM 의 Dependency 별 Latency 분석으로 병목이 Redis 임을 알아냈다.

조치

후속 조치

–

교훈

백엔드 팀은 Redis 운영과 모니터링에 미흡한 부분이 있었다고 돌아봤다. 데이터 사이에 관계가 있거나 값으로 검색해야 하면 관계형 데이터베이스를 쓰고, 영속성이 필요한 데이터는 되도록 Redis 가 아닌 on-disk 기반 저장소에 둔다. ElastiCache Snapshot 을 만들어야 하면 가용 메모리를 50% 이상으로 유지한다. DEL collection 처럼 O(n) 인 명령 때문에 운영 Redis 가 멈추지 않게 조심한다. 애플리케이션과 데이터베이스 사이에 불필요한 I/O 가 없는지도 주기적으로 확인한다. Spring Data Redis Repository 는 secondary index 를 프레임워크가 관리해 주므로, TTL 이 필요 없고 쿼리 성능이 많이 요구되는 업무에서는 좋은 선택지가 될 수 있다.

출처

참고 문서

관련 사례

내 클러스터에도 있는지 확인하기

kubegap 은 Claude Code 나 Codex 에서 쿠버네티스 클러스터를 읽기만 하며 이 사례와 같은 상태를 점검합니다. 이 사례의 구성 요소: 인메모리 캐시, 데이터베이스 백업과 스냅숏. 점검 항목의 근거는 이 사례 같은 공개 보고서입니다.

AI 도구와 스크립트용

API 는 키가 필요 없습니다. MCP 서버는 search_incidents, get_incident, list_incidents, get_topic, vocab 도구를 내놓습니다. 사용법은 API 와 MCP.