Back_End/Node.js

[ 백엔드 공부하기 : Node.js ] NodeJS의 비동기(Async) 흐름 제어 및 이벤트 루프.

안다미로 : Web3 & D.S 2024. 12. 10. 17:34

 

 

 

[ 백엔드 공부하기 : Node.js ] 

NodeJS의 비동기(Async) 흐름 제어 및 이벤트 루프.

 


 

∇백엔드_NodeJS : Node.JS의 비동기 흐름 제어 및 이벤트 루프는?

목   차

1. NodeJS의 비동기 흐름이란 무엇인가?
2. NodeJS의 이벤트 루프
3. 콜스택 및 비동기 API 이해하기.
4. Node.js에서 비동기 프로그래밍의 이점.
5. 일반적인 함정과 이를 피하는 방법.
6. 결론 정리.

 

 


Ⅰ. NodeJS의 비동기 흐름이란 무엇인가?


 ※ 비동기 흐름은 주요 프로그램 흐름이 차단되지 않도록

      Node.JS가 처리하고 실행하는 방식을 말합니다.

 

 

  Node.js는 크롬의 V8 자바스크립트 엔진을 기반으로 구축된 서버 사이드 런타임 환경입니다. 

 

  1. 효율적인 동시 작업 관리: Node.js는 동시 작업을 효율적으로 관리하여 리소스 활용을 최적화합니다.
  2. 비동기 작업 처리:
    • 파일 I/O, 네트워크 요청, 데이터베이스 쿼리 등 다양한 작업을 별도의 백그라운드 스레드에 위임합니다.
    • 이를 통해 메인 스레드는 다른 작업을 계속 진행할 수 있습니다.
  3. 결과 반환 메커니즘:
    • 백그라운드 작업이 완료되면 결과는 콜백, 프로미스 또는 async/await 메커니즘을 사용하여 메인 스레드로 반환됩니다.
  4. 응답성과 확장성:
    • 이러한 접근 방식을 통해 Node.js는 응답성과 확장성을 유지하며, 여러 동시 작업을 효과적으로 처리할 수 있습니다.
  5. 고성능 애플리케이션 구축:
    • Node.js는 논블로킹 방식으로 고성능 애플리케이션을 구축하는 데 선호됩니다.

     

 

◎ Node.js의 비동기 흐름과 이벤트 루프.

 

Node.js의 비동기 흐름의 중심에는 이벤트 루프가 있습니다.

이벤트 루프는 비동기 작업을 효율적으로 관리하고 실행하는 데 중요한 역할을 합니다.

 

  • 역할: 이벤트 루프는 비동기 작업을 스케줄링하고 실행하는 역할을 담당합니다.
  • 작동 방식: 이벤트 루프는 태스크 큐를 지속적으로 모니터링하여, 메인 스레드가 유휴 상태가 되면 보류 중인 작업을 실행합니다.
  • 장점: 이를 통해 Node.js의 응답성을 향상시키고, 동시 작업을 원활하게 처리할 수 있습니다.

이제 Node.js 이벤트 루프의 세부 사항을 자세히 살펴보고,

이 루프가 Node.js의 비동기 흐름을 어떻게 구동하는지 이해해 보겠습니다.

 

 


Ⅱ. NodeJS의 이벤트 루프.


 

    ▣ 이벤트 루프는 비동기 작업을 효율적으로 관리할 수 있도록 해주는 NodeJS의 핵심 요소입니다.

 

         - 이벤트 루프는 이벤트 큐에서 보류 중인 이벤트를 지속적으로 모니터링하여 애플리케이션의 응답성을 유지합니다.

              [ NodeJS의 이벤트 루프는 간단하면서도 매우 효과적인 매커니즘을 따릅니다.]

 

 

               ◇ 이벤트 등록 

                     :: 파일 읽기 또는 네트워크 요청과 같은 비동기 작업이 시작될 때마다,

                            해당 이벤트가 등록되어 이벤트 큐에 추가됩니다.

 

               ◇ 이벤트 루프 실행

                     :: 이벤트 루프는 이벤트 큐에서 보류 중인 이벤트를 지속적으로 확인합니다.

                         -> 이벤트가 완료되면, 이벤트 큐에서 제거되고 관련 콜백의 실행을 위해서 Nodejs 콜스택에 추가됩니다.

 

               ◇ 콜백 실행.

                     :: 큐에서 제거된 이벤트와 관련된 콜백이 실행되어서, 애플리케이션이 이벤트에 응답 가능해집니다.

 

               ◇ 논블로킹 실행.

                    :: Nodejs의 비동기 API는 작업이 완료될 때까지 기다리는 동안 앱이 다른 작업을 계속 실행할 수 있도록 보장.

                         -> I/O 집약적, CPU 집약적에 작업에 대해 높은 성능을 제공합니다.

 

nodejs 이벤트 루프의 예시

 

 

                      √ Nodejs가 시작되면 이벤트 루프가 초기화되고, 입력 스크립트가 처리됩니다.

                             =>> 이 입력 스크립트에는 비동기 API 호출과 타이머 스케쥴링이 포함될 수 있습니다.

 

                      √ Nodejs는  'libuv' 라는 전용 라이브러이 모듈을 사용하여 비동기 작업을 처리합니다.

                                 - libuv는 Node의 기본 로직과 함께 'libuv' 쓰레드-풀로 알려진 특수 스레드 풀을 관리합니다.

   

                      √  'libuv' 스레드 풀은 기본적으로 4개의 스레드로 구성되며,  이 스레드들은

                            이벤트 루프에 너무 많은 리소스를 필요로 하는 작업을 분할수행하는 역할을 담당합니다.

                                    =>> I/O작업, 커넥션 열기 및 닫가ㅣ, setTimeouts 처리 등이 포함.

 

                      √  'libuv' 스레드 풀이 작업을 완료하면 해당 콜백 함수가 호출됩니다. 

                           이 콜백 함수는 잠재적인 오류를 처리하고 기타 필요한 작업을 수행합니다.

                          그 후 콜백 함수가 이벤트  큐에 추가됩니다.

                           콜스택이 비게 되면 이벤트 큐의 이벤트가 처리되어 콜백을 콜스택에 배치하여 실행할 수 있습니다.

 

                      √  NodeJS 이벤트 루프는 6단계로 구성되며, 각 단계는 특정한 작업을 전담합니다. 

                            (이전 작성한 글 중 이벤트루프에 대한 설명 참조)

  

                     ● Timers : 타이머 단계에서는,  'setTimeout()' 및 'setInterval()' 에 의해 예약된 콜백을 실행합니다.

                           ->> 이러한 콜백은 지정된 시간이 경과한 후, 가능한 한 빨리 트리거됩니다.

                                    but, 운영체제스케쥴링 혹은 다른 콜백의 실행 등의 외부 요인으로 실행 지연 가능.

 

                     ● Pending callbacks: 이 단계는 TCP 오류 처리와 같은 특정 시스템 작업에 대한 콜백을 실행하는데 사용.

 

 

                     ● idle, prepare:  유휴 단계는 내부적으로만 사용됩니다. 

                              -> 이 단계에서는 이벤트 루프가 작업을 능동적으로 처리하지 않으므로 

                                      가비지 컬렉션과 같은 백그라운드 작업을 수행할 수 있는 기회를 제공합니다.

 

 

                     ● poll :  폴링 단계에서는 I/O 차단 및 폴링에 적절한 기간을 계산하고

                                  폴링 큐의 이벤트를 처리하는  두 가지 주요 기능이 실행 됩니다.

                                        -> 이벤트 루프가 예약된 타이머 없이 폴링 단계에 들어가면 폴링 큐를 확인.

 

                                        -> 폴링 큐에 콜백이 포함되어 있으면

                                             큐가 비거나 시스템의 한계에 도달할 때까지 콜백이 동기적으로 실행됩니다.

 

                                        -> 폴링 큐에 콜백이 포함되어 있지 않으면

                                            이벤트 루프는 'setImmediate()' 스크립트가 예약을 확인하고,

 

                                            예약이 있다면 확인 단계를 진행하거나 새 콜백이 큐에 추가될 때까지 기다렸다가

                                             즉시 콜백을 실행합니다.

 

                                        -> 폴링 큐가 비게 되면 이벤트 루프는 타이머가 시간 임계값에 도달했는지 확인하고,

                                                 도달한 경우, 타이머 단계로 다시 이동하여 해당 콜백을 실행합니다.

 

                     ● check :  확인 단계에서는 큐에 추가된 모든 'setImmediate()' 콜백을 호출합니다. 

                              -> 코드가 실행되면, 이벤트 루프는 결국 폴링 단계에 도달합니다

                              -> but,   'setImmediate()를 사용하여 콜백이 예약되어 있고 폴링 단계가 유휴 상태가 되면

                                    이벤트 루프는 폴링 이벤트가 발생할 대까지 기다리지 않고 바로 확인 단계로 진행됩니다.

 

                     ● Close 콜백:  소켓이나 핸들이 갑자기 닫히면, 이 단계에서 'close' 이벤트가 발생합니다.

                             but, 즉시 종료가 아닌 경우에는 process.nextTick() 을 사용하여 close 이벤트가 발생합니다.

 


 

Ⅲ. 콜스택 및 비동기 API 이해하기.


 

    ◇ 콜스택은 프로그램 내에서 함수 호출을 추적하는 데이터 구조로 동작합니다.

       -> 함수가 호출되면 스택의 맨 위에 추가되고, 완료되면 제거되는 "후입선출(LIFO)"의 순서를 따릅니다.

 

 

                        ∇ Nodejs는 처음에 스크립트에 대한 전역 실행 컨텐스트를 생성하여 스택의 맨 아래에 배치.

 

                        ∇ 호출되는 각 함수에 대한 함수 실행 컨텍스트를 생성하여 스택에 배치합니다.

 

                        ∇이러한 실행 컨텍스트의 스택을 '콜스택'이라고 합니다.

 

 

Node.js는 비동기적으로 동작하도록 설계되어, 비동기 작업의 결과를 관리하기 위해 콜백(callback) 또는

프로미스(promise)를 사용하는 다양한 API를 제공합니다.

  1. 비동기 함수 호출: 비동기 함수가 호출되면,
                                  Node.js 런타임 환경으로 오프로드되어 이벤트 루프가 다른 작업을 계속 처리할 수 있습니다.
  2. 콜백 큐: 비동기 작업이 완료되면, 연결된 콜백이 콜백 큐에 배치되어 이벤트 루프의 실행을 기다립니다.
  3. 콜스택 처리: 콜스택이 비어 있으면,
                          이벤트 루프는 콜백 큐에서 첫 번째 콜백을 선택하여 실행을 위해 콜스택으로 푸시합니다.

이러한 접근 방식은 비동기 작업이 메인 스레드를 차단하지 않도록 하여 애플리케이션의 응답성을 높이는 데 기여합니다.

 

 


 

 

Ⅳ. Node.js에서 비동기 프로그래밍의 이점.


 

Node.js의 비동기적 특성은 개발자와 애플리케이션에 여러 가지 주목할 만한 이점을 제공합니다.

  1. 확장성 및 동시성: Node.js는 비동기적 특성 덕분에 많은 수의 동시 연결을 효율적으로 처리할 수 있습니다.
                                 논블로킹 I/O 작업과 비동기 이벤트 처리를 활용하여, 과도한 리소스를 소비하지 않고도
                                 여러 클라이언트에 동시에 서비스를 제공할 수 있습니다.

  2. 리소스 효율성: Node.js는 단일 스레드 이벤트 루프를 사용하여 여러 개의 동시 연결을 처리합니다.
                            이로 인해 각 연결에 대한 스레드를 생성하고 관리하는 오버헤드가 줄어들어서
                            메모리 사용률과 리소스 효율성이 향상됩니다.

  3. 응답성 향상: 비동기 작업은 시간이 많이 걸리는 작업 중에도
                         애플리케이션이 응답하지 않도록 하여 사용자 경험을 개선합니다.

  4. 간소화된 코드: 비동기 모델을 사용하면 개발자는 복잡한 제어 흐름과 "콜백 지옥"을 피하면서
                             깔끔하고 간결한 코드를 작성할 수 있습니다.
                             비동기 API는 프로미스 및 async/await를 사용하여 코드베이스의 가독성과 유지보수성을 높여줍니다.

  5. 쉬운 디버깅: Node.js의 비동기 작업은 의미 있는 오류 메시지를 제공하도록 설계되어 있어
                          문제를 쉽게 식별하고 해결할 수 있습니다.

     


Ⅴ. 함정과 이를 피하는 방법.


  1. 이벤트 루프 차단
    • CPU 집약적인 작업을 실행하면 이벤트 루프가 차단되어 애플리케이션 성능이 저하되고,
      동시성이 감소하며, 사용자 경험이 나빠질 수 있습니다.

    • 해결책: 비동기 API와 논블로킹 I/O 작업을 활용하여
                   이벤트 루프가 다른 이벤트를 계속 처리할 수 있도록 작업을 백그라운드로 위임합니다.
  2. 콜백 지옥
    • 여러 콜백을 연결하면 코드가 깊게 중첩되어 읽기 어려워질 수 있습니다.

    • 해결책: 프로미스 또는 async/await 방식을 사용하여 코드의 가독성과 유지보수성을 개선합니다.
  3. 포착되지 않은 예외
    • 비동기 작업에서 처리되지 않은 오류는 애플리케이션을 중단시킬 수 있습니다.

    • 해결책: 적절한 오류 처리 메커니즘을 구현하여 예외를 원활하게 처리하고 애플리케이션 장애를 방지합니다.
  4. 메모리 누수
    • 이벤트 리스너를 잘못 관리하면 메모리 누수가 발생할 수 있습니다.

    • 해결책: 더 이상 필요하지 않은 이벤트 리스너는 반드시 제거하여 불필요한 메모리 소비를 방지합니다.
  5. 비동기 작업 남용
    • 모든 작업이 비동기일 필요는 없습니다.

    • 해결책: 성능과 코드 명확성 간의 적절한 균형을 맞추기 위해 동기 및 비동기 작업을 신중하게 선택합니다.

 


Ⅵ. 결론.


비동기 프로그래밍은 대규모 동시 작업을 효율적으로 처리하는 강력한 패러다임입니다.

 

Node.js는 뛰어난 동시성과 확장성을 달성하기 위해 이 접근 방식을 크게 활용합니다.

 

이벤트 루프, 비동기 API, Node.js 콜스택을 중심으로 한 비동기적 특성 덕분에

비동기 작업을 효율적으로 관리할 수 있어 응답성이 뛰어난 애플리케이션을 제공할 수 있습니다.

 

비동기 흐름의 이점을 활용함으로써 개발자는 이벤트에 실시간으로 반응하는 고성능의 확장 가능한 애플리케이션을

개발하여 사용자에게 원활하고 효율적인 경험을 제공할 수 있습니다.

 

그러나 이벤트 루프가 차단되거나 콜백 지옥에 빠지는 등의 잠재적인 문제를 염두에 두고,

원활한 실행과 오류 처리를 보장하기 위해 모범 사례를 채택하는 것이 중요합니다.

 

전반적으로 Node.js의 비동기적 특성은

최신 반응형 서버 사이드 애플리케이션을 구축하기 위한 강력한 기반을 제공합니다.