서비스를 런칭하고 운영을 시작한 뒤, 가장 당황스러운 순간이 있다.
혼자 개발하고, 혼자 테스트할 때는 분명히 잘 됐다.
기능도 정상적으로 작동했고, 속도도 괜찮았다.
그래서 자신 있게 배포했는데.
배포 이후 실제 사용자가 몇 명씩 동시에 접속하기 시작하자 갑자기 응답이 느려지기 시작한다.
그러더니 급기야 다른 사람들의 요청까지 죄다 멈춰버린다.
서버 로그를 보면 에러도 없다. 크래시도 없다. 그냥 모든 요청이 대기 상태에 걸려 있다.
이 상황, 생각보다 자주 일어난다.
특히 AI 도움을 받아 빠르게 코딩하는 바이브코딩 방식으로 Node.js 서버를 만든 경우에.
1. Node.js는 어떻게 작동하는가
Node.js를 이해하려면 먼저 한 가지 사실을 알아야 한다.
Node.js는 싱글 스레드다.
스레드(Thread)란 쉽게 말해 일을 처리하는 일꾼이다.
싱글 스레드라는 건, 이 일꾼이 딱 한 명이라는 뜻이다.
카페에 비유하자면 이렇다.

바리스타가 한 명인 카페를 생각해보자.
손님이 1명, 2명일 때는 문제없다. 주문받고, 커피 내리고, 완성되면 건네주는 흐름이 자연스럽게 돌아간다.
그런데 이 바리스타가 한 가지 특이한 방식으로 일한다.
주문을 받은 뒤 커피를 직접 내리는 대신, "머신이 커피를 다 내리면 나한테 알려줘" 라고 하고 그 사이 다른 손님의 주문을 받는다.
이게 바로 비동기(Async) 처리 방식이다.
Node.js의 이벤트 루프(Event Loop) 는 이 바리스타가 "지금 뭘 처리해야 하는지"를 계속 확인하면서 돌아가는 구조다.
DB 조회, 파일 읽기, API 호출 같은 작업들은 "머신에 맡기고 다음 손님을 받는" 방식으로 처리된다.
그래서 사실 Node.js는 적은 자원으로도 꽤 많은 요청을 동시에 처리할 수 있다.
여기까지만 보면 꽤 훌륭한 구조다.
2. 그런데 무엇이 문제인가

문제는 이 바리스타가 직접 손으로 커피를 갈기 시작할 때 발생한다.
머신에 맡기지 않고, 직접 원두를 손으로 갈고, 직접 물을 끓이고, 직접 필터를 세팅하는 작업을 하기 시작하면 어떻게 될까.
다른 손님들은 그 사이에 아무런 서비스도 받지 못한다.
주문을 받을 수도 없고, 이미 완성된 다른 손님의 커피를 건네줄 수도 없다.
바리스타 한 명이 오래 걸리는 작업에 묶여 있는 동안, 카페 전체가 멈추는 것이다.
Node.js에서 이 상황은 실제로 이렇게 생긴다.
// 위험한 코드 예시
app.post('/resize-image', (req, res) => {
// 이미지를 직접 CPU로 처리하는 무거운 연산
const result = sharpResizeSync(req.body.imageBuffer); // 동기 처리!
res.json({ result });
});이 코드는 혼자 테스트하면 잘 동작한다.
이미지 하나 올리고, 처리되고, 응답 받는다. 아무 문제없어 보인다.
그런데 동시에 10명의 사용자가 이 API를 호출하면?
첫 번째 요청이 처리되는 동안 나머지 9명의 요청은 이벤트 루프가 풀릴 때까지 전부 대기한다.
심지어 전혀 다른 API를 호출한 다른 사용자들의 요청까지.
이게 바로 Node.js에서 이벤트 루프를 막는(Blocking) 작업이 일으키는 사고다.
특히 위험한 작업 유형
- 이미지 리사이징, 동영상 인코딩 같은 CPU 집약적 연산
- 대용량 파일을 동기(sync) 방식으로 읽고 쓰는 작업
- JSON 수천 건을 루프로 돌며 무거운 계산을 하는 작업
- 암호화/복호화를 대용량 데이터에 직접 처리하는 작업
- AI 모델 추론(inference)을 서버 프로세스 내에서 직접 실행하는 작업
이런 작업들은 기능 자체는 정상 동작한다.
그래서 테스트에서 절대 안 잡힌다.
문제는 동시에 사용자가 몰리는 운영 환경에서만 터진다.
3. 바이브코딩이 특히 위험한 이유
요즘은 Claude나 GPT 같은 AI에게 "이미지 업로드하면 자동으로 리사이징해줘"라고 요청하면,
그럴싸하게 작동하는 코드를 순식간에 뽑아준다.
AI가 만들어준 코드는 기능적으로는 완벽하다.
테스트해보면 이미지도 잘 바뀌고, 응답도 잘 온다.
여기서 문제가 생긴다.
바이브코딩으로 만들어진 코드는 기능을 검증하기는 쉽지만,
동시성과 부하 환경을 검증하기는 매우 어렵다.
혼자 개발하고 혼자 테스트하는 환경에서는 동시 요청이 발생하지 않는다.
그래서 이벤트 루프를 막는 코드가 있어도, 그게 문제인 줄 모르고 넘어가게 된다.
거기에 AI는 "이 코드가 동시 요청 상황에서 이벤트 루프를 블록할 수 있습니다"라는 경고를 친절하게 달아주지 않는다.
물론, 잘 물어보면 알려주기도 한다.
하지만 그 질문을 하려면 이벤트 루프 개념을 이미 알고 있어야 한다.
이게 바이브코딩의 진짜 함정이다.
모르는 개념은 물어볼 수조차 없다.
그래서 기능은 완성됐지만, 아무도 모르는 시한폭탄이 운영 서버에 올라가게 된다.
4. 그래서 어떻게 대처해야 하는가
해결 방향은 크게 세 가지다.

4-1. Worker Threads 활용
Node.js는 worker_threads 모듈을 통해 무거운 작업을 별도 스레드에서 처리할 수 있다.
메인 이벤트 루프와 분리된 공간에서 작업이 실행되므로, 다른 요청들이 영향을 받지 않는다.
쉽게 말하면 카페에서 원두 갈기 전담 스태프를 한 명 더 두는 것이다.
바리스타는 여전히 주문을 받고 서빙을 하고, 무거운 작업은 전담 스태프가 따로 처리한다.
4-2. 별도 마이크로서비스로 분리
이미지 처리나 AI 추론처럼 무거운 작업은
Node.js 서버 밖으로 완전히 꺼내서 별도 서비스로 만드는 방법도 있다.
예를 들면 이런 구조다.
[Node.js API 서버]
↓ 요청 전달
[Python 이미지 처리 서비스]
↓ 처리 완료 후 응답
[Node.js API 서버] → 사용자에게 결과 전달
Node.js는 요청을 받아 전달하고 결과를 돌려주는 역할만 하고,
실제 무거운 연산은 Python이나 다른 언어로 된 서비스가 처리하게 한다.
이렇게 되면 이벤트 루프가 막힐 일이 없다.
4-3. 비동기 큐(Queue) 처리
즉각적인 응답이 필요하지 않은 작업이라면,
요청을 큐에 넣고 백그라운드에서 순차적으로 처리하는 방식도 효과적이다.
사용자는 "요청이 접수됐습니다"라는 응답을 즉시 받고,
실제 처리는 별도의 워커가 큐에서 꺼내 처리한다.
이미지 처리, 대용량 리포트 생성, 대량 메일 발송 같은 작업이 이 방식에 적합하다.
5. IT기획자와 PM이 알아야 할 체크포인트
개발자가 아닌 IT기획자나 PM 입장에서 이 글을 읽는다면,
코드를 직접 짤 필요는 없다.
하지만 이런 질문은 할 수 있어야 한다.
"이 기능에서 무거운 연산이 발생하는 경우, 동시 요청 상황은 어떻게 처리하고 있나요?"
"AI가 짜준 코드 그대로 올라가는 건가요, 아니면 부하 테스트를 거쳤나요?"
"이미지 처리나 파일 변환 같은 작업이 Node.js 이벤트 루프를 막을 가능성은 검토됐나요?"
이런 질문을 할 수 있는 것 자체가
개발팀 입장에서는 "이 기획자는 구조를 이해하고 있다"는 신뢰로 이어진다.
반대로, 이걸 모르는 채 "그냥 빠르게 만들어요"만 반복하면
운영 이후에 "왜 서버가 느려지나요?", "왜 요청이 몰리면 터지나요?" 같은 질문을 사후에 하게 된다.
사전에 한 번 질문하는 것과, 사후에 장애를 수습하는 것 사이에는
비용, 시간, 신뢰 모든 면에서 비교할 수 없는 차이가 있다.
6. 마무리
Node.js 이벤트 루프는 알고 나면 단순한 개념이다.
싱글 스레드 기반이라, 오래 걸리는 작업이 루프를 점유하면 다른 모든 요청이 멈춘다.
그런데 이 단순한 개념을 모르는 채로 바이브코딩을 하면,
기능은 완성되었지만 운영 환경에서만 폭발하는 코드가 만들어질 수 있다.
AI는 기능 구현에는 탁월하다.
하지만 AI가 짜준 코드가 동시 사용자 환경에서도 안전한지는
여전히 사람이 검토해야 할 영역이다.
그 검토를 가능하게 하는 게 바로 이런 개념적 이해다.
바이브코딩 시대일수록, 코드를 이해하는 사람과 그렇지 않은 사람의 차이는
기능이 완성되었느냐가 아니라
운영 환경에서 살아남느냐로 갈린다.
그리고 그 차이를 만드는 건, 거창한 코딩 실력이 아니라
이벤트 루프 같은 기초 개념 하나를 제대로 이해하는 것에서 시작된다.
