Reactor Pattern

Chang-__-·2023년 3월 5일
0

I/O

Blocking I/O

I/O 는 OS 에서 하는 동작중에 가장 느리다. RAM 에 데이터가 접근 할 때는 (10^-9 초)가 걸리는 반면, 디스크와 네트워크에 접근할 때는 (10^-3 초) 정도가 걸린다.
전통적인 블로킹 I/O 프레임워크를 통해 개발을 하면 Request 가 들어올때 새로운 스레드를 polling 하여 데이터를 처리하기에, 같은 스레드 내에서 여러 연결을 처리 못하는 것은 자명한 사실이다.
이 방법은 I/O의 작업이 각각의 스레드에서 처리되기 때문에 I/O 작업으로 인해 블로킹된 스레드가 다른 연결들의 가용성에 영향을 미치지 않는다.

멀티 스레드 방식으로 처리하면 위 그림 처럼 Idle Time 즉 유휴시간이 꽤 길게 처리 된다. 여기서 유휴시간은 Connection 으로 부터 새로운 데이터를 받기 위해 걸리는 시간을 말한다.
이렇게 되면 DB 나 File System 과 interaction 할 때 모든 유형의 I/O 가 요청 처리에 Blocking 을 하게 되는데 생각 보다 Blocking 하는 시간이 꽤 길어진다. 아쉽게도 스레드는 OS 측면에서 cost 가 저렴하지 않다.
어떻게 보면 하나의 스레드가 유휴시간 동안은 동작하지 않는다는 것인데 메모리와 CPU 사이클의 낭비가 된다.

Non Blocking I/O

대부분의 최신 OS는 리소스에 접근하기 위해 블로킹 I/O 외에도 논 블로킹 I/O 라고 불리는 다른 메커니즘을 지원한다.
논블로킹 I/O 에서는 blocking 방식의 비효율성을 극복하고자 도입된 방식인데, 데이터가 Read, Write 가 끝날 때 까지 기다리지 않고 즉시 반환한다.
기본적인 flow를 설명 하자면 아래와 같다.

1. 유저가 커널에 read 작업을 요청
2. 데이터가 입력 여부와 상관없이 요청하는 순간 바로 결과가 반환 (데이터가 없으면 EWOULDBLOCK을 반환)
3. 데이터가 있을 때 까지 1,2 번을 반복 

이런 종류의 논 블로킹 I/O 를 다루는 가장 기본적인 페턴은 실제 데이터가 반환될 때 까지 루프에서 리소스를 polling 하는 것이다. 이것을 busy-wating 이라고 한다. 하지만 폴링 알고리즘은 CPU 시간의 낭비를 초래한다.

Event Demultiplexing

busy-wating은 논 블로킹 리소스를 처리하기 위해서 이상적인 방법이 아니다.
대부분의 OS 에는 논 블로킹 리소스를 효율적으로 처리 하기 위한 기본적인 메커니즘을 지원한다.
이 메커니즘을 이벤트 디멀티플렉서 라고 부른다.
flow는 다음과 같다.

1. 기본적인 리소스가 존재함. 각 리소스를 연산과 연결 (ex. FOR_READ, FOR_WRITE)
2. 디멀티플렉서가 동기식으로 관찰되는 리소스들 중에서 읽을 준비가 된 리소스가 있을 때 까지 블로킹됨.
3. 리소스가 ready 상태가 되면 처리를 위한 새로운 이벤트 세트를 반환.
4. 디멀티플렉서에서 반환된 이벤트 처리

요약 하면 이벤트 디멀티플렉서는 read,write 리소스가 준비되어 있는지 관찰하고, 준비된 리소스가 생기면 이벤트 세트를 반환하는 역할이다.
여기서 재미있는 부분은 busy-wating 을 사용하지 않고 싱글스레드 에서 여러 I/O를 다룰 수 있다는 부분이다.

작업의 분배는 여러 스레드에 분산되는 대신에 시간에 따라 분산된다.
이것이 전체적인 유휴시간을 최소화 시키는데에 이점이 있는 부분도 그림을 통해 알 수 있다.

Reactor Pattern

리엑터 패턴의 주된 아이디어는 I/O 는 작업에 연관된 핸들러를 갖는다. Node.js 에서는 callback 함수라고 생각하면 됨.

1. 에플리케이션은 이벤트 디멀티플렉서에 요청을 전달함으로 새로운 I/O 작업을 생성한다.
   작업이 완료 되었을 때 호출할 핸들러도 명시한다.
(이때 디멀티플렉서는 여러 리소스들을 관찰한다. 리소스가 준비되면 이벤트를 반환.)
2. 이벤트 디멀티플렉서는 이벤트 작업들을 이벤트 큐에 넣는다.
3. 이벤트 루프가 이벤트 큐의 항목을 순환한다.
4. 이벤트와 관련된 핸들러가 호출된다.
5. 핸들러의 실행이 완료되면 제어권을 이벤트 루프에 되돌려준다.(5a)
   핸들러 실행중에 다른 비동기 작업을 요청 할 수 있고, 이벤트 디멀티 플렉서에 새로운 항목을 추가한다.(5b)
6. 이벤트 큐의 모든 항목이 처리되고 나면 이벤트 루프는 이벤트 디멀티플렉서에서 블로킹됨.
   처리가능한 새 이벤트가 있을 경우 이 과정이 다시 트리거 된다.

Node.js 에서는 libuv 엔진을 사용하는데, 이는 리엑터 패턴을 구현하고 있고, 이벤트 루프의 생성, 이벤트 큐의 관리, 비동기 I/O 작업의 실행 및 다른 유형의 작업을 큐에 담기위한 API 를 제공한다.

0개의 댓글