Node.js 리드·아키텍트 프레임워크 심층 면접
새 면접Q. Node.js의 Event Loop는 여러 단계(phases)로 구성되어 있습니다. timers, pending callbacks, idle/prepare, poll, check, close callbacks 각 단계의 역할과 실행 순서를 설명하고, process.nextTick()과 Promise.then()이 어느 시점에 실행되는지, 그리고 CPU 집약적 작업이 poll 단계에서 발생할 때 시스템 전체에 미치는 영향과 아키텍처 레벨의 해결 방안을 제시해주세요.
각 phase의 큐와 microtask queue의 우선순위, 그리고 blocking operation이 poll phase에 미치는 영향을 중심으로 생각해보세요.
Event Loop는 6개 phase로 구성되며 timers(setTimeout/setInterval), pending callbacks(시스템 콜백), poll(I/O 이벤트), check(setImmediate), close callbacks 순으로 실행됩니다. process.nextTick()과 Promise는 microtask queue에 들어가 각 phase 전환 시점마다 우선 실행되며, nextTick이 Promise보다 높은 우선순위를 가집니다. CPU 집약적 작업이 poll phase에서 발생하면 다른 I/O 이벤트 처리가 지연되어 전체 시스템 응답성이 저하됩니다. 해결 방안으로는 Worker Threads를 활용한 병렬 처리, 작업을 chunk 단위로 분할하여 setImmediate로 다음 tick에 위임, 또는 별도 마이크로서비스로 분리하는 아키텍처 전환을 고려할 수 있습니다. 특히 이미지 처리, 암호화, 대용량 데이터 파싱 등은 Worker Threads pool로 격리하는 것이 권장됩니다.
- • Event Loop의 6개 phase와 각각의 역할 이해
- • Microtask queue(nextTick, Promise)의 우선순위와 실행 시점
- • CPU 집약 작업의 poll phase blocking 영향
- • Worker Threads, chunking, 마이크로서비스 분리 등 해결 전략
실시간 채팅 서버에서 이미지 리사이징 작업이 메시지 전송을 지연시키는 문제를 Worker Threads로 분리하여 해결하는 상황
Worker Threads를 사용할 때 메인 스레드와의 통신 오버헤드를 최소화하기 위한 설계 전략은 무엇인가요?
Q. Node.js Stream API의 Readable, Writable, Duplex, Transform 각 타입의 차이와 내부 버퍼링 메커니즘을 설명하고, backpressure가 발생하는 원리와 이를 처리하지 않았을 때의 메모리 문제를 설명해주세요. 또한 대용량 파일 업로드/다운로드 시스템을 설계할 때 Stream을 활용한 메모리 효율적인 아키텍처 패턴을 제시해주세요.
highWaterMark와 내부 버퍼, 그리고 읽기 속도와 쓰기 속도의 불균형이 메모리에 미치는 영향을 중심으로 생각해보세요.
Readable은 데이터 소스, Writable은 데이터 목적지, Duplex는 양방향 독립 스트림, Transform은 데이터 변환 스트림입니다. 각 스트림은 highWaterMark 기준으로 내부 버퍼를 관리하며, Readable이 Writable보다 빠르게 데이터를 생성할 때 backpressure가 발생합니다. write() 메서드가 false를 반환하면 버퍼가 가득 찬 것이며, 이를 무시하고 계속 쓰면 메모리 누수가 발생합니다. pipe() 메서드는 자동으로 backpressure를 처리하므로 대용량 파일 처리 시 권장됩니다. 아키텍처 패턴으로는 multipart upload를 Stream으로 받아 chunk 단위로 S3에 직접 전송하거나, Transform Stream으로 암호화/압축을 파이프라인화하여 메모리 사용을 일정하게 유지하는 방식을 사용합니다. 또한 Stream pool을 구성해 동시 처리 수를 제한하고 시스템 리소스를 보호합니다.
- • 4가지 Stream 타입의 역할과 차이점
- • highWaterMark 기반 버퍼링과 backpressure 메커니즘
- • pipe()를 통한 자동 backpressure 처리
- • 대용량 파일 처리를 위한 Stream 파이프라인 아키텍처
동영상 스트리밍 서비스에서 수 GB 파일을 메모리에 로드하지 않고 실시간으로 클라이언트에 전송하는 시스템 구현
Stream을 사용하는 것과 Buffer에 전체 데이터를 로드하는 것의 성능 트레이드오프를 어떻게 판단하시나요?
Q. Node.js의 CommonJS(require)와 ES Modules(import/export)의 근본적인 차이를 로딩 방식, 실행 시점, 순환 참조 처리 관점에서 설명하고, 대규모 모노레포 환경에서 두 시스템을 혼용할 때 발생할 수 있는 문제와 마이그레이션 전략을 제시해주세요.
동기/비동기 로딩, 정적/동적 분석, 그리고 module.exports와 export의 차이를 중심으로 생각해보세요.
CommonJS는 동기적으로 런타임에 모듈을 로드하며 require()가 실행되는 시점에 파일을 읽고 캐싱합니다. ES Modules는 비동기적이며 파싱 단계에서 정적 분석을 통해 의존성 그래프를 먼저 구성합니다. 순환 참조 시 CommonJS는 부분적으로 초기화된 exports 객체를 반환하지만, ESM은 TDZ(Temporal Dead Zone)로 인해 ReferenceError가 발생할 수 있습니다. 모노레포에서 혼용 시 .mjs/.cjs 확장자 관리, package.json의 type 필드 설정, 그리고 interop 레이어에서의 default export 처리 문제가 발생합니다. 마이그레이션 전략으로는 먼저 leaf 모듈부터 ESM으로 전환하고, 점진적으로 상위 레이어로 확장하며, 과도기에는 dual package 방식으로 양쪽 모두 지원하는 번들을 제공하는 방법을 사용합니다.
- • CommonJS의 동기적 런타임 로딩 vs ESM의 정적 분석
- • 순환 참조 처리 방식의 차이
- • 모노레포 환경에서의 혼용 문제(확장자, type 필드, interop)
- • leaf-first 마이그레이션과 dual package 전략
레거시 Express 애플리케이션을 최신 Fastify와 ESM 기반으로 점진적으로 전환하는 대규모 리팩토링 프로젝트
Tree shaking이 CommonJS에서는 어렵고 ES Modules에서는 가능한 이유를 설명해주세요.
Q. Node.js Cluster 모듈을 사용하여 멀티 프로세스 환경을 구성할 때, 마스터-워커 프로세스 간 통신 방식과 로드 밸런싱 알고리즘(round-robin vs OS scheduling)의 차이를 설명하고, 세션 관리와 상태 공유 문제를 어떻게 해결해야 하는지 설명해주세요. 또한 PM2, Docker Swarm, Kubernetes 등 다른 프로세스 관리 방식과 비교하여 어떤 상황에서 Cluster 모듈을 선택해야 하는지 판단 기준을 제시해주세요.
IPC 채널, sticky session, 그리고 컨테이너 오케스트레이션의 스케일링 방식 차이를 고려해보세요.
Cluster 모듈은 마스터 프로세스가 소켓을 listen하고 워커에게 연결을 분배하며, IPC 채널을 통해 메시지를 주고받습니다. Round-robin 방식은 Node.js가 직접 분배를 제어하고, OS scheduling은 커널 레벨에서 처리하는데 Windows와 일부 Unix에서는 OS 방식이 기본입니다. 세션 관리는 Redis 같은 외부 저장소를 사용하거나, sticky session을 구현해 같은 클라이언트를 동일 워커로 라우팅해야 합니다. PM2는 Cluster 모듈을 wrapping하여 무중단 재시작과 모니터링을 제공하므로 단일 서버 환경에서 유용하고, Docker Swarm이나 Kubernetes는 컨테이너 레벨 스케일링으로 여러 노드에 분산 가능하므로 대규모 환경에 적합합니다. Cluster 모듈은 CPU 코어 활용이 주 목적이고 단일 서버 최적화가 필요할 때 선택하며, 다중 서버 환경에서는 컨테이너 오케스트레이션이 더 적합합니다.
- • 마스터-워커 IPC 통신과 소켓 분배 메커니즘
- • Round-robin vs OS scheduling 로드 밸런싱
- • Redis 기반 세션 관리와 sticky session 전략
- • Cluster vs PM2 vs Kubernetes 선택 기준
트래픽이 많은 API 서버에서 4코어 CPU를 모두 활용하기 위해 Cluster 모듈로 4개 워커를 실행하는 구성
무중단 배포를 위해 워커 프로세스를 순차적으로 재시작할 때 고려해야 할 사항은 무엇인가요?
Q. Node.js에서 비동기 에러 처리 패턴의 진화 과정을 callback(error-first), Promise, async/await 관점에서 설명하고, 각 패턴에서 uncaughtException, unhandledRejection 이벤트가 발생하는 조건과 프로덕션 환경에서 이를 처리하는 전략을 제시해주세요. 특히 async/await에서 try-catch를 빠뜨렸을 때의 위험성과 전역 에러 핸들러의 역할을 설명해주세요.
각 패턴에서 에러가 전파되는 방식과 catch되지 않은 에러가 프로세스에 미치는 영향을 중심으로 생각해보세요.
Callback 패턴에서는 error-first 규칙으로 첫 번째 인자에 에러를 전달하며, 에러 처리를 누락하면 조용히 실패합니다. Promise는 reject를 통해 에러를 전파하고 .catch()로 처리하며, catch되지 않으면 unhandledRejection 이벤트가 발생합니다. async/await는 동기 코드처럼 try-catch를 사용하지만, catch 없이 reject된 Promise는 여전히 unhandledRejection을 발생시킵니다. uncaughtException은 동기 코드나 callback에서 throw된 에러가 catch되지 않을 때 발생하며, 프로세스가 불안정한 상태가 됩니다. 프로덕션에서는 unhandledRejection 발생 시 로깅 후 graceful shutdown을 수행하고, uncaughtException은 즉시 프로세스를 종료하며 PM2나 Kubernetes가 재시작하도록 합니다. 전역 에러 핸들러는 마지막 안전망 역할이지만, 근본적으로는 모든 비동기 코드에 명시적 에러 처리를 하는 것이 원칙입니다.
- • callback, Promise, async/await의 에러 전파 메커니즘
- • uncaughtException과 unhandledRejection의 발생 조건
- • 프로덕션 환경에서의 graceful shutdown 전략
- • 전역 핸들러는 안전망이며 명시적 처리가 원칙
마이크로서비스에서 외부 API 호출 실패를 적절히 처리하지 않아 전체 서비스가 다운되는 것을 방지하는 에러 핸들링 설계
Domain 모듈이 deprecated된 이유와 대안으로 어떤 패턴을 사용하시나요?
Q. V8 엔진의 메모리 구조를 New Space(Scavenger)와 Old Space(Mark-Sweep-Compact) 관점에서 설명하고, Node.js 애플리케이션에서 메모리 누수가 발생하는 대표적인 패턴(클로저, 전역 변수, 이벤트 리스너)을 제시하고 진단 방법을 설명해주세요. 또한 --max-old-space-size 플래그의 의미와 대용량 데이터 처리 시 메모리 한계를 극복하기 위한 아키텍처 전략을 제시해주세요.
Generational GC의 원리와 힙 스냅샷 분석, 그리고 메모리 제약을 우회하는 설계 패턴을 고려해보세요.
V8은 Generational GC를 사용하며 New Space는 단명 객체를 빠르게 수집하는 Scavenger 알고리즘을, Old Space는 장수 객체를 Mark-Sweep-Compact로 처리합니다. 메모리 누수는 클로저가 외부 스코프를 계속 참조하거나, 전역 변수에 대량 데이터를 누적하거나, 이벤트 리스너를 removeListener하지 않을 때 발생합니다. 진단은 Chrome DevTools의 힙 스냅샷으로 retained size가 큰 객체를 찾거나, --inspect 플래그로 프로파일링하며, clinic.js나 heapdump 모듈을 사용합니다. --max-old-space-size는 Old Space 크기를 조정하는 플래그로 기본 1.4GB를 늘릴 수 있지만, 근본적으로는 Stream을 사용한 chunk 처리, Worker Threads로 메모리 격리, 또는 데이터를 외부 저장소(Redis, DB)에 오프로드하는 아키텍처로 메모리 한계를 우회해야 합니다.
- • New Space(Scavenger)와 Old Space(Mark-Sweep-Compact) GC 메커니즘
- • 클로저, 전역 변수, 이벤트 리스너 메모리 누수 패턴
- • 힙 스냅샷과 프로파일링 도구를 통한 진단
- • Stream, Worker Threads, 외부 저장소 오프로드 전략
데이터 ETL 파이프라인에서 수백만 건의 레코드를 처리할 때 메모리 부족으로 크래시가 발생하는 문제를 Stream 기반으로 재설계
WeakMap과 WeakSet이 일반 Map, Set과 다르게 메모리 관리에서 어떤 이점이 있나요?
Q. Node.js에서 N-API(Node-API)를 사용하여 C/C++ 네이티브 애드온을 작성하는 이유와 장점을 설명하고, 기존 NAN(Native Abstractions for Node.js)과의 차이점을 설명해주세요. 또한 네이티브 애드온을 도입할 때 고려해야 할 배포, 빌드, 크로스 플랫폼 이슈와 성능 트레이드오프를 설명하고, 어떤 상황에서 네이티브 애드온 대신 WASM(WebAssembly)을 선택해야 하는지 판단 기준을 제시해주세요.
ABI 안정성, 빌드 도구 체인, 그리고 JavaScript와 네이티브 코드 간 컨텍스트 전환 비용을 고려해보세요.
N-API는 ABI(Application Binary Interface) 안정성을 제공하여 Node.js 버전이 바뀌어도 재컴파일 없이 바이너리를 재사용할 수 있습니다. NAN은 각 Node.js 버전마다 재컴파일이 필요하고 V8 API 변경에 취약하지만, N-API는 V8에서 추상화되어 있어 안정적입니다. 배포 시 node-gyp로 소스를 빌드하거나 prebuild를 사용해 플랫폼별 바이너리를 미리 준비해야 하며, Windows/Linux/macOS 각각에서 빌드 환경 구성이 필요합니다. 성능 트레이드오프로는 JavaScript-C++ 컨텍스트 전환 오버헤드가 있어 호출 빈도가 높으면 오히려 느릴 수 있습니다. WASM은 샌드박스 환경에서 안전하고 브라우저와 코드 공유가 가능하며 빌드가 단순하므로, 시스템 리소스 접근이 불필요하고 크로스 플랫폼 배포가 중요한 경우 선택합니다. 네이티브 애드온은 하드웨어 직접 제어나 기존 C/C++ 라이브러리 통합이 필수적일 때 선택합니다.
- • N-API의 ABI 안정성과 NAN 대비 장점
- • node-gyp, prebuild 등 빌드 및 배포 전략
- • JavaScript-C++ 컨텍스트 전환 오버헤드
- • WASM과의 비교 및 선택 기준
이미지 처리 라이브러리인 sharp가 libvips C 라이브러리를 N-API로 바인딩하여 고성능 이미지 변환을 제공하는 사례
네이티브 애드온에서 비동기 작업을 처리하기 위한 libuv의 thread pool 활용 방법을 설명해주세요.
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!