분산된 서버에서 데이터를 저장하거나, TTL을 설정하거나, 동시성을 제어하기위해 등등 여러용도로 Redis는 많이사용되고
있다. 프로젝트에서도 Redis를 사용하면서 싱글스레드기반으로 동작되기 때문에 동시성문제를 제어하는데 있어서 문제는 없지만
싱글스레드기반이기 때문에 성능에 문제가 있지 않을까? 하는 의문은 한번 쯤은 해봤던 것 같다. 이 의문점을 풀기위해 글을 작성합니다.
왜 Redis는 높은 성능을 보장하는가 ?
1. 메모리 기반 데이터 저장
- Redis는 모든 데이터를 메인 메모리에 저장하고 주기적으로 스냅샷을 디스크에 저장한다.
2. 싱글 스레드 및 이벤트 루트 시스템
Redis는 Node.js와 같이 이벤트루프와 I/O Multiplexing을 통해 싱글 스레드 모델임에도 높은 동시성을 보장한다.
3. 효율적인 데이터 구조
Redis는 skip-lists.간단한 동적 문자열 등과 같이 효율적인 데이터 구조를 사용하여 빠른 연산을 지원한다.
📌 Redis Event-Loop
이벤트 루프를 알려면 , 먼저 동기 비동기의 개념부터시작하여 I/O MultiPlexing이 무엇인지 알아야한다.
Blocking I/O
Blocking I/O 란, I/O 요청이 수행될 동안 호출한 스레드가 멈추거나 블락되고 끝나기를 기다리는 것을 의미한다.
기본적으로 Synchronous 방식이며, 즉 파일과 같은 입출력이 일어났을 때 시스템 콜 함수가 완료될 때 까지 다른 일을 하지 못하고 언제 끝나나 계속 확인을 하면서 기다리게 된다. 글만 봐도 매우 비효율적일 것 같지 않는가?
- 호출된 함수가 바로 리턴해서 제어권을 넘겨주지 않는다.(Blocking)
- 호출한 함수가 작업 완료 여부를 계속 확인한다.(Synchronous)
멀티스레드 + Blocking I/O 문제점
물론 처리 속도는 빨라지겠지만, 여전히 문제가 발생한다.
- 여러 스레드가 트리거되어 대규모 컨텍스트 전환과 높은 메모리 사용량으로 인해 성능 문제가 발생한다. 이 경우 CPU 는 전환, 예약, 스레드 수명주기 유지 등에 대부분의 시간을 소비하게 된다.
- 각 스레드는 클라이언트가 데이터를 보내고 디스크 I/O 작업을 수행하기 위한 연결을 기다리느라, CPU 는 남은 시간을 I/O를 기다리며 낭비하게 된다.
이렇듯 Blocking I/O 접근을 사용하는 thread per connection 방식은 많은 동시 연결 상황에 적합하지 않다.
Non-Blocking I/O Model
이제 연결당 하나의 스레드 대신, Non-Blocking 방식으로 연결을 허용하는 단일 스레드를 사용해보자. Non-Blocking I/O 란 I/O 요청이 수행될 동안 스레드가 멈추거나 블락되지 않는 것을 의미한다.
기본적으로 Non-Blocking + Asynchronous 방식으로 동작한다.
- 호출된 함수가 바로 리턴해서 제어권을 넘겨준다. (Non-Blocking)
- 호출한 함수가 작업 완료 여부를 확인하며 기다리지 않는다.(Asynchronous)
바로 제어권을 넘겨주는 상황에서 데이터가 준비되지 않았다면, 즉 요청에 대한 결과를 반환할 수 없는 상태라면 -1 을 리턴해서 호출한 스레드가 다른 작업을 수행할 수 있도록 한다
각각 언제 써야 할까?
- CPU Burst Time > I/O Burst Time 인 프로세스
Blocking I/O 가 유리할 수도 있으며, Non-BlocKing I/O 가 커다란 도움을 주지 못할 수 있다. - CPU Burst Time < I/O Burst Time 인 프로세스
Non-Blocking I/O 가 절대적으로 유리하다. Node.js / Redis 역시 많은 네트워크 요청(I/O)을 주고 받기 때문에 여기에 속한다.
즉,Blocking I/O는 순차적인 흐름을 보장해야하거나, 작업의대한 순서가 중요한 경우 사용을 하는 것을 권장한다.
ex)파일 읽기, 데이터 처리, 결과 출력이 순차적으로 이루어져야하는경우 or 은행거래시스템
Non-Blocking I/O는 병렬작업이 필요하거나 , 많인 I/O작업이 필요할 때 사용하면 좋을 것 같다.
하지만 호출한 스레드가 어떻게 I/O 작업이 완료되었는지 알 수 있지?
주기적으로 확인하는 것을 폴링 방식이라 하는데, 확인을 한다는 점에서 폴링은 Non-Blocking + Synchronous 로 동작한다.
커널로부터 제어권을 받어 효율적이라 보일 수 있지만, 결과를 반환하기 까지 계속 데이터를 반환했는지 확인하는 busy-waiting 상태가 되어버린다.
이렇게 되면 다른 작업을 하다가도 상태를 확인하기 위해 컨텍스트 스위칭이 일어나고, 다시 작업을 수행하다가 또 컨텍스트 스위칭이 일어나는 의미 없이 컨텍스트 스위칭 비용만 낭비해 성능을 떨어트릴 수 있다. 또한 이렇게 Polling 주기에 따라 성능에 영향을 미치므로 설정이 매우 중요해진다.
준비됨을 알림 : I/O MultiPlexing
I/O MultiPlexing 이란, 관심 있는 I/O 작업들을 동시에 모니터링 하고 그 중에 완료된 I/O 작업들을 한번에 알려주는 기법이다. 선택 혹은 폴링 시스템 호출을 통해 운영체제에서 단일 스레드가, 여러 소켓 요청을 동시에 기다릴 수 있게 된다.
리눅스에서는 모든 것이 파일로 귀속된다. 따라서, 소켓 또한 파일이므로 스레드가 하나라도 여러 개의 파일을 동시에 관리할 수 있다면, 다수 유저의 요청을 처리할 수 있게 된다.
자바 스크립트의 Event-Loop 방식과 비슷하게 동작한다. 이벤트 루프는 콜 스택을 계속 감시하고 있다가 비어있는 상태가 되면, 콜백 큐에 있던 콜백을 전달해준다. 즉 콜 스택과 콜백 큐를 항상 모니터링 하고 있다.
효율적인 데이터 저장구조
Zip List
- 메모리 효율적인 데이터구조
- 연속된 메모리 공간에 데이터가 저장
- 데이터가 소량일 때 사용되어 메모리를 절약할 수 있다.
Skip List
- 계층적인 데이터구조
- 정렬된 데이터를 빠르게 탐색,삽입,삭제 가능
- 연결리스트에 다중 레벨의 인덱스를 추가하여 성능을 향상
- Sorted Set도 다음 구조를 사용함
결론
- Zip List는 메모리 효율성을 중시하는 소규모 데이터에 적합, Skip List는 대규모 정렬 데이터와 빠른 탐색/범위 조회가 필요한 경우에 적합
- Skip List는 우선순위 큐보다 삽입/삭제의 유연성, 탐색의 편의성, 메모리 접근 효율성에서 강점을 가짐, 정렬과 범위 기반 작업에서는 Skip List가 더 우수함
'MessageBroker' 카테고리의 다른 글
실습으로 배우는 선착순 이벤트 시스템 - review (1) | 2023.10.02 |
---|---|
Kafka Connect Source,Sink 통해 다른시스템과 데이터 주고받기 (0) | 2023.06.22 |
Spring Cloud Bus란 무엇인가? (0) | 2023.06.16 |
MOM 미들웨어란 무엇인가? (0) | 2023.06.16 |
RabbitMq 설치하는방법 (0) | 2023.06.16 |