Redis 키 삭제: DEL, UNLINK, 그리고 lazyfree-lazy-user-del의 차이점
들어가며: 키 삭제, 간단하지만 위험할 수 있는 작업
Redis에서 키를 삭제하는 것은 매우 흔하고 기본적인 작업입니다. 하지만 만약 삭제하려는 키가 수백만 개의 아이템을 가진 거대한 Set이나 Hash라면 어떻게 될까요? 이 간단한 삭제 명령 하나가 전체 Redis 서버를 몇 초간 멈추게 할 수도 있습니다.
Redis는 대부분의 명령을 싱글 스레드(single-threaded) 로 처리하기 때문에, 하나의 명령이 오래 실행되면 그 시간 동안 다른 모든 요청이 대기해야 합니다. 이로 인해 애플리케이션 전체의 응답성이 저하되는 심각한 문제가 발생할 수 있습니다.
이 글에서는 Redis의 대표적인 삭제 명령어인 DEL과 UNLINK의 차이점을 통해, 어떻게 하면 키 삭제 작업을 더 안전하고 효율적으로 수행할 수 있는지 알아보겠습니다.
1. DEL 명령어: 동기적 삭제와 성능 저하 위험
DEL은 Redis의 가장 기본적인 키 삭제 명령어입니다. 이 명령어는 동기적(synchronous) 으로 동작하며, 키와 관련된 메모리가 완전히 해제될 때까지 블로킹(blocking) 됩니다.
DEL의 시간 복잡도
- 단순 키 (String): 키 하나를 삭제하는 데 O(1)의 시간이 걸립니다.
- 복합 키 (List, Set, Hash, Sorted Set): 키 하나를 삭제하는 데 O(M)의 시간이 걸립니다. 여기서 M은 해당 키가 포함하는 요소의 수입니다.
만약 100만 개의 멤버를 가진 Set을 DEL로 삭제한다면, Redis는 100만 개의 요소를 모두 순회하며 메모리를 해제하는 작업을 완료할 때까지 다른 모든 명령의 처리를 멈춥니다. 이는 마치 도서관 사서가 아주 두꺼운 책을 한 페이지씩 파쇄하는 동안, 다른 모든 방문객들이 하염없이 기다리는 것과 같습니다.
이러한 블로킹 현상은 특히 높은 처리량이 요구되는 실시간 서비스에서 예기치 않은 지연 시간(latency) 급증의 원인이 될 수 있습니다.
2. UNLINK 명령어: 비동기적 삭제로 성능 확보
UNLINK는 Redis 4.0부터 도입된 명령어로, DEL의 블로킹 문제를 해결하기 위해 등장했습니다. UNLINK는 키 삭제 작업을 비동기적(asynchronous) 으로 처리합니다.
UNLINK의 동작 방식
UNLINK 명령어는 두 단계로 동작합니다.
- 키 공간에서 연결 해제 (Unlink): 먼저, 키를 Redis의 키 공간(keyspace)에서 즉시 제거합니다. 이 작업은 키의 크기와 상관없이 O(1)의 매우 빠른 시간 복잡도를 가집니다. 이 순간부터 클라이언트는 해당 키에 더 이상 접근할 수 없습니다.
- 메모리 해제 (Reclamation): 실제 메모리를 해제하는 작업은 별도의 백그라운드 스레드 에 위임됩니다. 메인 스레드는 즉시 다음 명령을 처리할 수 있게 되고, 무거운 메모리 해제 작업은 뒤에서 조용히 처리됩니다.
도서관 비유를 다시 사용하자면, 사서는 책을 파쇄기에 바로 넣는 대신, 도서 목록 카드만 즉시 제거하고(O(1) 작업) 책은 ‘나중에 파쇄할 책’ 바구니에 던져 넣습니다. 그리고 바로 다음 방문객의 요청을 처리하기 시작합니다. 실제 파쇄 작업은 다른 직원이 나중에 처리하게 됩니다.
이러한 비동기 방식 덕분에 UNLINK는 키의 크기가 아무리 크더라도 Redis 서버를 블로킹하지 않아 안정적인 성능을 보장합니다.
UNLINK의 내부 최적화 사실 모든UNLINK가 비동기로 처리되는 것은 아닙니다. 삭제하려는 키가 아주 작을 경우, 백그라운드 스레드에 작업을 넘기는 오버헤드가 오히려 더 클 수 있습니다. 그래서 Redis는 내부적으로 삭제 비용을 추산하여, 그 비용이 특정 임계값(LAZYFREE_THRESHOLD)을 넘을 때만 비동기 삭제를 수행하고, 그렇지 않으면DEL과 동일하게 즉시 삭제합니다.
3. lazyfree-lazy-user-del 설정: 코드 수정 없이 DEL을 UNLINK처럼
이미 운영 중인 서비스의 코드에 있는 수많은 DEL 명령어를 UNLINK로 바꾸는 것은 부담스러운 작업일 수 있습니다. 이러한 상황을 위해 Redis 6.0부터는 lazyfree-lazy-user-del이라는 유용한 설정 옵션을 제공합니다.
redis.conf 파일에 다음과 같이 설정하면,
lazyfree-lazy-user-del yes
클라이언트가 DEL 명령어를 보내더라도 Redis 서버는 이를 내부적으로 UNLINK처럼 처리합니다. 즉, 코드 변경 없이 DEL 명령어에 대해 비동기 삭제의 이점을 누릴 수 있게 됩니다.
정리 및 권장 사항
| 구분 | DEL | UNLINK |
|---|---|---|
| 동작 방식 | 동기적 (블로킹) | 비동기적 (논블로킹) |
| 성능 | 큰 키 삭제 시 서버 블로킹 가능 | 키 크기와 무관하게 일정한 응답 시간 |
| 메모리 해제 | 즉시 | 백그라운드 스레드에서 처리 |
| 도입 버전 | 1.0+ | 4.0+ |
언제 무엇을 사용해야 할까요?
- 새로운 애플리케이션 개발 시: 키의 크기가 커질 가능성이 있다면, 기본적으로
UNLINK를 사용하는 것이 안전하고 좋습니다. - 기존 애플리케이션 유지보수 시: 키 삭제로 인한 성능 저하가 의심된다면, 코드의
DEL을UNLINK로 점진적으로 변경하거나, 더 빠른 해결책으로lazyfree-lazy-user-del yes설정을 활성화하는 것을 적극적으로 고려해볼 수 있습니다. - 매우 작고 단순한 키 삭제 시:
DEL을 사용해도 성능에 아무런 문제가 없습니다.
결론
DEL과 UNLINK의 차이를 이해하는 것은 안정적이고 높은 처리량의 Redis 기반 시스템을 구축하는 데 매우 중요합니다. 특히 마이크로초(μs) 단위의 응답 시간이 중요한 서비스에서 예기치 않은 성능 저하를 방지하려면, 키 삭제 전략을 신중하게 선택해야 합니다. UNLINK와 lazyfree 관련 설정을 적극적으로 활용하여 더 견고한 시스템을 만들어 보시길 바랍니다.