KEYS 한 줄 때문에 운영 Redis가 수백 밀리초 멈춘 이유
문제 발생
세션 정리 배치가 돌 때마다 서비스 전체 응답이 느려졌습니다.
const keys = await redis.keys("session:*"); // 200만 개 키 데이터베이스Redis CPU는 100%가 아니었는데도 모든 요청의 지연이 함께 튀었습니다.
원인 분석
Redis는 명령을 하나씩 처리하고, KEYS는 그동안 전부를 세웁니다. 문서의 복잡도 표기가 O(N)이고, N은 데이터베이스의 키 개수입니다.
문서의 경고도 직접적입니다 — 운영 환경에서 이 명령을 쓸 때는 극도로 주의하십시오. 큰 데이터베이스를 대상으로 실행하면 성능을 망칠 수 있습니다. 이 명령은 디버깅과 키스페이스 레이아웃 변경 같은 특수 작업을 위한 것입니다. 일반 애플리케이션 코드에서 KEYS를 쓰지 마십시오.
그리고 대안까지 적어둡니다 — 키스페이스의 일부에서 키를 찾는 방법이 필요하다면 SCAN이나 set을 고려하십시오.
문서가 "엔트리급 노트북에서 100만 키를 40ms에 훑는다"고 덧붙이는 것이 오히려 함정입니다. 40ms 동안 다른 모든 클라이언트가 대기하고, 키가 몇 배가 되면 그대로 몇 배가 됩니다.
해결 방안
- 반복 조회에는
SCAN을 씁니다. 커서를 돌려주며 조금씩 훑기 때문에 서버를 세우지 않습니다.
let cursor = "0";
do {
const [next, keys] = await redis.scan(cursor, "MATCH", "session:*", "COUNT", 100);
cursor = next;
// keys 처리
} while (cursor !== "0");-
SCAN의 보장 범위를 이해하고 씁니다. 전체 반복 동안 계속 존재한 요소는 최소 한 번 반환되지만, 같은 요소가 여러 번 반환될 수 있습니다. 중복에 안전한 처리(멱등)로 만들어야 합니다. -
COUNT는 힌트일 뿐입니다. 정확히 그만큼 돌려주지 않으며, 크게 잡으면 한 번의 호출이 길어집니다 —KEYS에 가까워지는 방향입니다. -
애초에 스캔이 필요 없게 설계합니다. 문서가 함께 권하는 방법입니다 — 조회해야 할 키 목록을 set으로 따로 유지하면
SMEMBERS/SSCAN한 번으로 끝납니다. 사용자별 세션 목록을user:1:sessions같은 set에 넣는 식입니다. -
위험한 명령은 아예 막습니다. 운영 인스턴스에서
rename-command KEYS ""나 ACL로 차단하면, 누군가 디버깅하다 실수로 실행하는 사고를 구조적으로 없앨 수 있습니다.FLUSHALL도 같이 검토합니다. -
정리 작업은 만료에 맡깁니다. 세션처럼 수명이 정해진 데이터는 TTL을 걸면 스캔해서 지울 일 자체가 없습니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.