1강 질문
24
작성한 질문수 2
Node.js가 I/O집약적 서비스에서 탁월한 성능을 발휘하는 원리가 궁금합니다.
답변 2
1
안녕하세요 madwolves98님. 질문 주셔서 감사합니다!
우선 Apache나 전형적인 Spring Boot 같은 전통적인 서버 모델은 사용자의 요청이 들어올 때마다 일종의 작업자인 Thread를 한 명씩 전담시키는 방식을 사용합니다. 이런 구조에서는 데이터베이스에서 정보를 찾아오거나 외부 결제 시스템을 호출하고, 또 파일을 읽고 쓰는 등의 데이터 입출력(I/O) 작업이 발생할 때 문제가 생깁니다. 컴퓨터의 두뇌인 CPU는 할 일이 없어서 놀고 있는데도, 해당 작업자는 데이터베이스나 네트워크의 응답이 올 때까지 아무것도 하지 않고 가만히 멈춰 서서 기다리게 됩니다. 우리는 이렇게 작업자가 꼼짝 못 하고 대기하는 상태를 Blocking 상태라고 부릅니다.
반면 Node.js는 메인 작업자를 딱 한 명만 두고 이 작업자가 절대 대기하지 않고 계속 움직이는 Non-blocking 방식을 사용합니다. 여기서 Event Loop라는 개념이 등장하는데, 이는 들어오는 요청을 끊임없이 접수하는 단 한 명의 멈추지 않는 점원과 같습니다. 만약 사용자의 요청에 시간이 오래 걸리는 데이터베이스 조회 같은 I/O 작업이 포함되어 있다면, Event Loop는 이 작업을 운영체제의 백그라운드 시스템이나 자체적인 보조 작업 공간(엄밀히 말하면 libuv가 관리하는 내부 스레드 풀)에 던져버리고 즉시 다음 손님의 요청을 받으러 이동합니다. 이후 백그라운드 시스템이 일을 끝내서 완료 신호를 보내면, Event Loop는 다음 손님의 주문을 받는 틈틈이 그 결과값을 가지고 그다음으로 해야 할 후속 조치, 즉 Callback 처리를 진행하게 됩니다.
이를 현실 세계의 식당에 비유해 보면 이해가 쉽습니다. 전통적 방식은 손님이 메뉴를 고르고 음식을 먹고 나갈 때까지 점원 한 명이 그 손님 테이블 옆에 서서 계속 대기하는 구조입니다. 손님이 많아지면 점원을 계속 고용해야 하는데, 컴퓨터 세상에서는 점원이 많아질수록 이 점원들을 일일이 관리하고 교대시키는 데 드는 Context Switching 비용이 기하급수적으로 증가합니다. 결국 서버의 메모리가 버티지 못하고 고갈되어 다운되어 버리죠. 반면에 Node.js 방식은 메인 점원 한 명이 주문만 계속 받아서 주방에 넘기고, 주방에서 음식을 만들어 내오면 점원은 새로운 주문을 받는 틈틈이 서빙만 하는 구조입니다. 그렇기 때문에 점원 한 명이 수백, 수천 명의 손님을 거뜬히 커버할 수 있는 것입니다.
실제 기업들이 서비스를 개발하고 운영하는 현장에 빗대어 시장의 도구들을 냉정하게 비교해 보면 이 특징들이 더욱 선명해집니다. Node.js를 기반으로 하는 Express나 NestJS는 앞서 말씀드린 주문만 빠르게 쳐내는 구조 덕분에 수만 명의 동시 접속을 가볍게 처리합니다. 배달 앱에서 수많은 라이더와 고객의 위치 정보를 1초 단위로 끊임없이 중계하거나, 카카오톡처럼 수만 명이 동시에 메시지를 주고받는 실시간 채팅 서버를 만들 때 아주 강력합니다. 다만, 동영상 인코딩이나 복잡한 암호화 알고리즘 계산처럼 메인 점원 스스로 머리를 써서 오랫동안 풀어야 하는 무거운 수학적 계산 작업이 들어오면, 단 한 명뿐인 점원이 그 계산을 하느라 서빙 자체를 멈춰버려 전체 서버가 먹통이 될 수 있다는 약점이 있습니다.
이와 달리 Java 진영의 Spring Boot는 여러 명의 점원을 체계적으로 관리하는 방식을 바탕으로 거대한 기업용 시스템에 맞는 강력한 뼈대와 엄격한 규칙을 제공합니다. 토스나 은행 앱처럼 1원의 오차도 허용되지 않는 복잡한 송금 결제 시스템을 개발하거나, 수백 개의 데이터베이스 테이블을 복잡하게 엮어서 정밀하게 계산하고 통제해야 하는 대기업의 사내 인사 및 재무 관리 시스템을 만들 때 매우 적합합니다. (물론 최근에는 WebFlux 같은 논블로킹 모델이나 Java 21의 가상 스레드 도입으로 이러한 전통적인 구조의 한계를 극복해 나가고 있긴 합니다만,) 여전히 수많은 점원을 관리하고 교대하는 데 상대적으로 시스템적 비용이 많이 든다는 점은 분명합니다.
한편 Go 언어는 아주 가볍고 작은 작업자들을 엄청나게 많이 만들어내는 방식을 사용하여 압도적인 동시 처리 성능과 빠른 실행 속도를 자랑합니다. 이미 클라우드 인프라나 마이크로서비스 생태계에서는 주류로 자리 잡았지만, 아직 일반적인 웹 비즈니스 로직 개발에 있어서는 Java나 Node.js에 비해 실무 현장에서 이 언어를 능숙하게 다룰 줄 아는 백엔드 개발자를 채용하기 어렵다는 현실적인 제약이 있습니다.
여기까지 흐름을 따라오셨다면, 그렇다면 컴퓨터가 가장 잘 이해하는 빠르고 근본적인 언어인 C나 C++로 처음부터 모든 걸 만들면 제일 좋은 것 아닌가 하는 날카로운 의문이 생기실 수 있습니다. 실제로 Node.js를 돌아가게 하는 심장부인 V8 엔진과 내부 보조 작업 공간인 libuv도 모두 C와 C++로 만들어져 있습니다. 이론적으로 이 언어들을 사용해 앞서 칭찬했던 멈추지 않는 Non-blocking 구조를 개발자가 직접 완벽하게 만들어낸다면 Node.js보다 훨씬 빠르고 메모리도 적게 쓸 것입니다.
하지만 현실적으로 이를 바닥부터 직접 구현한다는 것은 불가능에 가깝습니다. Node.js의 기반이 되는 libuv 엔진은 전 세계 최고 수준의 천재적인 개발자들이 수많은 예외 상황과 엣지 케이스를 치열하게 고민하고 고려해서 만들었으며, 지금 이 순간에도 꾸준히 버전 업데이트를 진행하며 정교하게 다듬어가고 있는 거대한 시스템입니다. 개인이나 작은 회사의 개발자들이 이 사람들이 수십 년간 깎아온 수준의 안정성과 성능을 가진 엔진을 직접 만들어서 서비스를 런칭한다는 것은 사실상 불가능합니다.
게다가 현업에서 비즈니스 웹 서버를 C나 C++로 직접 짜지 않는 가장 큰 이유는 비즈니스의 기회비용과 개발 생산성 때문입니다. 현대 IT 기업은 아이디어를 빠르게 프로그램으로 구현해서 시장에 출시하고 고객의 반응을 살피는 것이 생명입니다. 개발팀이 C++로 석 달 동안 밤새워 메모리를 일일이 관리해가며 서버 응답 속도 100점짜리 서비스를 만드는 것보다, Node.js를 이용해 2주일 만에 속도 80점짜리 서비스를 빠르게 출시해서 먼저 시장을 선점하고 수익을 내는 것이 비즈니스 관점에서는 압도적인 승리입니다.
또한 C나 C++에서는 개발자의 아주 작은 실수로 사용이 끝난 메모리를 제때 비우지 않아 메모리 누수가 발생하거나 허락되지 않은 엉뚱한 공간을 건드리면, 멀쩡히 돌아가던 서버 전체가 치명적인 에러와 함께 강제로 꺼져버립니다. 반면 Node.js는 JavaScript라는 사람이 쓰기 편한 언어를 사용하므로, 안 쓰는 메모리 찌꺼기들을 시스템이 알아서 주기적으로 청소해 주는 Garbage Collector 기능이 탑재되어 있어 훨씬 안전하고 유연하게 돌아갑니다. 여기에 전 세계에서 가장 거대한 부품 창고인 npm 생태계가 더해져, 데이터베이스 연결이나 소셜 로그인 연동 등 당장 필요한 거의 모든 기능이 이미 만들어진 무료 오픈소스로 존재합니다. C++로 바닥부터 바퀴를 깎아야 하는 상황과는 확연히 다르죠. 즉, 기계가 이해하기 가장 좋은 언어와 인간이 비즈니스 문제를 가장 빠르게 풀기 좋은 언어 사이에서 완벽한 타협점을 찾은 결과가 바로 Node.js입니다.
결국 현업 개발 세계에서 무조건 이 기술이 정답이다라는 말은 결코 존재하지 않습니다. 모든 Framework와 언어는 저마다 하나를 얻으면 반드시 다른 하나를 잃는 Trade-off 관계를 가지고 있습니다.
물론 기술적으로 억지를 부리자면, Java Spring 환경에서도 설정을 조작해 싱글 스레드처럼 동작하게 만들 수 있고, 반대로 Node.js에서도 워커 스레드(Worker Thread)라는 기능을 사용해 멀티 스레딩을 흉내 낼 수는 있습니다. 하지만, 결국 비즈니스의 개발 생산성과 서버 운영의 효율성을 고려했을 때 각 프레임워크가 태생적으로 가장 잘하는 것을 하도록 두는 것이 맞습니다. 억지로 맞지 않는 옷을 입히느라 끙끙대는 것보다, 각자의 장점을 살려 적재적소에 배치하는 것이 현명하죠. Node.js는 압도적인 개발 생산성과 효율적인 네트워크 I/O 처리 능력을 얻은 대신 무거운 수학적 연산 능력을 양보했고, C와 C++은 극한의 컴퓨터 성능을 얻은 대신 사람의 개발 편의성과 시스템의 런타임 안전성을 양보한 것처럼 말입니다.
우리가 지향해야 하는 궁극적인 목적은 유행하는 특정 기술을 맹신하는 것이 아니라, 현재 접속하는 사용자의 규모, 개발팀의 실력, 비즈니스 목표 등 눈앞에 주어진 상황과 서버 인프라의 물리적 한계를 명확히 인식하고 그 상황에 가장 알맞은 도구를 냉정하게 판단하여 선택하는 시야를 갖추는 것입니다.
더 나아가 어떤 화려하고 새로운 Framework를 쓰더라도 그 밑바탕이 되는 동작 원리는 결국 같습니다. 운영체제가 어떻게 컴퓨터의 한정된 자원을 여러 프로그램에게 공평하게 빌려주는지인 System Call부터, 깊숙한 Kernel 수준에서 파일과 네트워크 데이터를 제어하는 방식, 그리고 메모리라는 임시 바구니인 Buffer에 데이터를 담아 끊임없는 물결인 Stream처럼 흘려보내고 받아내는 컴퓨터 과학의 근본 원리는 Java든 Python이든 C++이든 모두 완벽하게 동일합니다.
우리가 이 수업에서 Node.js라는 직관적인 도구를 선택한 이유도 결국 이 수면 아래에 숨겨진 컴퓨터 과학의 근본 원리를 파헤치기 위함입니다. 단순히 도구의 껍데기 같은 사용법을 외우는 것을 넘어 이 본질적인 뼈대를 이해하게 된다면, 훗날 Node.js가 아닌 완전히 새로운 도구가 시장을 지배하더라도 아주 손쉽고 빠르게 그 기술의 핵심을 장악하실 수 있을 것입니다.
감사합니다!
0
안녕하세요, 인프런 AI 인턴입니다. madwolves98님.
Node.js가 I/O 집약적 서비스에서 탁월한 성능을 발휘할 수 있는 이유는 그 독특한 아키텍처에 있습니다. Node.js는 싱글 스레드 기반의 이벤트 루프를 사용하여 비동기 I/O 처리에 매우 효율적입니다. 기본적으로 Node.js는 네트워크 요청, 파일 시스템 접근 등의 I/O 작업을 비동기로 처리하며, 이러한 작업을 대기 상태로 두지 않고 다른 요청을 빠르게 처리할 수 있습니다.
Node.js의 비동기 I/O 동작 원리:
이벤트 루프:
- Node.js는 메인 스레드에서 이벤트 루프를 돌려가며 작업을 처리합니다. 이 이벤트 루프는 가장 작은 단위의 이벤트(비동기 작업)을 순차적으로 처리하며, 작업이 완료되면 콜백을 실행합니다.
- Node.js는 메인 스레드에서 이벤트 루프를 돌려가며 작업을 처리합니다. 이 이벤트 루프는 가장 작은 단위의 이벤트(비동기 작업)을 순차적으로 처리하며, 작업이 완료되면 콜백을 실행합니다.
비차단 설계:
- 비차단(asynchronous) 방식으로 I/O 작업을 처리하는데, 이로 인해 한 요청의 처리 시간이 길어지는 동안에도 다른 요청을 계속해서 처리할 수 있습니다. 이는 특히 많은 클라이언트가 동시에 접속하는 환경에서 중요한 이점이 됩니다.
- 비차단(asynchronous) 방식으로 I/O 작업을 처리하는데, 이로 인해 한 요청의 처리 시간이 길어지는 동안에도 다른 요청을 계속해서 처리할 수 있습니다. 이는 특히 많은 클라이언트가 동시에 접속하는 환경에서 중요한 이점이 됩니다.
libuv와 운영체제:
libuv라는 라이브러리가 다양한 I/O 작업(파일 시스템, 네트워크 작업, 네트워크 요청 등)을 백그라운드의 스레드 풀로 위임하여 처리합니다. 결과가 준비되면 이벤트 루프는 이를 감지하고 콜백 함수를 실행합니다.
이와 같은 Node.js의 설계 덕분에, CPU 처리보다는 대기 시간이 많은 I/O 작업을 효율적으로 최적화할 수 있습니다. 데이터베이스 조회, 파일 읽기, 네트워크 전송 등의 작업에서 Node.js가 강력한 이유가 여기에 있습니다.
관련하여 이전 강의부터 이어지는 상세 답변을 통해 Node.js의 내부 구조와 비동기 처리 메커니즘을 깊이 있게 이해하실 수 있습니다. 아래 예제 링크를 참조합니다:
저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다. 현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏 추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.
0
방금 예제 링크로 들어가봤는데 Pages Not Found라고 뜹니다.
Node.js의 비동기 I/O 모델에 대한 이해
혹시 다음 강의 제작 예정된 것들이 있을까요?
0
4
0
Enable Linting 항목을 찾을수가 없습니다.
0
9
2
에러 질문드립니다
0
13
2
1번 질문
0
11
1
안녕하세요
0
19
1
강의 외 개념 및 시험 관련 질문
0
30
3
책에 있는 프롬프트 관련 질문입니다.
0
20
1
usecase 사용 기준
1
25
2
윈도우에서는동작줄이기가어디에있을까요??
0
16
0
구매후 바로 질문 하나만요..
0
38
1
형 이번에 낸 책이랑 강의 내용에 차이가 있어?
1
49
1
형 나 몰래 책내면 모를 줄 알고?
1
38
1
백오피스를 개발할 때 아키텍처 구성에 대한 질문
0
29
2
ai가 만든 강의인가요?
0
69
1
VSCode 설정 문의
0
42
2
PPT 코드 관련 질문
0
35
2
강의 중복 확인 요청
0
29
1
SP를 아직도 사용하나요?
0
33
2
캐시전략 - Write-behind
0
36
2
state 객체로 묶기
0
36
1
중복내용 제보?!
1
51
2
궁금한점이 여러개 생겼습니다.
1
51
1
3강 질문
0
60
2
2강 nodejs 3단계 설명 질문
0
65
1





