티스토리 뷰

728x90
반응형

JavaScript는 Single Thread 기반이기 때문에 기본 동기 방식으로 동작한다. 이는 높은 동시성을 요구하는 애플리케이션의 성능 저하를 일으키는 요인으로 작용한다.

Node.JS는 이와 같은 JavaScript의 Single Thread 방식을 개선하고 비동기 처리 방식을 지원하기 위해 Event Loop & Callback Function을 활용한다.

 Event Loop 

Event Loop는 JavaScript가 비동기 Thread를 실현하도록 하기 위한 중요한 개념 중 하나이다. 즉 이벤트 루프의 동작 방식을 이해하면 Node.JS를 기반으로 개발 시 높은 퍼포먼스를 나타낼 수 있다. Node.JS는 JavaScript의 사상을 그대로 적용하여 이벤트 기반 서비스라고 한다.

 Event Listener 

JavaScript는 이벤트를 실행하기 위한 Event Listener를 생성하여 이벤트 발생 시 대응하는 동작을 정의하도록 구성되어 있다.

 

[EventListener List]

- onblur(객체가 focus를 잃었을 때)

- onchange(객체의 내용이 바뀌고 focus를 잃었을 때)

- onclick(클릭했을 때)

- ondblclick(더블클릭할 때)

- onerror(에러가 발생했을 때)

- onfocus(객체에 focus가 되었을 때)

- onkeydown(키를 눌렀을 때)

- onkeypress(키를 누르고 있을 때)

- onkeyup(키를 눌렀다 뗐을 때)

- onload(문서나 객체가 로딩되었을 때)

- onmouseover(마우스가 객체 위에 올라왔을 때)

- onmouseout(마우스가 객체 바깥으로 나갔을 때)

- onreset(Reset 버튼을 눌렀을 때)

- onresize(객체의 크기가 바뀌었을 때)

- onscroll(스크롤바를 조작할 때)

- onsubmit(폼이 전송될 때)

 

이와 같은 Event Listener는 정의된 이벤트 발생 시 addEventListener or removeEventListener 등으로 이벤트를 연결하거나 제거하여 이벤트에 대한 대응을 처리한다.

 Promise 

Node.JS의 Event Loop & Callback에 대해 살펴보기 전 중요한 개념 한가지를 먼저 살펴보도록 하자. 바로 Promise이다.

앞서 살펴본 봐와 같이 JavaScript는 Callback 함수를 통해 비동기 처리를 수행한다. 솔직히 Event 처리에 대한 명확한 규칙과 불필요한 남용을 하지 않는다면, Promise가 등장하지 않았을 것이다. 그럼에도 불구하고 Promise가 등장하게 된 주요 원인은 바로 점점 JavaScript가 차지하는 범위가 늘어나고 이로인해 복잡도가 늘어남에 따라 콜백의 중첩이 발생하여 콜백 개미지옥에 갖히는 경우가 발생하기 시작하면서이다.

CallBack이 실행되는 과정에서 비동기 처리는 async method 1 → callback async method 2 callback → async method 3 callback ..... 의 반복으로 구성되어 있다. 이는 비동기 처리를 유도하지만, 하나의 Thread에서 동작하는 JavaScript의 처리 방식을 개선하는 것일 뿐 그 Depth가 깊어 질 수록 복잡한 처리 프로세스를 갖게 된다.

Promise는 이러한 복잡한 구조의 비동기 처리 프로세스를 간결하고 정돈되게 만들어 주는 역할을 한다.

JavaScript에서 Promise는 비동기 작업의 순차 처리, 병렬 처리 등을 처리하는데 보다 수월하고 가독성 높은 코드를 작성할 수 있도록 도와 준다. 또한 복잡한 처리 프로세스의 단점이라 할 수 있는 오류 처리를 보다 가시적으로 표출할 수 있도록 관리 기능을 제공한다.

Promise는 그 뜻 그대로 다음과 같은 서비스를 제공한다.

 

"Promis : 현재 처리 가능한 Thread가 없으니, 잠시 대기해주면 처리가능한 상태가 되었을 때 알려 줄께. 약속!"

 

이는 별거 아닌것 같지만, Thread의 다중 요청에 대해 순차처리를 지원하는 중요한 개념이라 할 수 있다.

Promise는 상태를 나타내기 위해 4가지 state를 정의하며, 바로 pending, fulfilled, rejected, settled 이다.

 

- Pending : Promise가 약속한 내용에 대해 대기하는 상태 (Fulfilled 또는 Rejected가 호출되기 전 상태)

- Fulfilled : Promise가 약속한 내용이 정상적으로 처리된 상태 (성공한 상태)

- Rejected : Promise가 약속한 내용이 거절 된 상태 (실패한 상태)

- Settled : Promise가 약속한 내용이 처리가 완료된 상태 (Fulfilled 또는 Rejected가 호출 완료된 상태)

 

이 상태 값들은 다음과 같이 동작한다.

위 예시는 MDN에 명시 된 Promise 처리 프로세스를 재구성한 도식이다. Promise가 모든 비동기 요청을 관리하고 처리하고 리턴하기 때문에 요청을 단일 유입점으로 관리하여 장애 처리 및 비동기 호출 방식의 다각화를 갖어갈 수 있다.

 

1) Job은 API, DB 요청 등을 수행하기 위해 Promise 객체를 통해 요청한다.

2) Promise를 통해 요청한 API, DB 조회 등의 요청은 setTimeout에 의해 비동기 처리를 위한 일시 대기가 발생한다.

3) 요청 처리가 가능해 지면, 특정 요구사항에 대한 비즈니스 로직을 수행한다.

4) Promise는 결과를 Fulfilled, Rejected로 전달한다.

5) 성공 시 Fulfilled는 Async Actions를 통해 return 값을 조합하여 Promise로 전달한다. 실패 시 Rejected는 에러 값을 조합하여 Promise로 전달한다.

6) Promist는 전달 받은 Return 값을 요청자에게 전달한다.

위와 같은 프로세스는 불특정 요청에 대한 비동기 처리를 지원하고, 모든 요청을 Promise가 관장하여 관리 용이성을 제공한다.

 Node.JS의 Event Loop 

다음은 Node.JS의 Event Loop 동작 방식이다. Node.JS의 이벤트 루프는 시스템 커널에 Task를 Offloading함으로써 JavaScript의 Single Thread라는 약점을 개선하는 방식으로 사용된다.

위 이미지는 Node.JS Application이 Event Loop를 통해 병렬로 동시에 처리되는 과정을 설명한다. 동시에 4개의 Task가 요청이 들어올 경우 Event Loop는 Task의 처리 과정을 관장하여 동시에 Multi Thread 환경 처럼 동작하도록 관리한다. 이는 실제는 Node.JS에서 처리되는 것이 아닌 Node.JS가 기동되어 있는 호스트 커널의 Multi Thread 동작방식을 이용한 것이다.

Node.JS의 Event Loop는 총 6단계의 처리 프로세스를 갖는다.

각 단계는 무한루프를 돌며 처리 되고, 단계 별 실행할 FIFO 콜백 대기열이 존재한다. 이벤트 루프가 특정 단계에 진입하면 해당 단계와 관련된 작업을 수행한 다음 큐가 소진되거나 최대 콜백 수까지 ​​해당 단계의 큐에서 콜백을 실행한다. 큐가 소진되었거나 콜백 처리 임계치에 도달하면 이벤트 루프가 다음 단계로 이동하는 방식으로 처리된다. 다음은 각 단계별 상세 설명이다.

 

[단계 개요]

  • timers :이 단계는 setTimeout(), setInterval()에 의해 예약된 callback 실행

  • pending callbacks : 루프 반복으로 지연된 I/O callback 실행

  • idle, prepare : event loop 내부 동작을 위해 실행

  • poll : 새로운 I/O 이벤트를 검색하여, I/O 관련 callback을 실행 (close callback, scheduled by timer callback, setImmediate() 제외)

  • check : setImmediate() callback 실행

  • close callbacks : socket.on('close', ...)와 같은 callback close 실행

 

 

이벤트 루프의 각 단계 사이에서 Node.js는 비동기 I/O 또는 타이머를 기다리고 있는지 확인하고 타이머가 없으면 즉시 종료된다.

 결론 

이벤트 루프는 JavaScript를 효과적으로 처리하기 위한 한가지 방식으로 Node.JS는 기동 시 자동으로 Event Loop가 로딩되도록 설계되어 있다. JavaScript의 Single Thread 방식을 개선하기 위해 우리는 callback 함수를 바로 사용할 수 있지만, 여전히 callback의 중첩으로 인한 무한 반복, loop 탈출 불가 등의 현상을 맞이하게 되며, 특히 가독성이 떨어져 이후 유지보수의 한계를 나타낸다. 이를 극복하기 위해 JavaScript는 Event Loop를 추가하여 JavaScript가 동작하는 호스트 OS의 커널에 Task work thread를 offloading하여 multi thread 환경과 동일하게 처리할 수 있는 기반 환경을 제시하며, 이는 Event Loop가 관리하여 Event의 유입, 처리, 반환의 일련과정이 하나의 Component에서 관리되어 유연한 관리 체계 및 장애 추적 등이 함께 제공될 수 있도록 기능을 제시해 준다는데에 의미가 있다.

728x90
반응형