지난주 화요일 새벽 2시 47분
알림이 울렸다. 캐시 레이어 P99가 평소 8ms 수준에서 4초까지 튀었다는 것이다. 멘탈이 나갔다. 침대에서 노트북을 펴는데 손이 약간 떨렸다.
원인은 Redis Cluster slot resharding이었다. 평소처럼 야간 저트래픽 시간대에 노드 두 대를 추가하고 슬롯을 옮기는 작업이 돌아가고 있었는데, 이게 그냥 평범하게 끝나지 않았다. 몇 시간 동안 로그와 메트릭을 뒤지면서 알게 된 것들을 정리해둔다.
우리 환경
24노드 Redis Cluster (마스터 12, 레플리카 12). EKS 위에서 StatefulSet으로 운영 중이고, 키 수는 약 1.2억개, 메모리는 노드당 평균 28GB. 샤드 수가 늘어 일부 노드가 메모리 한계에 다다라서 노드를 추가하기로 했다. 새 노드 두 대 붙이고 CLUSTER ADDSLOTS, 그리고 기존 노드들에서 새 노드로 슬롯을 옮기는 평범한 절차였다.
마이그레이션 도구는 사내에서 만든 래퍼인데, 내부적으로는 결국 CLUSTER SETSLOT ... MIGRATING → MIGRATE 명령을 keys 단위로 돌리는 전통적인 방식이다.
무슨 일이 벌어졌나
처음 30분은 멀쩡했다. 그러다가 P99가 갑자기 튀기 시작했고, 어플리케이션 레벨에서는 MOVED 에러가 폭주하다가 가끔 timeout으로 죽는 요청이 나왔다. 트래픽이 적은 새벽이었는데도 그랬다.
세 가지가 동시에 일어났다는 게 결론이었다.
첫 번째. 마이그레이션 중인 슬롯의 키 중에 사이즈가 비정상적으로 큰 게 몇 개 있었다. MEMORY USAGE로 찍어보니 800MB짜리 hash 하나, 그리고 200MB 넘는 list 몇 개. 이게 한 번의 MIGRATE 호출에서 직렬화-전송-역직렬화로 처리되면서 마스터가 그 시간 동안 다른 명령을 못 받았다. Redis는 single-threaded니까.
이건 우리 잘못이다. Cluster 환경에서는 단일 키가 일정 사이즈를 넘지 못하게 가드해야 하는데, 어플리케이션 팀이 캐싱 레이어로 잘못 쓰고 있었다. 평소엔 hot path가 아니어서 안 보이다가, 슬롯 이동 시점에 폭탄이 터졌다.
두 번째. ASK 리다이렉션이 클라이언트 라이브러리에서 제대로 처리되지 않고 있었다. 정확히는 처리는 되는데, Lettuce 버전이 좀 오래된 거였고, ASK 리다이렉션 후 connection pool에서 새로 connection을 잡아서 지연이 추가됐다. 그게 평상시면 문제 없는데 첫 번째 이슈와 겹치면서 백프레셔가 누적됐다.
세 번째. 우리 사내 마이그레이션 도구가 한 슬롯이 끝날 때까지 기다리지 않고 다음 슬롯을 병렬로 옮기는 옵션이 켜져 있었다. 빠르게 끝내려고 누군가 켜놨던 것. 결과적으로 동시에 여러 슬롯이 MIGRATING 상태였고, 클라이언트가 받는 MOVED/ASK가 뒤섞이면서 라이브러리의 토폴로지 캐시가 자주 무효화됐다.
임시로 한 것, 그리고 새벽 4시
일단 진행 중이던 마이그레이션을 멈추는 건 위험하다. SETSLOT 상태가 어중간하게 남으면 더 큰 문제가 된다. 그래서 큰 키를 가진 슬롯만 우선 끝내고, 병렬도 1로 내렸다. 그 다음부터는 P99가 점진적으로 안정화됐다. 새벽 4시쯤 평소 수준으로 돌아왔다.
큰 키 문제는 어플리케이션 팀에 다음날 공유했고, 해당 캐시 키를 chunk 단위로 쪼개는 작업을 시작했다. 임시방편이지만 그 키들은 마이그레이션 전에 미리 작은 단위로 분할하기로 했다.
그래서 무엇이 바뀌어야 하나
이 사건 이후로 팀에서 몇 가지 논의가 오갔다.
가장 큰 화두는 Valkey로 옮기는 게 맞냐는 것이었다. 사실 작년부터 이야기가 나왔는데, 이번 사건이 결정타가 됐다. Valkey 8.0에서 Atomic Slot Migration이 들어가면서 ASK 리다이렉션이 사라지고, 클라이언트가 마이그레이션을 거의 인지하지 못하게 바뀌었다. 우리가 겪은 두 번째와 세 번째 이슈가 구조적으로 해소되는 셈이다. CLUSTER SETSLOT도 레플리카 동기 복제를 기본으로 한다고 하니, 토폴로지 일관성도 더 강해진다.
Redis 8.0에서도 engine-driven slot migration이 들어갔다는 건 알고 있었지만, 우리가 쓰는 OSS 라인업과 라이선스 이슈를 고려하면 Valkey 쪽이 더 자연스럽다. 아직 검증 중이긴 하다. 운영 환경에서 본격 도입하려면 클라이언트 라이브러리 호환성, 모니터링 도구 호환성을 더 봐야 한다.
큰 키 가드는 이미 시작했다. 어플리케이션에 들어가는 캐시 라이브러리에서 일정 사이즈(우리는 일단 10MB로 잡았다) 넘는 객체는 거부하거나 청크로 쪼개도록 강제한다. 운영 쪽에서도 redis-cli --bigkeys를 야간 배치로 돌려서 이상치를 미리 잡는 잡을 추가했다.
마이그레이션 도구의 병렬 옵션은 그냥 옵션 자체를 코드에서 지웠다. 이런 옵션은 누구도 안 켜야 안전하다.
남는 의문
아직 답을 못 찾은 것도 있다. 같은 환경에서 비슷한 사이즈의 클러스터를 운영하는 다른 팀은 왜 이런 사건을 겪지 않았을까? 우리 어플리케이션의 키 패턴이 유난히 한쪽으로 쏠려 있었던 것 같긴 한데, 정확히 무엇 때문인지는 더 봐야 한다.
혹시 비슷한 사건 겪으신 분, 아니면 Valkey atomic migration을 운영에서 이미 쓰고 계신 분 있으면 댓글로 경험 공유 부탁드립니다.
'IT > DB 운영' 카테고리의 다른 글
| Velero 1.15 데이터 무버 마이그레이션 삽질기 (0) | 2026.05.07 |
|---|---|
| PostgreSQL logical replication, 17/18 와서 다시 들여다본 이야기 (0) | 2026.05.04 |
| pg_stat_io로 새벽 3시에 vacuum I/O 폭탄 잡은 이야기 (0) | 2026.05.01 |
| PgBouncer transaction 모드에서 prepared statement 제대로 쓰는 법 (0) | 2026.04.29 |
| PostgreSQL 16 → 17 메이저 업그레이드, replication slot 살리려다 새벽을 태운 이야기 (0) | 2026.04.26 |