본문 바로가기

CS/데이터베이스

Redis에 대해서 -5

Redis에서의 GET, DEL, SET 동작 방식과 메모리 관리 전략

Redis를 사용할 때 우리는 종종 GET, SET, DEL 같은 기본 명령어를 무심코 사용했습니다. 하지만 이 단순한 명령어들조차 Redis 내부에서 어떻게 동작하는지, 그리고 그에 따른 메모리 관리 전략은 무엇인지 이해하는 것은 매우 중요합니다. 특히 서비스의 트래픽이 증가하고 대량의 데이터를 Redis에 주고받는 경우, 이러한 내부 동작을 제대로 이해하지 못하면 성능 저하나 장애로 이어질 수 있습니다.

우선 GET 명령어는 메모리 누수를 유발하지 않는다.

Redis에서 데이터를 조회할 때는 CPU 캐시나 LRU 정책이 간접적인 영향을 줄 수는 있으나, 메모리 측면에서 직접적인 문제가 발생하지 않는다. 단순히 값을 읽기만 하기 때문에 메모리 해제나 할당이 발생하지 않는다. LRU 정책으로 최근 사용되지 않은 키는 삭제가 되지만, Redis는 즉시 삭제되는 것이 아니라, GET 이후에 삭제됩니다.

DEL 명령어는 메모리 누수와 직접적인 관련은 없지만, 이 명령어는 키를 삭제하고 해당 메모리 공간을 해제하는 작업을 수행한다. 하지만 이 과정에서 삭제할 데이터의 크기와 자료구조에 따라 CPU 부하가 커질 수 있다.

예를 들어 list, hash, set, zset 같은 복잡한 자료형은 삭제 연산에 O(N)의 비용이 소요될 수 있다. 특히 list 타입에서는 헤드부터 순회해야 하므로 크기가 커질수록 삭제 비용도 기하급수적으로 증가한다. 삭제는 실제로 한번에 휙 하고 사라지는 것이 아니다. 어떤 위치에 저장되어있는지 파악을 해야하고, 그 파악한 위치에서 삭제가 된다면 앞과 뒤를 연결시켜줘야하는 오버헤드가 발생한다. 이유는 Redis는 싱글 스레드이기 때문이다. 이런 과정이 길어지면 Blocking 될 수 있는 위험이 있기에 삭제는 신중해야한다.

Redis는 이러한 상황을 고려하여 Lazy Freeing이라는 전략을 제공한다.

이는 메모리 해제 작업을 메인 스레드가 직접 수행하지 않고, 백그라운드 스레드에 위임하는 방식이다. 이때 사용하는 명령어가 바로 UNLINK이다. DEL 명령어는 동기적으로 동작하는 반면, UNLINK는 비동기적으로 작동하여 메인 스레드가 블로킹되지 않도록 돕는다. Redis는 싱글 스레드 구조이기 때문에 이러한 비동기 처리는 성능을 유지하는 데 매우 중요하다.

실제로 UNLINK, FLUSHDB ASYNC, FLUSHALL ASYNC와 같은 명령어들은 lazy-free worker thread라고 불리는 백그라운드 작업 스레드가 메모리 해제를 담당한다. 메인 스레드는 키를 삭제된 것으로 처리하고, 실제 메모리 해제는 이 스레드가 나중에 수행한다. 이렇게 함으로써 메인 스레드가 다음 명령어를 빠르게 처리할 수 있도록 하는 것이다.

이와 관련하여 또 하나 주목할 점은 Expiration, 즉 키의 만료 정책이다.

Redis는 두 가지 방식으로 만료를 처리한다.

첫 번째는 Lazy Expiration으로, 사용자가 GET 명령어 등으로 키에 접근했을 때 TTL이 만료된 경우 해당 키를 즉시 삭제하는 방식이다. 이 방법은 CPU 사용량을 줄이는 데 효과적이지만, 접근이 없는 오래된 키는 계속 메모리를 점유할 수 있다는 단점이 있다.

두 번째 방식은 Active Expiration으로, Redis가 주기적으로 만료된 키를 직접 스캔하여 제거하는 방법이다. Redis는 기본적으로 100ms마다 일부 키를 샘플링하여 만료 여부를 확인하고 삭제한다. 하지만 이 방법 역시 모든 키를 즉시 삭제하는 것은 아니기 때문에, Redis는 Lazy와 Active Expiration을 조합하여 효율적인 만료 정책을 운영한다.

반면, SET 명령어는 조금 다른 특성을 가진다.

대량의 SET 명령어가 Redis에 집중되면 메모리 사용량이 급격히 증가할 수 있으며, 이는 다양한 문제를 야기할 수 있다. 대표적으로 Swap 발생이나 Out of Memory 문제가 있다.

Redis는 기본적으로 메모리 기반의 데이터베이스이므로, 가용 메모리를 초과하는 데이터를 저장할 경우 Eviction 정책에 따라 데이터를 제거하거나, 더 나아가 OS 차원에서 Swap이 발생할 수 있다. Swap은 디스크 I/O를 동반하며, 이는 Redis의 싱글 스레드 구조상 심각한 성능 저하로 이어진다. 특히 대량의 SET이 몰릴 경우 Redis가 순간적으로 많은 메모리를 할당하게 되는데, 이때 Eviction이 발생하지 않거나 maxmemory 설정이 없다면 OOM(Out Of Memory) 에러가 발생하게 된다. Redis는 LRU 정책에 따라 오래된 데이터를 우선 제거하지만, 설정이 올바르지 않다면 장애로 이어질 수 있다.

이러한 문제를 방지하기 위해서는 다음과 같은 설정을 고려해야 한다. 먼저 LRU 정책을 통해 불필요한 데이터를 자동으로 제거하도록 설정해야 한다. 그 다음으로는 maxmemory 옵션을 활용하여 Redis가 사용할 수 있는 메모리 상한선을 명확하게 지정해야 한다. 마지막으로는 SET 명령어에 TTL을 함께 설정하는 것이 중요하다. 이는 저장 시점에 해당 키의 생존 기간을 정의함으로써 불필요한 데이터가 메모리에 영구적으로 남아 있는 것을 방지할 수 있다.

Redis는 뛰어난 성능과 유연성을 제공하지만, 그 내부 동작과 메모리 처리 방식에 대한 이해 없이는 예상치 못한 장애나 성능 저하를 경험할 수 있다. 특히 실시간성이 중요한 시스템이나 고트래픽 환경에서 Redis를 사용하는 경우, Lazy Freeing, Expiration 정책, 그리고 메모리 관리 전략을 정확히 이해하고 활용하는 것이 안정적인 운영의 핵심이 된다.

실제 여기저기 찾아다니며 들은 내용은 실무에서는 swap이 발생되지 않게 만드는 환경변수를 체크한다고 한다. 실제로 Redis는 swap이 되어버리면 그 효과를 전혀 내지 못한다.

'CS > 데이터베이스' 카테고리의 다른 글

MySQL의 기본(이해가 필요한 내용 다시 읽어보기) -1  (0) 2025.06.01
Redis에 대해서 -6  (0) 2025.05.29
Redis에 대해서 -4  (0) 2025.05.29
Redis에 대해서 -3  (0) 2025.05.29
Redis에 대해서 -2  (0) 2025.05.29