Node.js 시니어 프레임워크 심화 면접
새 면접Q. Node.js의 Async Hooks API를 사용하여 비동기 작업의 생명주기를 추적할 수 있습니다. init, before, after, destroy 각 훅의 역할을 설명하고, 실제 프로덕션 환경에서 Request Context Propagation(요청 컨텍스트 전파)을 구현할 때 Async Hooks를 어떻게 활용할 수 있는지 설명해주세요. 또한 Async Hooks 사용 시 성능 오버헤드와 주의사항도 함께 설명해주세요.
Async Hooks의 init은 비동기 리소스 생성 시, before는 콜백 실행 직전, after는 콜백 실행 직후, destroy는 리소스 파괴 시 호출됩니다. Request Context Propagation은 AsyncLocalStorage(Async Hooks 기반)를 사용하여 구현하며, 각 비동기 작업마다 요청 ID나 사용자 정보를 자동으로 전파할 수 있습니다. Async Hooks는 모든 비동기 작업마다 훅을 실행하므로 10-30%의 성능 오버헤드가 발생할 수 있으며, 프로덕션에서는 AsyncLocalStorage처럼 최적화된 래퍼를 사용하고 불필요한 훅은 비활성화해야 합니다. 또한 메모리 누수 방지를 위해 destroy 훅에서 반드시 리소스를 정리해야 합니다.
- • init, before, after, destroy 각 훅의 실행 시점과 역할
- • AsyncLocalStorage를 활용한 Request Context Propagation 구현
- • 10-30% 성능 오버헤드 발생 가능성과 최적화 전략
- • destroy 훅에서 리소스 정리를 통한 메모리 누수 방지
AsyncLocalStorage와 cls-hooked(continuation-local-storage) 라이브러리의 차이점과 각각의 장단점을 비교해주세요. 또한 AsyncLocalStorage가 내부적으로 어떻게 컨텍스트를 격리하는지 설명해주세요.
Q. Node.js의 Buffer 객체와 JavaScript의 TypedArray(Uint8Array 등)의 차이점을 메모리 할당 방식, 성능, 사용 사례 관점에서 비교 설명해주세요. 특히 Buffer.allocUnsafe()와 Buffer.alloc()의 차이점, 그리고 대용량 바이너리 데이터를 처리할 때 Buffer Pooling 메커니즘이 어떻게 동작하며 이것이 성능에 미치는 영향을 설명해주세요.
Buffer는 Node.js 전용으로 V8 힙 외부 메모리를 사용하며, TypedArray는 표준 JavaScript로 ArrayBuffer 기반입니다. Buffer.alloc()은 메모리를 0으로 초기화하여 안전하지만 느리고, Buffer.allocUnsafe()는 초기화하지 않아 빠르지만 이전 데이터가 남아있을 수 있습니다. Node.js는 8KB 미만의 Buffer 할당 시 내부 Buffer Pool을 사용하여 메모리 할당 오버헤드를 줄이며, 8KB 이상은 직접 할당합니다. 대용량 파일 처리나 네트워크 통신에서는 Buffer Pool을 활용하면 GC 압력을 줄이고 성능을 30-50% 향상시킬 수 있습니다.
- • Buffer는 V8 힙 외부 메모리, TypedArray는 ArrayBuffer 기반
- • Buffer.alloc()은 안전하지만 느리고, allocUnsafe()는 빠르지만 초기화 안됨
- • 8KB 미만은 Buffer Pool 사용, 이상은 직접 할당
- • Buffer Pool 활용 시 GC 압력 감소와 30-50% 성능 향상
Node.js 애플리케이션에서 Buffer를 과도하게 사용할 때 발생할 수 있는 메모리 문제와, V8 힙 외부 메모리가 --max-old-space-size 제한에 포함되지 않는 것이 어떤 위험을 초래할 수 있는지 설명해주세요.
Q. Express.js, Koa.js, Fastify의 미들웨어 아키텍처를 비교 설명하고, 각 프레임워크의 에러 처리 방식과 성능 특성의 차이를 설명해주세요. 특히 Koa의 어니언 모델(Onion Model)과 Express의 선형 모델의 차이점, 그리고 대규모 프로젝트에서 미들웨어 체인이 길어질 때 발생할 수 있는 성능 문제와 해결 방안을 제시해주세요.
Express는 선형 미들웨어 체인으로 next()를 호출하면 다음 미들웨어로 넘어가며, 에러는 4개 인자를 받는 에러 핸들러로 처리합니다. Koa는 async/await 기반 어니언 모델로 await next()를 기준으로 요청과 응답 처리를 대칭적으로 구성하며, try-catch로 에러를 처리합니다. Fastify는 스키마 기반 라우팅과 최적화된 파싱으로 Express보다 2-3배 빠릅니다. 미들웨어 체인이 길어지면 함수 호출 오버헤드가 누적되므로, 조건부 미들웨어 적용, 미들웨어 병합, 라우트별 선택적 적용으로 최적화해야 합니다.
- • Express는 선형 체인, Koa는 어니언 모델, Fastify는 스키마 기반 최적화
- • Koa의 await next()는 요청/응답 처리를 대칭적으로 구성 가능
- • Fastify는 Express 대비 2-3배 빠른 성능
- • 긴 미들웨어 체인은 조건부 적용과 병합으로 최적화
NestJS의 Interceptor, Guard, Pipe, Filter가 Express/Fastify 미들웨어와 어떻게 다르며, 각각의 실행 순서와 역할을 설명해주세요. 또한 AOP(Aspect-Oriented Programming) 관점에서 이들의 장점을 설명해주세요.
Q. Node.js에서 setTimeout, setInterval, setImmediate의 내부 구현 방식과 Event Loop 내에서의 실행 순서를 설명해주세요. 특히 setInterval의 drift 문제와 이를 해결하기 위한 재귀적 setTimeout 패턴, 그리고 수천 개의 타이머를 관리할 때 성능을 최적화하는 방법(타이머 휠, 계층적 타이밍 휠 등)을 설명해주세요.
setTimeout과 setInterval은 타이머 단계에서 실행되며 최소 힙으로 관리되고, setImmediate는 check 단계에서 실행됩니다. setInterval은 콜백 실행 시간과 무관하게 고정 간격으로 스케줄링되어 drift가 발생할 수 있으며, 재귀적 setTimeout을 사용하면 이전 작업 완료 후 정확한 간격을 보장할 수 있습니다. 수천 개의 타이머는 O(log n) 복잡도의 최소 힙 대신 O(1) 복잡도의 타이머 휠(Timing Wheel) 자료구조를 사용하여 최적화할 수 있으며, Node.js는 내부적으로 계층적 타이밍 휠을 사용합니다. 실무에서는 유사한 만료 시간의 타이머를 그룹화하거나 타이머 풀을 사용하여 오버헤드를 줄입니다.
- • setTimeout/setInterval은 타이머 단계, setImmediate는 check 단계 실행
- • setInterval의 drift 문제는 재귀적 setTimeout으로 해결
- • 타이머 휠 자료구조로 O(1) 복잡도 달성
- • 유사 만료 시간 그룹화와 타이머 풀로 오버헤드 감소
Node.js 애플리케이션에서 수백만 개의 WebSocket 연결을 유지하며 각 연결마다 타임아웃을 관리해야 할 때, 메모리와 CPU 효율을 모두 고려한 타이머 관리 전략을 설계해주세요.
Q. Node.js의 require 캐싱 메커니즘(require.cache)의 동작 원리를 설명하고, 개발 환경에서 코드 변경 시 자동으로 모듈을 다시 로드하는 Hot Reload를 구현하는 방법과 그 한계점을 설명해주세요. 특히 순환 참조가 있는 모듈을 다시 로드할 때 발생할 수 있는 문제와, 싱글톤 패턴이나 전역 상태를 가진 모듈의 Hot Reload 시 주의사항을 설명해주세요.
Node.js는 require.cache 객체에 절대 경로를 키로 모듈을 캐싱하여 동일 모듈의 재로딩을 방지합니다. Hot Reload는 파일 시스템 감시(fs.watch) 후 require.cache에서 해당 모듈과 의존 모듈을 삭제하고 재로딩하는 방식으로 구현하지만, 이미 생성된 인스턴스나 클로저에 캡처된 참조는 갱신되지 않습니다. 순환 참조가 있는 모듈은 부분적으로 초기화된 exports를 반환하므로 Hot Reload 시 불완전한 상태가 될 수 있습니다. 싱글톤이나 전역 상태는 재로딩 시 초기화되지 않거나 중복 인스턴스가 생성될 수 있어, 상태 초기화 로직과 의존성 주입 패턴이 필요합니다.
- • require.cache는 절대 경로 키로 모듈 캐싱
- • Hot Reload는 캐시 삭제 후 재로딩이지만 기존 참조는 갱신 안됨
- • 순환 참조 모듈은 부분 초기화 상태로 Hot Reload 시 문제 발생
- • 싱글톤과 전역 상태는 명시적 초기화와 의존성 주입 필요
프로덕션 환경에서 무중단 배포를 위해 새 버전의 코드를 동적으로 로드하는 전략을 설계한다면, Blue-Green 배포나 Rolling Update 외에 애플리케이션 레벨에서 구현할 수 있는 방법과 그 위험성을 설명해주세요.
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!