본문으로 건너뛰기
개발 머꼬
개발 노트Redis
hohyeon.dev17

KEYS 한 줄 때문에 운영 Redis가 수백 밀리초 멈춘 이유

  • #Engineering Note
  • #Performance
  • #Redis

문제 발생

세션 정리 배치가 돌 때마다 서비스 전체 응답이 느려졌습니다.

const keys = await redis.keys("session:*");   // 200만 개 키 데이터베이스

Redis CPU는 100%가 아니었는데도 모든 요청의 지연이 함께 튀었습니다.

원인 분석

Redis는 명령을 하나씩 처리하고, KEYS는 그동안 전부를 세웁니다. 문서의 복잡도 표기가 O(N)이고, N은 데이터베이스의 키 개수입니다.

문서의 경고도 직접적입니다 — 운영 환경에서 이 명령을 쓸 때는 극도로 주의하십시오. 큰 데이터베이스를 대상으로 실행하면 성능을 망칠 수 있습니다. 이 명령은 디버깅과 키스페이스 레이아웃 변경 같은 특수 작업을 위한 것입니다. 일반 애플리케이션 코드에서 KEYS를 쓰지 마십시오.

그리고 대안까지 적어둡니다 — 키스페이스의 일부에서 키를 찾는 방법이 필요하다면 SCAN이나 set을 고려하십시오.

문서가 "엔트리급 노트북에서 100만 키를 40ms에 훑는다"고 덧붙이는 것이 오히려 함정입니다. 40ms 동안 다른 모든 클라이언트가 대기하고, 키가 몇 배가 되면 그대로 몇 배가 됩니다.

해결 방안

  1. 반복 조회에는 SCAN을 씁니다. 커서를 돌려주며 조금씩 훑기 때문에 서버를 세우지 않습니다.
let cursor = "0";
do {
  const [next, keys] = await redis.scan(cursor, "MATCH", "session:*", "COUNT", 100);
  cursor = next;
  // keys 처리
} while (cursor !== "0");
  1. SCAN의 보장 범위를 이해하고 씁니다. 전체 반복 동안 계속 존재한 요소는 최소 한 번 반환되지만, 같은 요소가 여러 번 반환될 수 있습니다. 중복에 안전한 처리(멱등)로 만들어야 합니다.

  2. COUNT는 힌트일 뿐입니다. 정확히 그만큼 돌려주지 않으며, 크게 잡으면 한 번의 호출이 길어집니다 — KEYS에 가까워지는 방향입니다.

  3. 애초에 스캔이 필요 없게 설계합니다. 문서가 함께 권하는 방법입니다 — 조회해야 할 키 목록을 set으로 따로 유지하면 SMEMBERS/SSCAN 한 번으로 끝납니다. 사용자별 세션 목록을 user:1:sessions 같은 set에 넣는 식입니다.

  4. 위험한 명령은 아예 막습니다. 운영 인스턴스에서 rename-command KEYS ""나 ACL로 차단하면, 누군가 디버깅하다 실수로 실행하는 사고를 구조적으로 없앨 수 있습니다. FLUSHALL도 같이 검토합니다.

  5. 정리 작업은 만료에 맡깁니다. 세션처럼 수명이 정해진 데이터는 TTL을 걸면 스캔해서 지울 일 자체가 없습니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.