Node.js 디자인 패턴 바이블을 읽고 개인적으로 기록한 글입니다. 문제가 있을 경우 알려주시면 관련된 모든 게시글을 비공개로 전환하도록 하겠습니다.
철학
Node.js는 프로그램 코드를 구성하는 기본적인 수단으로써 모듈 개념을 사용한다. 여기서 모듈이란 애플리케이션과 재사용 가능한 라이브러리를 만들기 위한 구성 요소로 Unix 철학에 근거하고 있다.
- 작은 것이 아름답다.
- 각 프로그램은 한 가지 역할만 잘하도록 만들어라
Node.js는 패키지 관리자(npm, yarn)의 도움을 받아 자신의 필요한 종속성 패키지를 갖도록 함으로써 종속성 지옥에서 벗어나게 해 준다. 이렇게 작은 모듈은 재사용성 외에도 여러 장점이 있다.
작은 모듈을 구성하기 위해선 명백한 단일 진입점을 제공해 최소한의 기능을 노출하도록 하는 것이 일반적인 패턴으로 이렇게 만들어진 모듈은 구현이 단순해지고 유지관리가 용이해지게 된다.
단순함이야말로 궁극의 정교 함이다 - 레오나르도 다빈치 -
완벽하게 모든 기능을 갖춘 소프트웨어와는 반대로 단순하게 설계하는 것이 좋은 실천이다. 빠르게 구현하고 가볍게 보급 가능하며, 유지보수가 쉽고 이해를 빨리할 수 있는 코드가 소프트웨어 자체의 성장과 향상을 돕게 된다.
동작 원리
I/O는 컴퓨터의 기본적인 동작들 중에서 가장 느리다. 전통적인 블로킹 I/O는 I/O 요청하는 함수의 호출은 작업이 완료될 때까지 스레드의 실행을 차단한다. 이런 방식으로 구현된 웹 서버는 각각의 요청들은 자신이 끝나기 전까지 다른 요청들을 차단하기 때문에 정상적인 서비스를 제공할 수 없을 것이다. 이런 문제를 해결하기 위한 전통적인 방법은 요청들은 각각의 스레드가 처리하지만 스레드는 메모리를 소모하고 컨텍스트 전환을 유발하여 메모리와 CPU 사이클을 낭비하게 된다.
대부분의 최신 운영체제는 리소스에 접근하기 위해서 블로킹 I/O 외에도 논 블로킹 I/O를 지원한다. 논 블로킹 I/O에서 시스템 호출은 데이터를 쓰거나 읽여 지기를 기다리지 않고 항상 즉시 반환된다. 하지만 이 방식은 리소스가 준비될 때까지 바쁜 대기(busy-waiting)를 수행해 CPU의 엄청난 낭비를 초래하게 된다.
따라서 효율적으로 논 블로킹 리소스를 처리하기 위해서 운영체제는 동기 이벤트 디멀티플렉서(또는 이벤트 통지 인터페이스) 메커니즘을 제공한다.
대기리소스.add(socket, FOR_READ)
대기리소스.add(file, FOR_READ)
while (events = demultiplexer.watch(대기리소스)) {
for (event of events) {
data = event.resource.read()
if (data === CLOSED_RES) {
대기리소스.unwatch(event.resource)
} else {
consumeData(data)
}
}
}
I/O 작업을 위한 리소스 대기열을 감시하며 읽을 준비가 된 리소스가 생겨 이벤트 세트가 반환될 때까지 블로킹 상태로 대기하다 반환된 이벤트 세트를 처리하기를 반복한다. 이를 이벤트 루프라고 한다.
이전의 리소스가 처리되기까지 폴링 방식으로 매번 요청한 리소스에 대해 상태를 검사하는 것과 달리 요청 리소스들을 블로킹으로 대기하다 완료된 리소스에 대해서 처리를 수행하는 이벤트 루프를 사용하면 단일 스레드만으로 동시적 다중 I/O 사용 작업에 시간에 따라 분산되어 처리가 가능하게 된다. 뿐만 아니라 멀티 스레드에서 발생하게 되는 경쟁 상태와 동기화 문제가 없는 것으로 더 간단한 동시성 전략을 사용 가능하게 해 준다.
리액터 패턴
방금 제시된 알고리즘에 특화된 리액터 패턴은 Node.js에서 비동기 요청 작업에 콜백 함수를 지정해주는 방식을 주된 아이디어로 요청 작업이 이벤트 루프에 의해 처리되는 즉시 호출되게 된다.
- 비동기 작업을 이벤트 디멀티플렉서에 요청과 콜백을 함께 전달해 새로운 I/O 작업을 생성한다. 이때 새 요청을 전달하는 작업은 논 블로킹으로 제어권은 애플리케이션으로 즉시 반환된다.
- 일련의 I/O 작업들이 완료되면 이벤트 디멀티플렉서는 대응하는 이벤트 작업들을 이벤트 큐에 집어넣는다.
- 이벤트 루프는 이벤트 큐의 항목을 순환하며 콜백을 호출한다.
- 콜백이 실행이 완료되면 제어권을 이벤트 루프에 돌려준다. 만약 콜백 내부에 다른 비동기 작업을 요청할 경우 이벤트 디멀티플렉서에 새로운 항목을 추가한다.
- 이벤트 큐가 모두 처리되면 이벤트 루프는 처리 가능한 새 이벤트가 올 때까지 블로킹되어 대기한다.
'메모' 카테고리의 다른 글
우당탕탕 k8s 기록 (0) | 2023.11.10 |
---|---|
Type<Challenge[]> #Medium (0) | 2022.02.05 |
Type<Challenge[]> #Easy (0) | 2022.02.04 |
Typescript로 구현한 Linked list (0) | 2022.02.01 |
JS의 메모리관리 (0) | 2022.01.13 |