들어가며: 키 삭제도 지연 시간이 될 수 있다

Redis에서 키를 삭제하는 일은 흔합니다. 하지만 삭제하려는 키가 수백만 개의 아이템을 가진 큰 Set이나 Hash라면 이야기가 달라집니다. 삭제 명령 하나가 Redis 서버를 몇 초 동안 붙잡을 수도 있습니다.

Redis는 대부분의 명령을 싱글 스레드(single-threaded) 로 처리합니다. 하나의 명령이 오래 걸리면 그 시간 동안 다른 요청도 기다려야 합니다. 애플리케이션 입장에서는 갑작스러운 지연 시간 증가로 보입니다.

이 글에서는 Redis의 삭제 명령어인 DELUNLINK가 어떻게 다른지, 운영 중인 서비스에서는 어떤 선택지가 있는지 정리합니다.

1. DEL 명령어: 동기적 삭제와 성능 저하 위험

DEL은 Redis의 가장 기본적인 키 삭제 명령어입니다. 이 명령어는 동기적(synchronous) 으로 동작하며, 키와 관련된 메모리가 완전히 해제될 때까지 블로킹(blocking) 됩니다.

DEL의 시간 복잡도

  • 단순 키 (String): 키 하나를 삭제하는 데 O(1)의 시간이 걸립니다.
  • 복합 키 (List, Set, Hash, Sorted Set): 키 하나를 삭제하는 데 O(M)의 시간이 걸립니다. 여기서 M은 해당 키가 포함하는 요소의 수입니다.

만약 100만 개의 멤버를 가진 Set을 DEL로 삭제한다면, Redis는 100만 개의 요소를 모두 순회하며 메모리를 해제하는 작업을 완료할 때까지 다른 모든 명령의 처리를 멈춥니다. 이는 마치 도서관 사서가 아주 두꺼운 책을 한 페이지씩 파쇄하는 동안, 다른 모든 방문객들이 하염없이 기다리는 것과 같습니다.

이런 블로킹은 처리량이 높은 서비스에서 예기치 않은 지연 시간(latency) 증가로 이어질 수 있습니다.

UNLINK는 Redis 4.0부터 도입된 명령어로, DEL의 블로킹 문제를 해결하기 위해 등장했습니다. UNLINK는 키 삭제 작업을 비동기적(asynchronous) 으로 처리합니다.

UNLINK의 동작 방식

UNLINK 명령어는 두 단계로 동작합니다.

  1. 키 공간에서 연결 해제 (Unlink): 먼저, 키를 Redis의 키 공간(keyspace)에서 즉시 제거합니다. 이 작업은 키의 크기와 상관없이 O(1)의 매우 빠른 시간 복잡도를 가집니다. 이 순간부터 클라이언트는 해당 키에 더 이상 접근할 수 없습니다.
  2. 메모리 해제 (Reclamation): 실제 메모리를 해제하는 작업은 별도의 백그라운드 스레드 에 위임됩니다. 메인 스레드는 즉시 다음 명령을 처리할 수 있게 되고, 무거운 메모리 해제 작업은 뒤에서 조용히 처리됩니다.

도서관 비유를 다시 사용하자면, 사서는 책을 파쇄기에 바로 넣는 대신, 도서 목록 카드만 즉시 제거하고(O(1) 작업) 책은 ‘나중에 파쇄할 책’ 바구니에 던져 넣습니다. 그리고 바로 다음 방문객의 요청을 처리하기 시작합니다. 실제 파쇄 작업은 다른 직원이 나중에 처리하게 됩니다.

이 비동기 방식 덕분에 UNLINK는 큰 키를 삭제할 때도 메인 스레드를 오래 붙잡지 않습니다.

UNLINK의 내부 최적화 사실 모든 UNLINK가 비동기로 처리되는 것은 아닙니다. 삭제하려는 키가 아주 작을 경우, 백그라운드 스레드에 작업을 넘기는 오버헤드가 오히려 더 클 수 있습니다. 그래서 Redis는 내부적으로 삭제 비용을 추산하여, 그 비용이 특정 임계값(LAZYFREE_THRESHOLD)을 넘을 때만 비동기 삭제를 수행하고, 그렇지 않으면 DEL과 동일하게 즉시 삭제합니다.

3. lazyfree-lazy-user-del 설정: 코드 수정 없이 DELUNLINK처럼

이미 운영 중인 서비스에서 모든 DEL 명령어를 UNLINK로 바꾸기는 부담스러울 수 있습니다. 이런 경우 Redis 6.0부터 제공되는 lazyfree-lazy-user-del 설정을 검토할 수 있습니다.

redis.conf 파일에 다음과 같이 설정하면,

lazyfree-lazy-user-del yes

클라이언트가 DEL 명령어를 보내더라도 Redis 서버가 내부적으로 UNLINK처럼 처리합니다. 즉, 코드 변경 없이 DEL에 비동기 삭제 동작을 적용할 수 있습니다.

정리 및 권장 사항

구분DELUNLINK
동작 방식동기적 (블로킹)비동기적 (논블로킹)
성능큰 키 삭제 시 서버 블로킹 가능키 크기와 무관하게 일정한 응답 시간
메모리 해제즉시백그라운드 스레드에서 처리
도입 버전1.0+4.0+

언제 무엇을 사용해야 할까요?

  • 새로운 애플리케이션 개발 시: 키의 크기가 커질 가능성이 있다면 기본적으로 UNLINK 를 사용하는 편이 안전합니다.
  • 기존 애플리케이션 유지보수 시: 키 삭제로 인한 성능 저하가 의심된다면 코드의 DELUNLINK로 점진적으로 변경하거나, 더 빠른 대응으로 lazyfree-lazy-user-del yes 설정을 검토할 수 있습니다.
  • 매우 작고 단순한 키 삭제 시: DEL을 사용해도 성능에 아무런 문제가 없습니다.

결론

DELUNLINK의 차이는 큰 키를 다루는 Redis 환경에서 꽤 중요합니다. 특히 짧은 지연 시간이 중요한 서비스라면, 삭제도 하나의 비용으로 보고 전략을 정해야 합니다. 새 코드에서는 UNLINK를 우선 검토하고, 기존 코드에는 lazyfree 설정을 단계적으로 적용하는 방식이 현실적입니다.


이 글은 AI의 도움을 받아 교정 및 정리되었습니다.