레디스의 활용은 백엔드 개발자로서 매우 필수적이다. 레디스를 활용하여 읽기 성능을 높이거나 세션 관리 등 다양한 상황에서 유용하게 사용할 수 있기 때문에 필수로 사용하는 기술 중 하나이다. 이번 글에선 인-메모리 데이터베이스의 개요와 레디스에 대해 정리해보았다.
레디스(Redis)는 대표적인 인-메모리(In-Memory) 데이터베이스이다. 인-메모리 데이터베이스란 말 그대로 메모리를 활용하여 데이터를 다루는 것이다.

위의 메모리 계층 구조를 살펴보자. 위로 갈수록 속도가 빠르고 비용이 비싸다. 아래로 갈수록 속도가 느리지만 비용이 저렴하다. 따라서 우리가 일반적으로 사용하는 HDD, SDD(보조기억장치)는 상대적으로 비용이 저렴하여 데이터 저장 용도로 많이 사용된다. 하지만 캐시에 비하면 속도가 느린 편이다. 캐시에 비해 저렴하고 HDD, SDD보다 속도가 빠른 메인 메모리(주기억장치)를 데이터베이스 용도로 사용하는 것이 인-메모리 데이터베이스이다.
레디스(Redis)는 Remote Dictionary Server의 약자로, Key-Value 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터베이스 관리 시스템(DBMS)이다. 데이터를 메모리에 저장하여 빠른 읽기와 쓰기를 가능하게 한다.
레디스는 다양한 용도로 사용된다. 우선 데이터 조회 성능 향상을 위해 가장 많이 사용된다. 또한 데이터의 유효 시간을 지정할 수 있고, 분산된 시스템에서 데이터를 모든 서버에서 일관되게 유지할 수 있기 때문에 사용자 세션 관리 용도로도 사용된다. 그리고 빠른 응답 속도로 실시간 서비스에도 자주 사용된다.
Single Thread 처리
레디스는 싱글 스레드 방식으로 동작한다. 네트워크로부터 명령을 받고, 이를 처리하는 과정이 단일 스레드로 이루어지기 때문에, 개발과 사용이 간단하고 직관적이다.
적은 Context Switching
레디스는 싱글 스레드 모델을 사용하기 때문에, Context Switching이 적다. 멀티 스레딩 환경에서 발생할 수 있는 컨텍스트 스위칭 오버헤드가 없기 때문에, 작업 처리가 더 효율적이다.
Event-Driven 비동기 처리
레디스는 Event-Driven architecture로 설계되어 있어, 비동기적인 작업 처리에 최적화되어 있다. 네트워크 I/O 작업과 같이 대기 시간이 긴 작업을 비동기적으로 처리함으로써 전체 성능을 높인다.
IO-bound 처리
레디스의 작업은 대부분 IO-bound 즉, 데이터를 읽고 쓰는데 시간이 소요된다. 따라서 CPU 연산보다는 I/O 처리에 더 많은 시간을 할애하며, 이 때문에 CPU 최적화가 성능 향상에 큰 영향을 미치지 않는다.
Command 처리 메커니즘
네트워크로부터 패킷이 도착하면, processCommand 함수에서 실제로 명령을 실행한다. 이 때, 한 명령이 오랜 시간을 요구할 경우, 다른 요청이 대기 상태에 놓이게 될 수 있으므로 이에 대한 처리가 중요하다.
레디스는 다양한 데이터 타입을 제공한다. 주로 사용하는 데이터 타입과 용도에 대해 살펴보자.
이외에도 다양한 타입을 지원한다. 추가적인 확인은 해당 링크에서 확인할 수 있다.
In-memory 특성상 메모리 파편화, 가상 메모리 등의 이해가 필요하다.

메모리 파편화는 물리적 메모리에 존재하는 실제 여유 공간보다 적은 양으로 인식되게 만들 수 있으며, 결과적으로 프로세스가 필요한 메모리를 확보하지 못하고 종료되는 문제를 일으킬 수 있다.
이를 방지하기 위해 레디스를 사용할 때는 메모리 할당량에 여유를 두어야 한다. 충분한 여유 공간을 확보하는 것은 레디스의 안정적인 운영을 위해 필수적이며, 메모리 파편화로 인한 문제를 미연에 방지할 수 있다.

프로세스를 메모리에 올릴 때, 시스템은 효율성을 위해 전체 프로세스를 한 번에 메모리에 적재하지 않는다. 대신, 활발히 사용되는 부분만을 메모리에 올리고, 나머지는 디스크에 저장한다.
싱글 스레드 환경인 레디스에서는, 해당 과정에서 latency가 발생할 수 있다. 레디스처럼 싱글 스레드로 작동하는 시스템에서는 스왑으로 인한 지연이 전체 성능에 영향을 미칠 수 있으며, 요청 처리 속도가 느려질 가능성이 있다.
따라서, 레디스를 운영할 때는 스왑에 대한 충분한 이해가 필요하다. 스왑 공간을 사용할지 여부를 결정하기 위해서는 시스템의 메모리 요구사항, 스왑의 장단점, 그리고 레디스 인스턴스의 성능 목표를 고려해야 한다.
레디스는 휘발성의 특성으로 인해 데이터 유실의 위험이 있다. 이러한 위험을 최소화하기 위해, 데이터의 실시간 복제가 중요하다.
데이터를 보호하기 위한 일반적인 방법은 레디스 슬레이브 인스턴스나 디스크에 데이터를 복사하는 것이다. 이러한 복제 과정은 주로 fork 를 통해 현재 레디스 프로세스의 정확한 복사본을 생성하는 데 사용된다. fork 연산은 새로운 프로세스를 시작할 때 현재 프로세스의 메모리 상태를 복제하여 사용한다.
그러나 fork 과정 중에 충분한 메모리 공간이 확보되지 않으면, 새 프로세스가 제대로 생성되지 않아 서버에 심각한 장애를 일으킬 수 있다. 따라서 fork 전 충분한 메모리 공간 확보는 필수적이다.
Memcached도 꽤 많이 알려진 인-메모리 데이터베이스이다. Redis와 Memcached를 비교해보자.
Redis
오픈소스 MDBMS로 향상된 Key-Value 캐시와 저장 기능을 제공하며, 다양한 데이터 타입과 기능을 지원하는 데이터 구조 서버
Memcached
오픈소스 MDBMS로 분산된 메모리 캐싱 시스템에 일반적으로 사용되며, RAM에서 캐싱 데이터를 통해 동작하는 웹 기반 다이나믹 데이터베이스에서 속도 향상을 위해 주로 사용됨

Memcached는 멀티 스레드를 지원한다는 장점이 있지만, 데이터 타입이 String 밖에 없는 점과 트랜잭션, pub/sub, replication, 영속성(presistence) 관련 지원이 없는 점을 고려한다면 웬만한 상황에서 Redis를 사용하는 것이 좋은 선택인 것 같다.
참고 자료
https://bommbom.tistory.com/entry/%EC%9D%B8%EB%A9%94%EB%AA%A8%EB%A6%ACIn-memory-DB-%ED%8A%B9%EC%A7%95%EA%B3%BC-%EC%A2%85%EB%A5%98-%EB%B9%84%EA%B5%90
https://www.youtube.com/watch?v=Gimv7hroM8A&t=574s