프로그래밍/DataBase

[Redis] 인메모리 데이터베이스 Redis - (1) Introduction

Churnobyl 2024. 7. 19. 14:37
728x90
반응형


1. What is Redis?

2018년 가장 인기있는 데이터베이스 컨테이너 (출처 : Redis Youtube)

 

 Redis(REmote DIctionary Server)캐시 서버로 가장 많이 사용되는 key-value기반 인메모리 데이터베이스다. 프로세스에 할당된 메모리에 데이터를 올려놓고 사용하므로 하드디스크와 같은 대용량 저장장치를 사용하는 RDBMS 같은 타 데이터베이스에 비해 굉장히 빠른 속도가 장점이다. 하지만 대용량 저장장치에 비하면 용량이 매우 작으므로 메인 데이터베이스로 사용하기에는 한계가 있다.

 또한 메모리는 volatile한 특성(휘발성)을 가지고 있으므로 프로세스가 종료되면 데이터가 유실될 수 있다. 그래서 Redis는 이러한 단점을 AOF, RDB라는 두가지 백업 방식을 이용해 커버한다. Redis의 백업 방식에 대한 자세한 내용은 뒤에 다시 언급하려고 한다.

 

 추가적으로 Redis는 key-value 자료 구조 기반으로 String뿐만 아니라 Lists, Sets, Hashes, HyperLogLog등의 다양한 자료구조를 제공하므로 상황에 따라 적절한 자료구조를 사용해 효율성을 극대화할 수 있다. 예컨데, 집합의 카디널리티를 추정하는 확률적 데이터 구조인 HyperLogLog를 사용하면 해당 자료구조로 저장된 데이터를 다시는 확인할 수 없지만 몇 개의 데이터가 저장되어 있는지 확인할 수 있다. 또한 12kb의 고정적인 크기로 유지되어 용량을 매우 작게 차지하므로 대용량 데이터의 유니크한 값을 카운팅할 때 사용할 수 있다. 사용 예로 검색엔진에서 하루동안 검색된 검색어의 수나 한달동안 해당 사이트에 접속한 IP들의 유니크한 개수등을 알고 싶을 때 사용할 수 있다.

 

 위의 설명을 토대로 Redis Github에서는 Redis를 'memcached +  디스크 백업 + 다양한 자료구조'이 가능한 memcached의 확장버전으로 생각해도 된다고 언급한다. 두 프로젝트의 차이에 대해 알고 싶다면 aws에 두 오픈 소스 프로젝트를 비교해 정리한 페이지가 있다. 

 

 Redis는 2009년 이탈리아의 Salvatore Sanfilippo가 처음으로 개발했으며 2015년부터 Redis Lab에 합류해 수석 개발자로서 개발을 지속하고 있다.

 

 


2. 레디스는 싱글 쓰레드 구조 (Single-Thread)

레디스는 싱글 쓰레드로 동작한다

 

 Redis는 지금까지 하나의 쓰레드만으로 작업을 처리하는 싱글 쓰레드 디자인을 채택했다. 그리고 싱글 쓰레드만으로 초당 10만건 이상의 데이터 요청을 처리할 수 있었다. Redis 6.0을 발표하면서 멀티쓰레드를 지원하는 ThreadedIO 기능이 추가되었지만, 어쨌거나 Redis는 싱글쓰레드만으로 어떻게 빠른 속도를 달성할 수 있었을까? 그 부분은 아래 I/O Multiplexing에서 알아보기로 하고, 그전에 싱글 쓰레드 디자인이 멀티프로세스/쓰레드 방식과 구분되는 이점에 대해서 먼저 알아보자.

 

트랜잭션을 모른다면.

더보기

 먼저 데이터베이스 시스템의 Transaction(트랜잭션)을 알아야 한다. 데이터베이스 시스템에서 모든 작업은 트랜잭션 단위로 진행된다. 일반적으로 데이터베이스에 수행되는 작업은 단순히 하나의 데이터를 읽거나 쓰는 게 아니라 복수의 작업이 동시에 진행되어야 의미있는 경우가 대부분이다. 가령, 유저가 게시판에 글을 썼을 때 단순히 게시판 테이블에 INSERT문으로 레코드를 하나 추가하는 것에 그치지 않고 함께 작성한 태그나 이미지 관련 테이블에도 함께 INSERT를 해야할 경우가 있다. 이 중 하나의 작업만 성공해서는 아무 의미도 없고 그래서도 안된다(이미지를 올렸는데 이미지가 없는 글이 작성된다면..?). 따라서 하나의 트랜잭션은 모든 작업이 성공할 경우에만 실제로 데이터베이스에 반영될 수 있도록 commit을 하고 하나라도 실패했다면 rollback해 기존의 상태를 유지할 수 있도록 한다. 이러한 트랜잭션의 특성을 ACID라고 줄여부르기도 하며 각각 Atomicity(원자성), Consistency(일관성), Isolation(고립성), Durability(지속성)을 나타낸다.

 

멀티 쓰레드/프로세싱 방식에서는 다수의 쓰레드나 프로세스가 동시에 단일 데이터베이스와 상호작용하려고 하는 경쟁 상태(Race Condition)가 발생하므로 이러한 특성을 지키기 쉽지 않아 다양한 방식의 Lock을 이용해 해결해야 한다. 이는 설계가 복잡해질 수 밖에 없다. 하지만 Redis는 단 하나의 쓰레드만 사용하므로 ACID를 만족하기 쉬울뿐더러 CPU 컨텍스트 스위칭 비용도 발생하지 않는다. 수많은 사람이 창고의 물건을 이곳저곳에 넣었다 빼기 위해서 넣는 순서나 빼는 순서 규칙을 지정하는 것보다, 단 한 사람이 창고를 관리하는 것이 물건의 위치를 더 잘 기억하며 관리하기 쉬운 것과 같다. 다만 그 한 사람이 수많은 사람들이 작업하는 속도만큼 빨라야 효율적이겠지만..

 

 그리고 Redis는 창고지기 하나가 작업하는 속도를 비약적으로 끌어올렸다.

 


3. Blocking I/O vs Non-Blocking I/O

 Redis가 빠른 속도를 달성할 수 있었던 기반인 I/O Multiplexing에 대해서 알아보기 위해서는 먼저 Blocking I/O와 Non-Blocking I/O의 차이에 대해서 알아보아야 한다.

 

Blocking I/O

Blocking I/O

 

 Blocking I/O는 가장 일반적인 방식으로, 어떤 유저 프로세스(애플리케이션 프로세스)가 실행되다가 외부 파일이 필요하거나 외부로 파일을 내보내려고 할 경우 커널에 시스템 콜을 하고 I/O 작업이 진행되는 동안 자신의 작업을 중단하고 I/O가 끝날 때까지 대기하는 방식이다.

 

 위 그림을 보면 애플리케이션 입장에서 I/O작업이 필요한 시점에 Read()를 호출해 커널에 시스템 콜을 하고, 커널이 필요한 I/O작업을 시작하고 Response가 올 때까지 하염없이 대기한다. 이 상태를 Block되었다고 하고 그동안 애플리케이션 프로세스는 다른 작업을 하지 못한다. 

 

 

Non-Blocking I/O

Non-Blocking I/O

 

 Non-Blocking I/O는 유저 프로세스가 시스템 콜을 했을 때, I/O작업이 완료될 때까지 Blocking되지 않고 커널이 바로 리턴해 제어권을 유저 프로세스가 다시 받아 다른 작업을 계속 수행할 수 있는 방식이다. Read()를 호출해 시스템 콜을 하면 커널은 요청을 받은 즉시 작업의 완료 여부와는 무관하게 제어권을 다시 유저 프로세스에게 넘겨준다. 그리고 유저 프로세스는 다른 작업을 수행하는 중간중간에 시스템 콜을 보내서 앞선 작업의 완료 여부를 확인하고, 완료되었다면 그 작업의 나머지 부분을 수행한다.

 

 위의 두 그림은 둘다 Synchronous 모델이다. Redis가 ACID 특히 원자성을 보장하기 위해서는 작업의 순서가 보장되어야 하니 Asynchronous 모델은 이번 글에서 제외했다. Sync, Async, Block, Non-block에 대해서 더 확실하게 알고 싶다면 네이버 클라우드 블로그를 참조하자. 아주 잘 정리되어 있다! 여기


3. I/O Multiplexing

I/O Multiplexing - Linux의 epoll()

 

 이제 Redis가 단 하나의 쓰레드만으로 어떻게 빠른 속도를 달성할 수 있었는지 알아보자.

 

 Redis는 메모리에 데이터를 적재하거나 읽는 것보다 외부에서 데이터 가져오고 외부로 내보내는(입출력) 네트워크 I/O가 훨씬 오래 걸린다는 사실에 주목했다. 데이터를 준비하는 과정이 더 오래걸리는 것이다. Redis는 이를 해결하기 위한 방법으로 OS의 I/O Multiplexing을 이용했다.

 

 I/O Multiplexing은 세부적으로 어떻게 구현되는지에 따라 달라지기 때문에 의견이 분분하지만  일반적으로 Sync Non-blocking 모델로 분류된다. 

즉, 어떤 명령이 실행됐을 때 Redis는 해당 명령을 실행하기 위한 데이터가 전부 준비될 때까지 무작정 대기하지 않고 작업할 배열에 추가만 해놓고 바로 다음 명령 수행을 위해 대기(Event Loop System)한다. 데이터를 준비하는 일은 전부 OS에게 맡겨뒀다가 데이터가 전부 준비됐다고 OS가 알려주면 그제서야 해당 명령을 처리하는 것이다. 이 방식을 사용하면 다수의 요청이 들어와도 처리할 준비가 완료된 명령부터 처리하면 되기 때문에 단일 쓰레드로도 높은 속도를 달성할 수 있다. 

반응형