DOKKAEBI HILL

방문을 환영합니다!

모든 포스팅 >

이벤트 루프 [0] 2025-01-21
sync/async & blocking/non-blocking 2 [0] 2025-01-20
[실전]Asynchronous I/O 적용 [0] 2025-01-14
sync/async & blocking/non-blocking [0] 2025-01-12
CPU Bound - I/O Bound [0] 2025-01-10

이벤트 루프

open_in_new

이전 포스팅에서 Asynchronous & Non-Blocking 방식이 I/O를 효과적으로 처리할 수 있는 다는 것을 다루었습니다. 

이 방식으로 동작하는 대표적인 프레임워크가 node.js와 FastAPI입니다.

Asynchronous & Non-Blocking I/O는 어떻게 이들 프레임워크에서 처리가 되는 지 알아보겠습니다.


서버가 Async & Non-blocking 방식으로 응답하는 것 말고 다른 방법이 없던 것은 아닐 겁니다.

대표적인 방법이 스레드 풀을 이용해서 요청이 오면 스레드로 처리를 하는 방식이 있습니다. 스레드 수가 부족하다면 점점 늘리면 됩니다.

하지만 한계가 있겠죠, 스레드 하나당 1MB 정도의 메모리 용량을 차지한다고 합니다. 또한 스레드 수가 많아질수록 context-switching

overhead가 생기며, 프로세스간 통신을 한다면 IPC를 구현해야하고, race-condition 같은 문제를 마주할 가능성이 높아집니다. 


유닉스 기반에 운영체제에서는 "모든 것은 파일로 이루어져 있다" 라는 말이 있습니다. 
모든 동작은 결국 파일을 읽고 쓰는 것이다 라는 의미입니다. 소켓 통신을 통해 주고 받는 데이터 역시 파일이라고 생각하면 됩니다. 

멀티 스레딩을 이용한 서버 처리 방식은 위 이미지와 같을 것입니다. 

앞서 말한 것처럼 이런 방식에는 한계가 있습니다. 


I/O multiplexing

지난 포스팅에서 잠깐 언급이 되었습니다. 

멀티플렉싱이란 하나의 채널을 통해서 여러 통신을 할 수 있는 기술입니다.

통신을 위해서 소켓마다 스레드를 할당하는 것이 아니라 하나의 프로세스(스레드)에서 여러 소켓의 입출력을 관리하는 것입니다. 

그렇다면 어떻게 이게 가능할까요?


소켓=파일 이라는 관점에서 각 소켓은 file descriptor를 가집니다.

fd(file descriptor)을 통해서 대상 파일, 권한(read, write, update(read & write))을 알 수 있으며, fd를 통해서 소켓을 관리할 수 있습니다. 

select, poll, epoll은 여러 fd를 모니터링해 어떤 소켓에 어떤 이벤트가 발생했는지 알 수 있도록 하는 system call입니다. 

epoll(UNIX)은 select와 poll의 단점을 보완한 것이며, 운영체제에 따라서 kqueue(BSD), IOCP(MS Windows)가 있습니다.

( select, poll : 타겟 fd 수에 비례하여 성능 부담이 생김 O(n), epoll은 커널 레벨에서 직접 fd를 관리하여 성능 부담이 적음)


무한 루프를 돌며 그 안에서 epoll이 fd를 모니터링하고 이벤트가 발생한 fd들을 핸들링하는 방식으로 I/O multiplexing의 동작 매커니즘이고, 

이것이 곧 이벤트 루프입니다. (또한 이는 EDP, event driven programming 방식)


libuv와 node.js

이벤트 루프의 실체에 대해서 알게 되었습니다. 선배 개발자들은 이런 매커니즘을 쉽게 가져다 쓸 수 있도록 구현해놓았고 그 중 대표적인 것이

libuv 입니다. libuv는 C++로 구현된 Cross-platform asynchronous I/O입니다. node.js와 uvloop 내부에도 libuv가 쓰였습니다!

( libuv의 이름에 uv가 무슨 기술을 함축한 것인 줄 알고 검색해보았으나 아무 뜻이 없었다고 한다... 사람들이 하도 물으니 Unicorn Velociraptor 를 갖다 붙였고, 이게 로고 이미지가 되었다는 .. 어이없는 사실과 어이없는 양형님식 유머? )


node.js은 'single-threaded 기반의 non-blocking I/O를 지원하는 javascript 런타임 환경'라는 문구로 소개를 하고 있습니다. 

single-threaded로 어떻게 요청을 처리하지? 에 대한 답은 node.js의 중요 부분을 차지하는 libuv로 설명이 되었을 거라 생각합니다. 

그리고, node.js는 single thread 인데 multiple threads 입니다. 

그들이 single thread라고 하는 것은 그들의 V8 엔진이 event-loop이 돌고 있는 main thread (single)에서 돌고 있어서 그렇게 표현한 것이며

실제 요청(method)의 종류에 따라서 메인 스레드에서 실행하거나 이벤트 루프와 스레드 풀을 함께 사용해서 처리합니다. (아래 그림 참고)

node.js를 구성하는 여러 API와 libuv가 스레드에 접근하여 관리합니다. default로는 4개의 스레드를 사용하고, 

설정을 통해서 늘릴 수 있습니다. 

UV_THREADPOOL_SIZE=110 && node index.js
or
process.env.UV_THREADPOOL_SIZE=62 from code.

그리고 event loop와 함께 event queue, task queue으로 이벤트에 대한 관리와 callback(task queue)을 관리합니다. 


node.js의 내부 구조에 대해서도 알아보았습니다. 

node.js처럼 single-threaded based asynchronus/non-blocking I/O 방식의 서버 개발 프레임워크가 많이 부상하는 것 같습니다. 

비동기처리가 multi threading 약점에 대한 대안? (경우에 따라 성능이 더 좋다는 글도 본 것 같습니다.)이 된다는 것을 보여주는 것 같습니다.


참조 : https://medium.com/preezma/node-js-event-loop-architecture-go-deeper-node-core-c96b4cec7aa4



게시글을 삭제하시겠습니까?

'delete'를 입력하고 '삭제' 버튼을 눌러주세요.

All rights reserved.

GUEST님 환영합니다! ^_^