Redis 메모리 관리의 모든 것: TTL과 LRU로 성능을 극대화하는 법

1. Redis는 단순한 캐시가 아닙니다: 인메모리 데이터 구조 서버의 진짜 정체

Redis를 단순한 캐시로만 생각하셨다면, 지금부터 시선을 조금 바꾸셔야 합니다. Redis는 단순히 데이터를 메모리에 저장하는 것을 넘어, 리스트, 셋, 정렬된 셋, 해시, 비트맵, 하이퍼로그로그, 스트림 등 다양한 데이터 구조를 다룰 수 있는 ‘인메모리 데이터 구조 서버’입니다. 메모리에 모든 걸 올려두니, 속도는 번개처럼 빠르고, 레이턴시는 극도로 낮습니다. 그리고 모든 데이터를 메모리에 두기 때문에 읽기/쓰기 속도가 압도적으로 빠르며, 이 속도가 Redis가 많은 실시간 시스템에서 선택받는 이유 중 하나입니다.

그런데 Redis가 단순히 빠르기만 한 도구였다면 이토록 인기를 끌지는 않았겠죠. Redis는 내부적으로 굉장히 정교한 구조를 가지고 있으며, 다양한 전략을 통해 메모리 관리를 아주 똑똑하게 수행합니다. 특히 TTL(Time to Live)과 LRU(Least Recently Used) 같은 메모리 관리 전략은 성능과 안정성을 보장하는 핵심입니다. 이 글에서는 Redis의 내부 구조부터 TTL, LRU 전략까지, 실무에 필요한 핵심 포인트 10가지를 자세히 알아보겠습니다.

2. 단일 스레드 구조, 그런데도 빠르다니?

Redis는 기본적으로 단일 스레드(single-threaded)로 작동합니다. 이 말은, 하나의 스레드가 모든 클라이언트 요청을 처리한다는 의미입니다. 보통은 멀티스레드를 써야 성능이 좋을 거라고 생각하시겠지만, Redis는 오히려 단일 스레드 덕분에 락(lock) 문제 없이 빠르게 작동합니다. 락 경합(lock contention)이 없으니, 처리 속도가 굉장히 안정적이고 예측 가능합니다.

또한 Redis는 이벤트 루프 기반으로 작동하며, 내부적으로는 epoll이나 kqueue 같은 고성능 I/O 멀티플렉서를 사용합니다. 이것은 마치 초고속 도로에서 차량을 한 줄로만 잘 통제해서 교통 체증 없이 움직이는 것과 비슷합니다. 물론 연산이 오래 걸리는 작업은 Redis에서 피해야 합니다. 블로킹이 걸리면 모든 요청이 멈추기 때문입니다. Redis가 빠르다고 무조건 무거운 연산까지 시키면 낭패를 볼 수 있습니다.

3. Redis 내부는 ‘Object’로 이루어져 있다

Redis의 모든 데이터는 내부적으로 redisObject라는 구조체로 관리됩니다. 이 오브젝트 안에는 값 뿐만 아니라, 타입(type), 인코딩(encoding), 참조 카운트(refcount), LRU 시간 정보 등 다양한 메타데이터가 포함돼 있습니다. 예를 들어 문자열을 저장하더라도 그게 단순한 문자열인지, 정수인지에 따라 인코딩 방식이 달라집니다.

이렇게 Redis는 상황에 따라 데이터를 가장 효율적인 구조로 자동 변환해줍니다. 예를 들어, 작은 리스트는 ziplist로 저장해 메모리 절약을 하고, 리스트가 커지면 linked list로 바꾸죠. 이런 동적인 구조는 메모리를 최대한 아끼면서도 성능을 유지하려는 Redis의 지혜라고 할 수 있습니다.

4. TTL(Time to Live): 살아있는 시간, 그리고 죽음

TTL은 Redis에서 가장 중요한 기능 중 하나입니다. 특정 키에 유효 기간을 설정하면, 그 시간이 지나면 자동으로 사라집니다. 캐시 시스템에서 자주 쓰이는 이 기능 덕분에, 만료된 데이터를 자동으로 정리하면서 메모리를 깔끔하게 유지할 수 있습니다.

TTL은 초 단위 또는 밀리초 단위로 설정할 수 있으며, EXPIRE, PEXPIRE, EXPIREAT, TTL, PTTL 같은 명령어를 통해 제어합니다. Redis는 이 TTL 값을 따로 관리하면서, 일정 시간이 지나면 해당 키를 삭제합니다. 다만, 삭제는 즉시 일어나지 않고 lazy, active 두 방식으로 진행됩니다. lazy는 키를 접근했을 때 만료됐는지 확인하고 삭제하고, active는 Redis가 주기적으로 스캔해서 직접 지우는 방식입니다. 메모리 관리를 철저히 하면서도, 성능까지 놓치지 않으려는 Redis의 세심함이 엿보입니다.

5. 만료 전략에도 우선순위가 있다: Lazy vs. Active

앞서 말씀드린 것처럼 TTL이 적용된 키는 자동으로 삭제되는데요, Redis는 이를 위해 두 가지 전략을 사용합니다. 바로 Lazy 삭제와 Active 삭제입니다. Lazy는 요청이 들어올 때만 확인해서 삭제하는 방식입니다. 그래서 자주 쓰이지 않는 키는 오랫동안 메모리에 남아 있을 수 있죠. 반면 Active는 Redis가 백그라운드에서 주기적으로 일정 수의 키를 무작위로 뽑아 TTL을 검사하고, 만료된 키를 지웁니다.

이 두 전략이 잘 조화되어야 메모리 누수를 막을 수 있습니다. Redis는 주기적으로 active 삭제를 실행하고, 이때 너무 많은 리소스를 쓰지 않도록 전체 키 중 일부만 검사합니다. 무작위로 검사하지만 반복적으로 여러 번 수행되기 때문에, 결과적으로 대부분의 만료된 키는 일정 시간 내에 정리됩니다. 이것이 바로 Redis가 효율적이면서도 가벼운 이유입니다.

6. LRU(Least Recently Used): 가장 오랫동안 안 쓰인 녀석부터 제거

Redis는 메모리가 꽉 찼을 때도 똑똑하게 대처합니다. 바로 LRU 전략을 통해 오래된 데이터를 먼저 제거하는 방식입니다. 이름 그대로 ‘가장 최근에 사용되지 않은’ 키를 우선적으로 삭제합니다. 캐시의 생존법칙 같죠?

이 전략은 maxmemory 설정이 적용된 경우에 동작합니다. Redis는 각 키의 마지막 사용 시간을 기록하고 있다가, 메모리가 초과되면 그 정보를 바탕으로 키를 삭제합니다. 정확한 LRU는 비용이 크기 때문에, Redis는 샘플링 기반의 근사값으로 LRU를 계산합니다. 설정에 따라 5개, 10개, 혹은 더 많은 키를 무작위로 뽑아 그 중 가장 오래된 키를 지우는 식입니다. 비록 완벽한 LRU는 아니지만, 속도와 정확성 사이의 밸런스를 고려한 똑똑한 선택입니다.

7. LFU와 RANDOM 전략도 있습니다

LRU 외에도 Redis는 다양한 메모리 관리 정책을 제공합니다. 예를 들어 LFU(Least Frequently Used)는 ‘사용 빈도가 가장 낮은 키’를 우선 삭제합니다. Redis 4.0부터 추가된 이 기능은 데이터가 얼마나 자주 사용되었는지를 기준으로 판단합니다. 특히 같은 키가 자주 호출되는 환경에서는 LFU가 LRU보다 효과적일 수 있습니다.

또 하나 흥미로운 전략은 RANDOM입니다. 말 그대로 아무 키나 랜덤으로 골라 삭제합니다. 이건 속도만 중요하고 정확성은 별로 중요하지 않을 때 유용합니다. 예컨대 수많은 데이터를 단기적으로 캐싱하는 경우라면, 랜덤 전략도 의외로 괜찮은 성능을 보여줍니다.

8. 메모리 정책은 상황에 따라 달라야 합니다

Redis는 다양한 메모리 관리 정책을 설정할 수 있도록 유연한 구조를 제공합니다. maxmemory-policy 옵션을 통해 noeviction, allkeys-lru, volatile-lru, allkeys-random 등 다양한 정책을 선택할 수 있습니다. 예를 들어 전체 키에 대해 LRU를 적용할 수도 있고, TTL이 설정된 키에만 적용할 수도 있습니다.

이건 마치 옷장 정리를 하는 방법과 비슷합니다. 어떤 사람은 가장 오래 안 입은 옷부터 버리고, 어떤 사람은 계절 지난 옷만 정리하죠. Redis도 마찬가지입니다. 시스템의 목적에 맞게 전략을 잘 설정하면 메모리를 훨씬 효율적으로 사용할 수 있습니다.

9. RDB와 AOF, 그리고 TTL의 관계

TTL이 있는 키는 Redis가 저장소에 데이터를 저장할 때도 영향을 줍니다. RDB 스냅샷이나 AOF(Append Only File)를 사용할 때, 만료된 키는 저장되지 않거나 로드 시점에 바로 제거됩니다. RDB는 일정 시간마다 데이터를 디스크에 저장하는 방식이고, AOF는 모든 쓰기 명령을 로그 형태로 저장하는 방식입니다.

TTL이 적용된 키는 AOF에도 expire 명령어가 포함되어 기록됩니다. 나중에 Redis가 복구될 때도 TTL이 그대로 유지되므로, 캐시 무결성 측면에서 매우 중요합니다. 단, 만약 AOF가 비동기적으로 기록되고 있는 도중 시스템이 다운된다면, TTL 오차가 발생할 수 있습니다. 그렇기 때문에 Redis를 운영할 때는 AOF와 RDB 설정도 TTL 전략과 함께 고려해야 합니다.

10. TTL과 LRU는 Redis의 생존 전략입니다

마지막으로 꼭 강조드리고 싶은 점은, TTL과 LRU는 단순한 기능이 아니라 Redis가 메모리를 효율적으로 유지하면서도 빠른 성능을 제공하는 ‘생존 전략’이라는 점입니다. 수십만, 수백만 개의 키가 오가는 환경에서 메모리를 정리하지 않으면 언젠가는 터지게 됩니다. TTL은 데이터의 생명 주기를 자동으로 관리하고, LRU는 오래된 데이터를 말없이 정리해 줍니다.

이 두 기능 덕분에 Redis는 수많은 대형 서비스에서도 안정적으로 돌아갑니다. 실시간 분석, 세션 저장소, 메시지 큐, 랭킹 시스템 등 다양한 분야에서 Redis가 선택받는 이유는 단순한 속도 그 이상입니다. 관리의 철학이 있고, 구조의 논리가 있으며, 전략의 똑똑함이 있기 때문입니다.

결론: Redis는 속도만큼이나 전략적인 도구입니다

Redis는 그저 빠르기만 한 도구가 아닙니다. 내부 구조부터 메모리 관리 전략까지, 정말 정교하게 설계된 시스템입니다. TTL로 자동 삭제를 관리하고, LRU로 공간을 확보하는 모든 메커니즘은 실시간 데이터 처리 환경에서 큰 힘을 발휘합니다. 따라서 Redis를 제대로 활용하려면 단순한 명령어 암기에서 벗어나, 내부 작동 원리를 이해하는 것이 무엇보다 중요합니다. 오늘 알아본 10가지 내용을 바탕으로 Redis를 더 깊이 이해하시고, 실무에 제대로 적용해 보시길 바랍니다.

자주 묻는 질문 (FAQs)
Q1. Redis에서 TTL을 설정하지 않으면 데이터는 어떻게 되나요?
TTL을 설정하지 않은 키는 수동으로 삭제하기 전까지 영구적으로 메모리에 남아 있습니다. 따라서 만료가 필요한 데이터는 꼭 TTL을 지정해 주셔야 합니다.

Q2. LRU 전략이 항상 가장 좋은 선택인가요?
아닙니다. 사용 빈도가 중요한 시스템에서는 LFU가 더 적합할 수 있으며, 단순한 캐시에선 RANDOM도 괜찮은 선택입니다. 목적에 따라 전략을 다르게 선택하는 것이 좋습니다.

Q3. TTL이 적용된 키는 정확히 그 시간에 삭제되나요?
아니요. 삭제는 lazy 또는 active 방식으로 이뤄지며, 즉시 삭제되지는 않습니다. 다만 일정 시간 내에 대부분 삭제됩니다.

Q4. Redis는 단일 스레드인데, 멀티 코어를 활용할 수 없나요?
기본 Redis는 단일 스레드로 작동하지만, 클러스터 환경을 구성하거나 여러 인스턴스를 운영함으로써 멀티코어 활용이 가능합니다.

Q5. Redis 메모리가 꽉 차면 데이터가 자동으로 삭제되나요?
maxmemory와 maxmemory-policy 설정이 되어 있어야만 자동으로 키가 삭제됩니다. 그렇지 않으면 쓰기 명령이 실패합니다.

Similar Posts

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다