밀키트 DB Redis

골두·2024년 6월 17일

Backend

목록 보기
8/10
post-thumbnail

레디스는 key, value 형태로 저장되는 메모리 내 데이터 구조 저장소로 주로 DB, 캐싱, 메세지 브로커 등의 용도로 사용하게 된다.

왜 메모리 내 저장하는가?

디스크에 저장하는 것 보다 메모리에 직접 접근해 데이터에 빠르게 접근하는 장점이 있다. 메모리라는 것은 CPU에 직접적으로 연결되어 있어 빠르게 데이터 검색이 가능하다.

그렇기 때문에 읽기, 쓰기 처리량이 높고 낮은 Latency(지연 시간)가 장점이나 메모리의 크기에 따라 저장의 최대 용량이 정해진다는 단점 또한 존재한다.

싱글 스레드

싱글 스레드의 가장 큰 장점이자 단점은 하나의 스레드를 기반으로 동작한다라는 것이다.

장점은 한번에 하나씩 처리하기 때문에 여러개를 동시에 처리하는 멀티 스레드와는 다르게 데이터 동기화 및 컨텍스트 스위칭이 필요하지 않다

단점은 어찌됬든 하나에서 처리함으로 처리량이 많아지면 많아질 수록 할당된 자원은 한계가 있고 대기 queue의 양은 늘어나게 되면서 허용된 메모리 이상 사용하다 죽어버릴 수 있다. (Redis는 아마 keys만 사용하지 않는다면 이런 문제는 생기지 않을 것이다)

멀티 플렉싱

레디스는 싱글 스레드의 단점 (순차적 처리로 인한 지연)을 방지하기 위해 Multi Flexing이라는 방식을 사용해 해결한다.

멀티 플렉싱의 사전적 의미는 하나의 통신 채널을 통해 둘 이상의 데이터(시그널)을 전송하는데 사용되는 기술이다.

효율성을 높히기 위해 물리적인 장치를 최소한으로 사용해 최대한의 데이터를 전송하는 기술이라고 정의도 가능한데, 네트워크 소켓에 적용시키게 되면

다수의 프로세스의 생성 없이 여러 개의 클라이언트에게 서비스를 제공할 수 있는 기술이 된다고도 할 수 있다.

프로세스를 생성하는데 드는 비용과 작업이 많아 위 멀티플랙싱 방법을 이용해 I/O(입출력) 멀티플렉싱을 사용해 효율성을 올린다.

입출력 모델은 여러 종류가 있어 하나하나 알아보면서 왜 멀티플렉싱이 효율적인지 알아보자

Blocking I/O

가장 널리 사용되고 있는 입출력 모델로 UDP 통신을 이용해 Blocking 모델을 구성한다.

동작 원리

  1. 프로세스에서 recvfrom(데이터그램을 수신하고 원본 주소를 저장하는 함수) 함수를 호출해 커널에게 데이터 그램을 보내라 요청함
  2. 데이터 그램이 커널에 도착하면 그 데이터 그램을 복사해 어플리케이션으로 전송한다.

위 과정이 진행되는 동안 계속, 또는 오류가 발생할 때 까지 시스템 콜은 끝나지 않게되는데 recvfrom 함수가 위 과정을 거쳐 성공적으로 return 된다면 어플리케이션에서 데이터그램을 처리한다.

즉 프로세스는 recvfrom을 호출하고 return 될 때 까지 계속 기다리는 blocking 상태가 되어 Blocking I/O 모델이라고 부른다.

Non-Blocking I/O

위의 Blocking 모델과 다르게 말 그대로 막히지 않는 모델이고 소켓을 non-blocking I/O로 정할 때 요청한 입출력 작업을 수행할 때 프로세스가 block 되어야 하면 오류를 돌려달라는 요청을 주는 모델이다.

동작 원리

  1. 프로세스에서 동일하게 recv 함수를 호출해 커널에 데이터 그램을 보내라고 요청
  2. 현재 데이터 그램이 준비되지 않았으면 오류를 호출함
  3. 오류가 호출된 후 데이터 그램이 준비될 때 까지 polling 기법을 사용해 계속 요청
  4. 데이터 그램이 준비되면 정상적인 데이터 그램을 복사 해 어플리케이션으로 전송

준비가 안되었을 때 프로세스를 blocking 처리해 다른 작업을 수행하지 못하게 막는 것이 아닌 받을 때 까지 요청함으로써 프로세스가 중단되지 않지만 계속된 요청으로 인해 CPU에 부하가 일어나는 단점이 있어 주로 하나의 함수만을 수행하는 시스템에서 사용한다.

I/O Multiplexing

이제 Redis 에서 사용하는 I/O 모델의 본제로 입출력 다중화 모델이라고도 부른다.

이 모델의 경우 select() 함수와 poll() 함수를 활용한다.

용어

파일 디스크립터

select, poll 함수를 알기 위한 선행 지식으로 리눅스 or 유닉스 계열의 시스템에서 프로세스가 파일을 다룰 대 사용하는 개념이다.

프로세스에서 특정 파일에 접근할 때 사용하는 추상적인 값으로 프로세스에서 열린 파일의 목록을 관리하는 테이블의 인덱스다.

리눅스(유닉스) 환경에서는 모든 것을 파일(파일, 소켓 등...)로 취급하기 때문에 각각의 프로세스는 파일 디스크립터를 보유하고 있다.

select()

싱글스레드로 여러 파일을 작업하고자 할 때 사용할 수 있는 매커니즘으로 입출력 관리하고자하는 파일의 그룹을 fd_set이라는 파일 비트 배열에 넣고 값이 변했는지 확인하는 방식으로 동작

성공 시 준비된 fd 갯수를 return하고 timeout 발생 시 0, 오류 시 -1을 return하게 된다.

fd가 입출력을 수행할 준비가 되거나 정해진 시간이 경과할 때 까지는 block된다.

단점
  • 감시하고자 하는 이벤트 설정을 변형하는 방식이라 매번 이벤트 비트를 새로 설정해야함
  • fd를 하나하나 체크하기에 O(n)의 계산이 필요해 관리하는 fd가 늘어나면 늘어날 수록 성능이 떨어진다.
  • fd길이에 제한이 있음.
  • 이런 이슈를 조금 더 보완해서 epoll이라는 함수를 사용하곤 한다.
poll()

select와 비슷한 방식으로 동작하나 select비해 더 많은 정보를 반환해주는 함수

동작 원리

  1. select 함수를 호출해 여러 소켓 중 data ready 상태가 된 소켓이 있을 때 까지 대기
  2. select() 결과로 read() 함수를 호출할 수 있는 소켓의 목록이 반환되면 해당 소켓에 대해 read() 함수 호출
  3. 2번의 여러 소켓을 동시에 확인해 소켓이 준비될 때 까지 대기

이 방식을 이용해 어플리케이션에서 필요한 데이터를 select 함수 호출로 datagram이 준비된 소켓들을 계속 찾아 기다리면서 소켓을 찾으면 그 때 반환해주는 방식을 활용해 커널 상태를 block 처리 하지 않고 동시에 처리가 가능하다.

결론

결론을 위해 알아야 하는 지식이 많았지만 Redis는 multi-thread 형식으로 여러 요청을 받고 그 요청들을 event-loop를 통해 task-queue 안에 집어넣어 순차적으로 이벤트를 빠르게 처리해준다.

네이티브 데이터 구조

Redis는 단순한 데이터 구조를 사용해 문자열, 리스트, 해시, 집합(Set) 등 다양한 네이티브 데이터 구조를 지원하면서도 가볍고 간단해 Redis 메모리 사용량을 최소화하면서 빠른 데이터 처리를 보장해준다.

Redis는 내부적으로 적절하게 최적화된 데이터 구조를 사용해 매우 빠른 데이터 저장 및 검색을 제공해준다.

profile
나 볼려고 만든 블로그 (블로그 이전: https://goldfrosch.tistory.com/)

0개의 댓글