Node.js 시니어 백엔드 면접 질문

Milad Bonakdar
작성자
이벤트 루프, 스트림, API 보안, 시스템 설계, 확장, 성능, 테스트, 프로덕션 운영 책임을 다루는 30개 실전 질문으로 Node.js 시니어 백엔드 면접을 준비하세요.
Node.js 시니어 백엔드 면접 질문
Node.js 시니어 면접은 문법보다 더 깊은 내용을 봅니다. 이벤트 루프, 백프레셔, API 경계, 분산 시스템, 관측 가능성, 보안, 프로덕션 트레이드오프를 어떻게 설명하는지 질문받을 수 있습니다. 좋은 답변은 Node.js 내부 동작을 실제 서비스에서 내린 결정과 연결합니다.
이 30개 질문으로 시니어 수준의 간결한 설명을 연습하세요. 각 주제마다 직접 겪은 예시 하나를 준비하세요. 확장 결정, 성능 문제, 안정성 사고, 설계 트레이드오프처럼 과장 없이 설명할 수 있는 사례가 좋습니다.
고급 Node.js 개념 (10가지 질문)
1. Node.js 이벤트 루프를 자세히 설명하세요. 서로 다른 단계는 무엇입니까?
답변: 이벤트 루프는 JavaScript가 기본적으로 하나의 메인 스레드에서 실행되는 동안 Node.js가 논블로킹 I/O를 처리할 수 있게 해줍니다. 대기 시간이 있는 작업은 운영체제나 libuv가 처리하고, 완료된 콜백은 JavaScript 실행 큐에 들어갑니다.
- 타이머:
setTimeout()및setInterval()콜백을 실행합니다. 최신 Node.js에서는 poll 단계의 작업량이 타이머 실행 시점에 영향을 줄 수 있습니다. - 보류 중인 콜백: 다음 루프 반복으로 미뤄진 일부 저수준 I/O 콜백을 실행합니다.
- 유휴, 준비: libuv 내부 단계입니다.
- 폴: 새 I/O 이벤트를 가져오고 관련 콜백을 실행합니다. 큐가 비어 있으면 Node.js는 I/O를 기다리거나
setImmediate()콜백이 있을 때 다음 단계로 이동합니다. - 확인:
setImmediate()콜백을 실행합니다. - 닫기 콜백:
socket.on('close', ...)같은 close 핸들러를 실행합니다.
시니어 답변은 마이크로태스크 큐도 언급해야 합니다. process.nextTick()은 Promise 마이크로태스크보다 먼저 실행되고, 둘 다 이벤트 루프가 계속되기 전에 처리됩니다. 과도하게 사용하면 I/O가 굶주릴 수 있습니다.
희소성: 매우 흔함 난이도: 어려움
2. process.nextTick()과 setImmediate()의 차이점은 무엇입니까?
답변:
process.nextTick(): 이벤트 루프의 일부가 아닙니다. 현재 작업이 완료된 직후에 실행되지만 이벤트 루프가 계속되기 전에 실행됩니다.setImmediate()보다 우선순위가 높습니다. 과도하게 사용하면 이벤트 루프를 차단할 수 있습니다(고갈).setImmediate(): 이벤트 루프의 확인 단계의 일부입니다. 폴 단계 후에 실행됩니다.
희소성: 흔함 난이도: 중간
3. Node.js는 단일 스레드인 경우 어떻게 동시성을 처리합니까?
답변: Node.js는 이벤트 기반의 논블로킹 I/O 모델을 사용합니다.
- 메인 스레드: JavaScript 코드(V8 엔진)를 실행합니다.
- Libuv: 이벤트 루프와 스레드 풀(기본 4개 스레드)을 제공하는 C 라이브러리입니다.
- 메커니즘: 비동기 작업(예: 파일 I/O 또는 네트워크 요청)이 시작되면 Node.js는 이를 Libuv에 오프로드합니다. Libuv는 스레드 풀(파일 I/O, DNS용) 또는 시스템 커널 비동기 메커니즘(네트워크용)을 사용합니다. 작업이 완료되면 콜백이 메인 스레드에서 실행될 수 있도록 이벤트 루프 큐로 푸시됩니다.
희소성: 흔함 난이도: 중간
4. Node.js의 스트림과 그 유형을 설명하세요.
답변: 스트림은 연속적인 청크 단위로 소스에서 데이터를 읽거나 대상에 데이터를 쓸 수 있게 해주는 객체입니다. 전체 데이터를 메모리에 로드할 필요가 없으므로 메모리 효율적입니다.
- 유형:
- 읽기 가능: 데이터 읽기용 (예:
fs.createReadStream). - 쓰기 가능: 데이터 쓰기용 (예:
fs.createWriteStream). - 이중: 읽기 및 쓰기 가능 (예: TCP 소켓).
- 변환: 출력이 입력을 기반으로 계산되는 이중 스트림 (예:
zlib.createGzip).
- 읽기 가능: 데이터 읽기용 (예:
희소성: 흔함 난이도: 중간
5. 스트림의 백프레셔란 무엇이며 어떻게 처리합니까?
답변: 백프레셔는 readable 스트림이 writable 쪽이 소비할 수 있는 속도보다 빠르게 데이터를 만들 때 발생합니다. 이를 무시하면 버퍼가 커지고 GC 부담이 증가하며 프로세스가 메모리 부족에 빠질 수 있습니다.
stream.pipeline()또는node:stream/promises의pipeline사용: 스트림을 연결하고 오류 전파와 정리를 올바르게 처리합니다..write()존중:false가 반환되면 더 쓰기 전에drain을 기다립니다.- 신중한 튜닝:
highWaterMark는 특정 워크로드에 도움이 될 수 있지만, 측정 없이 올리면 압력을 메모리로 옮기는 것뿐입니다. - 업로드 처리: 전체 파일을 메모리에 버퍼링하지 말고 object storage나 처리 파이프라인으로 직접 스트리밍합니다.
희소성: 중간 난이도: 어려움
6. cluster 모듈은 어떻게 작동합니까?
답변:
Node.js는 단일 스레드이므로 단일 CPU 코어에서 실행됩니다. cluster 모듈을 사용하면 동일한 서버 포트를 공유하는 자식 프로세스(워커)를 만들 수 있습니다.
- 마스터 프로세스: 워커를 관리합니다.
- 워커 프로세스: 각각 애플리케이션 인스턴스를 실행합니다.
- 이점: 사용 가능한 모든 CPU 코어를 활용하여 처리량을 늘릴 수 있습니다.
희소성: 흔함 난이도: 중간
7. Worker Threads vs Cluster 모듈: 언제 무엇을 사용해야 합니까?
답변:
- Cluster: 별도의 프로세스를 만듭니다. 각각 자체 메모리 공간과 V8 인스턴스가 있습니다. HTTP 서버 (I/O 바운드)를 확장하는 데 가장 적합합니다.
- Worker Threads: 단일 프로세스 내에서 스레드를 만듭니다. 메모리 (
SharedArrayBuffer를 통해)를 공유합니다. 메인 이벤트 루프를 차단하지 않도록 CPU 집약적인 작업 (예: 이미지 처리, 암호화)에 가장 적합합니다.
희소성: 중간 난이도: 어려움
8. 잡히지 않은 예외와 처리되지 않은 프로미스 거부를 어떻게 처리합니까?
답변:
- 잡히지 않은 예외:
process.on('uncaughtException', cb)를 수신합니다. 애플리케이션 상태가 손상되었을 수 있으므로 오류를 기록하고 프로세스 (PM2와 같은 프로세스 관리자 사용)를 다시 시작하는 것이 가장 좋습니다. - 처리되지 않은 거부:
process.on('unhandledRejection', cb)를 수신합니다. - 모범 사례: 항상
try/catch블록과 프로미스에.catch()를 사용합니다.
희소성: 흔함 난이도: 쉬움
9. package-lock.json의 역할은 무엇입니까?
답변: 중간 종속성 업데이트에 관계없이 후속 설치가 동일한 트리를 생성할 수 있도록 생성된 정확한 트리를 설명합니다. 프로젝트가 모든 머신(CI/CD, 다른 개발자)에서 정확히 동일하게 작동하도록 보장합니다.
희소성: 흔함 난이도: 쉬움
10. Express.js의 미들웨어 개념을 설명하세요.
답변:
미들웨어 함수는 애플리케이션의 요청-응답 주기에서 요청 객체(req), 응답 객체(res) 및 다음 미들웨어 함수(next)에 액세스할 수 있는 함수입니다.
- 작업: 코드 실행, req/res 객체 수정, 요청-응답 주기 종료, 다음 미들웨어 호출.
- 순서: 정의된 순서대로 순차적으로 실행됩니다.
희소성: 흔함 난이도: 쉬움
시스템 설계 및 아키텍처 (10가지 질문)
11. 실시간 채팅 애플리케이션을 어떻게 설계하시겠습니까?
답변:
- 프로토콜: 전이중 통신을 위한 WebSocket (
socket.io또는ws사용). - 백엔드: Node.js는 많은 동시 연결을 처리하는 이벤트 기반 특성으로 인해 이상적입니다.
- 확장:
- Redis Pub/Sub: 여러 서버 인스턴스가 있는 경우 서버 A에 연결된 사용자가 서버 B의 사용자에게 메시지를 보내야 합니다. Redis Pub/Sub은 서버 간에 메시지를 브로드캐스트하는 메시지 브로커 역할을 합니다.
- 데이터베이스:
- 메시지: 높은 쓰기 처리량을 위한 NoSQL (MongoDB/Cassandra).
- 사용자: 관계형 (PostgreSQL) 또는 NoSQL.
희소성: 매우 흔함 난이도: 어려움
12. Node.js의 마이크로서비스: 통신 패턴.
답변:
- 동기: HTTP/REST 또는 gRPC. 간단한 요청/응답에 적합합니다.
- 비동기: 메시지 큐 (RabbitMQ, Kafka, SQS). 서비스 분리 및 로드 스파이크 처리에 적합합니다.
- 이벤트 기반: 서비스는 이벤트를 내보내고 다른 서비스는 수신합니다.
희소성: 흔함 난이도: 중간
13. 분산 트랜잭션 (사가 패턴)을 어떻게 처리합니까?
답변: 마이크로서비스에서 트랜잭션은 여러 서비스에 걸쳐 있을 수 있습니다. ACID를 보장하기는 어렵습니다.
- 사가 패턴: 로컬 트랜잭션 시퀀스입니다. 각 로컬 트랜잭션은 데이터베이스를 업데이트하고 이벤트 또는 메시지를 게시하여 사가의 다음 로컬 트랜잭션을 트리거합니다.
- 보상: 로컬 트랜잭션이 실패하면 사가는 이전 로컬 트랜잭션에서 수행한 변경 사항을 취소하는 일련의 보상 트랜잭션을 실행합니다.
희소성: 중간 난이도: 어려움
14. 서킷 브레이커 패턴을 설명하세요.
답변: 애플리케이션이 실패할 가능성이 높은 작업(예: 다운된 마이크로서비스 호출)을 반복적으로 실행하려고 시도하는 것을 방지합니다.
- 상태:
- 닫힘: 요청이 통과합니다.
- 열림: 서비스 호출 없이 요청이 즉시 실패합니다(빠른 실패).
- 반쯤 열림: 제한된 수의 요청을 허용하여 서비스가 복구되었는지 확인합니다.
희소성: 중간 난이도: 중간
15. Node.js API를 어떻게 보호합니까?
답변: 좋은 답변은 패키지 목록이 아니라 위협 모델링에서 시작합니다. Node.js API라면 다음을 다룹니다.
- 인증과 인가: 신원을 검증하고 객체 수준 권한을 강제하며 클라이언트가 보낸 사용자 또는 tenant ID를 신뢰하지 않습니다.
- 입력 검증: 경계에서 타입, 형식, 범위, content type, 요청 크기를 Zod나 Joi 같은 도구로 검증합니다.
- 전송과 헤더: HTTPS, 필요한 경우 secure cookie, CORS allowlist, Helmet 또는 플랫폼 헤더 제어를 사용합니다.
- 남용 방지: rate limit, timeout, body size limit, reverse proxy로 느리거나 대량의 클라이언트를 제어합니다.
- 의존성과 시크릿: 의존성을 lock하고 취약 패키지를 모니터링하며 시크릿을 코드에 두지 않고 유출 시 회전합니다.
- 관측 가능성: 민감한 데이터를 노출하지 않으면서 보안 관련 실패를 로그로 남깁니다.
희소성: 흔함 난이도: 중간
16. 서버리스란 무엇이며 Node.js와 어떻게 어울립니까?
답변: 서버리스 (예: AWS Lambda)를 사용하면 서버를 프로비저닝하거나 관리하지 않고도 코드를 실행할 수 있습니다.
- Node.js 적합성: Node.js는 빠른 시작 시간 (콜드 스타트)과 가벼운 특성으로 인해 서버리스에 탁월합니다.
- 사용 사례: API 엔드포인트, 이벤트 처리 (S3 업로드), 예약된 작업.
희소성: 중간 난이도: 중간
17. GraphQL vs REST를 설명하세요. 언제 GraphQL을 사용해야 합니까?
답변:
- REST: 여러 엔드포인트, 데이터의 과도한 페칭 또는 과소 페칭.
- GraphQL: 단일 엔드포인트, 클라이언트가 필요한 것만 요청합니다.
- GraphQL 사용: 복잡한 데이터 요구 사항, 서로 다른 데이터 모양이 필요한 여러 클라이언트 (웹, 모바일)가 있거나 네트워크 왕복을 줄여야 하는 경우.
희소성: 흔함 난이도: 중간
18. Node.js에서 캐싱을 어떻게 구현합니까?
답변:
- 인 메모리:
node-cache(단일 인스턴스에 적합하지만 다시 시작하면 데이터가 손실되고 공유되지 않음). - 분산: Redis (업계 표준).
- 전략: Cache-Aside, Write-Through.
- HTTP 캐싱: ETag, Cache-Control 헤더를 사용합니다.
희소성: 흔함 난이도: 중간
19. 데이터베이스 연결 풀링.
답변: 모든 요청에 대해 새 데이터베이스 연결을 여는 것은 비용이 많이 듭니다.
- 풀링: 재사용할 수 있는 데이터베이스 연결 캐시를 유지 관리합니다.
- Node.js:
pg(PostgreSQL) 또는mongoose와 같은 라이브러리는 풀링을 자동으로 처리합니다. 워크로드 및 DB 제한에 따라 풀 크기를 구성해야 합니다.
희소성: 중간 난이도: 중간
20. Node.js에서 파일 업로드를 어떻게 처리합니까?
답변:
- Multipart/form-data: 파일 업로드의 표준 인코딩입니다.
- 라이브러리:
multer(Express용 미들웨어),formidable,busboy. - 저장: 서버 파일 시스템에 파일을 저장하지 마십시오 (상태 비저장). AWS S3와 같은 클라우드 스토리지에 업로드합니다. 메모리에 로드하지 않도록 파일을 S3로 직접 스트리밍합니다.
희소성: 흔함 난이도: 중간
성능 및 테스트 (10가지 질문)
21. Node.js에서 메모리 누수를 어떻게 디버깅합니까?
답변:
- 증상: 시간이 지남에 따라 메모리 사용량이 증가하고 (RSS) 결국 충돌합니다.
- 도구:
- Node.js 검사기:
--inspect플래그, Chrome DevTools와 연결합니다. - 힙 스냅샷: 스냅샷을 찍어 비교하여 가비지 수집되지 않는 객체를 찾습니다.
process.memoryUsage(): 프로그래밍 방식으로 모니터링합니다.
- Node.js 검사기:
희소성: 흔함 난이도: 어려움
22. Node.js 애플리케이션 프로파일링.
답변: 프로파일링은 CPU 병목 현상을 식별하는 데 도움이 됩니다.
- 내장 프로파일러:
node --prof app.js. 로그 파일을 생성합니다.node --prof-process isolate-0x...log로 처리합니다. - Clinic.js: 성능 문제를 진단하는 도구 모음 (
clinic doctor,clinic flame,clinic bubbleprof).
희소성: 중간 난이도: 어려움
23. "이벤트 루프를 차단하지 마십시오" 규칙을 설명하세요.
답변: 스레드가 하나만 있으므로 장기 실행 동기 작업(예: 과도한 계산, 동기 파일 읽기, 복잡한 정규식)을 실행하면 이벤트 루프가 중지됩니다. 다른 요청은 처리할 수 없습니다.
- 해결 방법: 계산을 분할하거나(setImmediate), 워커 스레드를 사용하거나 마이크로서비스로 오프로드합니다.
희소성: 매우 흔함 난이도: 쉬움
24. Node.js의 단위 테스트 vs 통합 테스트.
답변:
- 단위 테스트: 개별 함수/모듈을 격리하여 테스트합니다. 종속성을 모의합니다. (도구: Jest, Mocha, Chai).
- 통합 테스트: 모듈이 함께 작동하는 방식을 테스트합니다 (예: API 엔드포인트 + 데이터베이스). (도구: Supertest).
희소성: 흔함 난이도: 쉬움
25. TDD (테스트 주도 개발)란 무엇입니까?
답변: 코드를 작성하기 전에 테스트를 작성하는 개발 프로세스입니다.
- 실패하는 테스트를 작성합니다 (Red).
- 테스트를 통과하는 최소한의 코드를 작성합니다 (Green).
- 코드를 리팩터링합니다 (Refactor).
희소성: 중간 난이도: 중간
26. 프로덕션 Node.js 앱에서 로깅을 어떻게 처리합니까?
답변: 프로덕션 로깅은 구조화되어야 하고 검색 가능해야 하며 관측 가능성 도구에 안전하게 보낼 수 있어야 합니다.
- 로거 사용: 흩어진
console.log대신 Pino, Winston 또는 플랫폼 로거를 사용합니다. - 구조화: request ID, 안전한 사용자/tenant 식별자, route, status, latency, error metadata를 JSON으로 기록합니다.
- 레벨: error, warn, info, debug를 일관되게 사용합니다.
- 마스킹: token, password, 전체 결제 정보, 사적인 사용자 콘텐츠를 기록하지 않습니다.
- 상관관계: logs, metrics, traces를 연결해 서비스 간 프로덕션 장애를 디버깅합니다.
희소성: 흔함 난이도: 쉬움
27. 시맨틱 버전 관리 (SemVer)를 설명하세요.
답변:
형식: MAJOR.MINOR.PATCH (예: 1.2.3).
- MAJOR: 호환되지 않는 API 변경 사항.
- MINOR: 이전 버전과 호환되는 기능.
- PATCH: 이전 버전과 호환되는 버그 수정.
^vs~:^1.2.3은<2.0.0으로 업데이트됩니다.~1.2.3은<1.3.0으로 업데이트됩니다.
희소성: 흔함 난이도: 쉬움
28. 환경 변수란 무엇이며 어떻게 관리합니까?
답변:
- 목적: DB URL, API 키와 같은 개발, 스테이징, 프로덕션과 같은 환경에 따라 달라지는 구성입니다.
- 사용법:
process.env.VARIABLE_NAME. - 관리: 로컬 개발을 위한
.env파일 (dotenv패키지 사용). 프로덕션에서는 OS 또는 컨테이너/플랫폼 설정에서 설정합니다.
희소성: 흔함 난이도: 쉬움
29. Node.js 애플리케이션을 어떻게 배포합니까?
답변:
- 프로세스 관리자: PM2 (앱을 계속 실행하고, 다시 시작을 처리하고, 로그를 기록합니다).
- 역방향 프록시: Nginx (SSL, 정적 파일, 로드 밸런싱을 처리합니다) -> Node.js 앱.
- 컨테이너화: Docker (표준).
- 오케스트레이션: Kubernetes.
- CI/CD: GitHub Actions, Jenkins.
희소성: 흔함 난이도: 중간
30. 이벤트 이미터란 무엇입니까?
답변:
events 모듈은 Node.js 이벤트 기반 아키텍처의 핵심입니다.
- 사용법:
- 많은 핵심 모듈이 확장됩니다:
fs.ReadStream,net.Server,http.Server.
희소성: 흔함 난이도: 쉬움


