ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [NodeJS] C10K Problem 과 NodeJS
    ✍️ 개인 스터디 기록 2022. 11. 27.

    C10K Problem 란?

    하드웨어가 충분한데도 불구하고 10K만큼의 소켓을 열게 된다면 OS에서 제공하는 I/O 처리방식의 문제 때문에 프로세스가 제대로 처리하지 못한다 (10K가 한계란 뜻은 아니고 많다는 뜻)

     

    ⇒ 🧐 I/O 처리방식에 어떤 문제가 있길래 저만큼 소켓을 열지 못했던 걸까?

     

    리눅스 select, poll

    20세기말 리눅스 네트워크 I/O모델은 select()과 poll()이 전부였다.

    select은 클라이언트 1024개만 처리 가능했고, poll의 경우 제한은 없었다.

    두 모델 모두 이벤트가 발생시 어떤 소켓에서 처리해야 할지 알 수 없어

    소켓들을 모두 풀스캔해야하는 이슈가 있었고, 또한 가장 큰 fd 번호에 따라

    처리 속도 이슈가 있었다.

     

    int select(int nfds, fd_set *restrict readfds,
                fd_set *restrict writefds, fd_set *restrict exceptfds,
                struct timeval *restrict timeout);
    
    void FD_CLR(int fd, fd_set *set);
    int  FD_ISSET(int fd, fd_set *set);
    void FD_SET(int fd, fd_set *set);
    void FD_ZERO(fd_set *set)

    위 코드는 mac에서 제공하는 select 함수의 인터페이스인데 첫번째 인자로 받는 nfds는

    fd의 가장 큰 번호이다. 가장 큰 번호가 1000이라면 반복문을 1000번을 돌아야 한다

    따라서 select, poll 함수를 기반으로 한 서버는 실제로 디스크럽터 개수에 따라 성능차이가 심하다

     

    🤔 파일 디스크럽터란?

    흔히 유닉스 시스템에서 모든 것을 파일이라고 한다. 일반적인 정규파일부터 디렉토리, 소켓, 파이프, 블록 디바이스, 케릭터 디바이스 등 모든 객체들을 파일로 관리한다.
    **

    프로세스가 실행 중에 파일을 Open하면 커널은 해당 프로세스의 파일 디스크립터 숫자 중 사용하지 않는 가장 작은 값을 할당해준다. 그 다음 프로세스가 열려있는 파일에 시스템 콜을 이용해서 접근할 때, 파일 디스크립터(FD)값을 이용해서 파일을 지칭할 수 있다.

    **

    기본적으로 할당되는 파일디스크립터는 
    표준입력(Standard Input)  → 0
    표준 출력(Standard Output) → 1 
    표준에러(Standard Error) → 2

     

    리눅스 epoll 등장 (윈도우에서는 iocp)

    21세기에는 select과 poll을 보완하기 위해 새로운 epoll이 제안되었다.

    이벤트가 발생한 시점에 이벤트가 발생한 소켓과 종류를 구분할 수 있기 때문에 디스크럽터가 늘어나도 성능차이가 없다.

    그렇다고 항상 epoll을 사용하는것은 아니다.

    epoll은 성능적으로 우수하지만 제어하는데 많은 코드와 비용이 필요하기 때문에,

    제어하는데 많은 코드와 비용 ⇒ 멀티쓰레드 처리 하는데

    적은 클라이언트를 처리할때는 poll이 유용할 수 있다

    두 모델의 동작 방식이 다름을 이해하는게 중요하다.

    CPU 자원이 넉넉한 지금 이런 것까지 고민해야 하나 싶지만

    생산성이라는 관점에선 쉬운길을 두고 어려운길을 택하는건 어리석다고 본다.

    요약

    ⇒ 디스크럽터가 늘어나도 성능차이가 없다.

    ⇒ epoll은 성능적으로 우수하지만 제어하는데 많은 코드와 비용이 필요

    [멘토님 피드백] ⇒ 왜?? 일단은 나는 리눅스 시스템을 자세히 모르니까 !성능은 좋은데 제어하기가 어렵다! 이렇게 알고있자!

     

     

    libuv 라이브러리를 사용하는 NodeJS도 같은 맥락에서 등장했다.

    nodejs는 C++  libuv 라이브러리를 기반으로 한다. libuv는 epoll과 icop 모델을 지원한다.

    libuv가 싱글 스레드위에서 동작하기 때문에 멀티 스레드를 사용할 수 없지만, 비동기 방식을 기본적으로 사용하기 때문에 쉽게 C10K 문제를 해결할 수 있다.

     

     

    🤔 생각 해보기 1.

    그렇다면 NodeJs는 왜 메인로직돌릴때나, 이벤트루프를 싱글쓰레드로 만든걸까? 멀티쓰레드 로 구현되어있으면 더 효율적이지 않을까?

    => 공유 자원에 대한 동시성 문제나 데드락 같은 문제를 해결하는게 오히려 오버헤드라서 아닐까?

    멀티쓰레드 프로그래밍의 경우 쓰레드마다 가지고있는 스텍 영역 외에 코드, 데이터, 힙 영역의 변수를 공유하며 통신을 하는데, 여러 쓰레드가 사용하는 공유 자원을 컨트롤 하기엔 동시에 많은 요청을 처리하기 힘들것 같다.

     

    ChatGPT
    Node.js는 이벤트 중심의 논블로킹 I/O 플랫폼으로 설계되었기 때문에 단일 스레드를 사용합니다. 즉, Node.js는 응답 대기 시간이 상당할 수 있는 데이터베이스, 네트워크 연결 및 파일 시스템에 대한 읽기 및 쓰기와 같은 I/O 바인딩 작업을 처리하는 데 최적화되어 있습니다.

    단일 스레드를 사용하면 Node.js가 이벤트 루프를 사용하여 I/O 작업을 효율적으로 관리할 수 있습니다. 이벤트 루프는 Node.js가 각 작업에 대해 새 스레드를 만들지 않고 여러 I/O 작업을 동시에 처리할 수 있도록 하는 메커니즘입니다.

    기존의 멀티스레드 환경에서는 스레드를 생성하고 관리하는 데 비용이 많이 들 수 있으며 스레드 간 전환에도 시간이 많이 소요될 수 있습니다. 반면에 Node.js의 이벤트 루프는 실행의 주요 스레드를 차단하지 않고 비동기적으로 I/O 작업을 처리할 수 있게 한다. 이러한 접근 방식은 특히 많은 동시 연결을 처리하는 응용프로그램에서 더 나은 성능과 확장성으로 이어질 수 있습니다.

    Node.js는 메인 이벤트 루프에 단일 스레드를 사용하지만 하위 프로세스를 생성하거나 CPU 바인딩 작업에 Worker 스레드를 사용하여 멀티코어 시스템을 활용할 수 있습니다. 이를 통해 Node.js는 I/O 작업을 처리하기 위한 단일 스레드 이벤트 루프를 유지하면서 여러 프로세서에 걸쳐 수평으로 확장할 수 있습니다.

     

    🤔 생각 해보기 2.

    현재 운영환경에서도 서버 한개가 동시에 1만개의 클라이언트를 꼭 처리 할수 있어야 하는게 중요한 이슈일까?

    => 20세기에는 유효한 문제였을 수 있지만 21세기에는 유효하지 않을 수 있다.

    그 이유로는 지금은 이전과 달리 컴퓨팅 리소스가 넉넉하다 또한 성능보단 생산성에 치중되어있다,

    그리고 가장 큰 이유는 20세기엔 서버 한대로 최대의 효율을 끌어냈다면 지금은 서버 한대로 서비스를 운영하지 않는다.

    클라이언트가 늘어나게 되면 그에 따라 서버의 수도 늘리며 운영을 하게 된다.

    게다가 서비스 기능에 대한 복잡도가 높아지면서 데이터 베이스 구조도 점점 복잡해지며 외부 API사용 등

    기능 로직을 처리하는 I/O시간이 절대적으로 더 커지게 되었다.

    쉽게 말해 네트워크 I/O 처리 방식을 2초에서 1초로 단축시킨다 해도 기능 로직이 5초라면 큰 의미가 없다는 점이다.

    따라서 내 결론은 내 서버(하드웨어, OS)가 얼마나 많은 요청을 받을 수 있는지 보다는

    내 서비스(내가 짠 로직, 코드)가 얼마나 많은 요청을 받아낼 수 있는지, 그리고 그 양을 받아내기 위해 어떤것이 필요한지 아는 것이다.

     

    댓글

GitHub: https://github.com/Yesung-Han