Node.js 미드레벨 프레임워크 심화 면접
새 면접Q. Node.js에서 Readable Stream을 생성할 때 flowing mode와 paused mode의 차이점을 설명하고, 두 모드 간 전환이 일어나는 시점과 각 모드에서 데이터를 소비하는 방법을 설명해주세요.
Flowing mode는 데이터가 자동으로 읽혀서 이벤트를 통해 최대한 빠르게 제공되는 모드이며, 'data' 이벤트 리스너를 등록하거나 pipe() 메서드를 호출하면 전환됩니다. Paused mode는 명시적으로 read() 메서드를 호출해야 데이터를 읽을 수 있는 모드로, 스트림 생성 시 기본값입니다. pause() 메서드를 호출하거나 unpipe()로 모든 파이프를 제거하면 paused mode로 전환되며, resume() 메서드로 다시 flowing mode로 변경할 수 있습니다. Paused mode에서는 'readable' 이벤트와 read() 메서드 조합으로 데이터 소비 타이밍을 세밀하게 제어할 수 있습니다.
- • Flowing mode: 자동 데이터 전달, 'data' 이벤트 또는 pipe() 사용
- • Paused mode: 명시적 read() 호출 필요, 'readable' 이벤트 활용
- • 모드 전환: pause()/resume() 메서드, 이벤트 리스너 등록/제거로 제어
- • Paused mode가 데이터 소비 속도를 더 세밀하게 제어 가능
Transform Stream을 구현할 때 _transform 메서드와 _flush 메서드의 역할과 차이점을 설명해주세요.
Q. Node.js 애플리케이션에서 V8 엔진의 가비지 컬렉션(GC)이 성능에 미치는 영향을 설명하고, Old Space와 New Space의 차이, 그리고 --max-old-space-size 플래그를 조정해야 하는 상황과 주의사항을 설명해주세요.
V8의 가비지 컬렉션은 메모리 정리 중 애플리케이션 실행을 중단시켜 지연(latency)을 발생시킵니다. New Space는 단기 생존 객체를 위한 작은 메모리 영역으로 Scavenge 알고리즘을 사용해 빠르게 GC를 수행하며, Old Space는 New Space에서 살아남은 장기 생존 객체를 저장하고 Mark-Sweep-Compact 알고리즘으로 느리지만 철저하게 정리합니다. --max-old-space-size는 Old Space의 최대 크기를 지정하는데, 기본값(약 1.4GB)보다 큰 메모리가 필요한 대용량 데이터 처리 시 증가시켜야 하지만, 너무 크게 설정하면 GC 실행 주기가 늘어나 한 번의 GC 시간이 길어져 응답 지연이 커질 수 있습니다. 메모리 사용 패턴과 애플리케이션 특성에 맞춰 적절한 값을 프로파일링을 통해 결정해야 합니다.
- • GC는 Stop-the-World 방식으로 애플리케이션 실행을 일시 중단
- • New Space: 단기 객체, Scavenge 알고리즘, 빠른 GC
- • Old Space: 장기 객체, Mark-Sweep-Compact, 느린 GC
- • --max-old-space-size 증가는 메모리 부족 해결하지만 GC 시간 증가 트레이드오프 존재
Node.js 애플리케이션에서 GC 로그를 활성화하고 분석하는 방법과 GC 성능 문제를 진단할 수 있는 주요 지표들을 설명해주세요.
Q. Express.js에서 애플리케이션 레벨 미들웨어, 라우터 레벨 미들웨어, 에러 처리 미들웨어의 차이점을 설명하고, 각각의 적절한 사용 사례와 함께 미들웨어 실행 순서가 어떻게 결정되는지 설명해주세요.
애플리케이션 레벨 미들웨어는 app.use() 또는 app.METHOD()로 등록되어 모든 요청에 대해 실행되며, 로깅, 인증, CORS 같은 전역 기능에 사용됩니다. 라우터 레벨 미들웨어는 express.Router() 인스턴스에 바인딩되어 특정 라우트 그룹에만 적용되며, 모듈화된 라우트 관리에 유용합니다. 에러 처리 미들웨어는 4개의 인자(err, req, res, next)를 받으며 반드시 다른 미들웨어 뒤에 등록되어야 하고, next(error) 호출 시 일반 미들웨어를 건너뛰고 실행됩니다. 미들웨어 실행 순서는 등록 순서대로 결정되며, 라우트 매칭 시에는 경로와 HTTP 메서드가 일치하는 순서대로 실행됩니다.
- • 애플리케이션 레벨: 전역 기능, app.use()로 등록
- • 라우터 레벨: 특정 라우트 그룹, Router 인스턴스 사용
- • 에러 처리: 4개 인자, 마지막에 등록, next(error)로 호출
- • 실행 순서는 등록 순서와 라우트 매칭 규칙에 따라 결정
Express.js에서 비동기 미들웨어 체인에서 발생한 에러를 에러 핸들러로 전달하기 위해 express-async-errors 같은 라이브러리가 필요한 이유와 내부 동작 원리를 설명해주세요.
Q. Node.js 애플리케이션에서 Prototype Pollution 취약점이 무엇인지 설명하고, 이 취약점이 발생할 수 있는 상황과 방어 방법을 구체적으로 설명해주세요. 특히 Object.assign(), lodash merge 같은 함수 사용 시 주의사항을 포함해주세요.
Prototype Pollution은 공격자가 JavaScript 객체의 프로토타입을 조작하여 애플리케이션 전체에 영향을 미치는 취약점으로, __proto__, constructor.prototype 같은 특수 속성을 통해 Object.prototype을 오염시킵니다. JSON 파싱 후 객체 병합, 쿼리 파라미터를 객체로 변환할 때 사용자 입력을 검증 없이 처리하면 발생할 수 있습니다. Object.assign()은 얕은 복사만 수행해 __proto__ 같은 키를 일반 속성으로 처리하므로 상대적으로 안전하지만, lodash의 merge나 deep clone 함수들은 재귀적으로 깊은 병합을 수행해 프로토타입 체인까지 접근할 수 있어 위험합니다. 방어를 위해서는 Object.create(null)로 프로토타입 없는 객체 생성, JSON Schema 같은 입력 검증, Object.freeze(Object.prototype) 같은 프로토타입 동결, 그리고 최신 버전의 라이브러리 사용이 필요합니다.
- • Prototype Pollution: __proto__를 통한 Object.prototype 오염 공격
- • 사용자 입력을 객체 병합/깊은 복사할 때 발생 위험
- • lodash merge 같은 깊은 병합 함수가 더 위험
- • 방어: Object.create(null), 입력 검증, 프로토타입 동결, 라이브러리 업데이트
Node.js 애플리케이션에서 helmet.js 미들웨어가 제공하는 보안 헤더들과 각각이 방어하는 공격 유형을 설명해주세요.
Q. Node.js에서 Dependency Injection(DI) 패턴을 구현하는 방법과 이점을 설명하고, Constructor Injection과 Property Injection의 차이점, 그리고 DI 컨테이너(예: InversifyJS, Awilix) 사용의 장단점을 설명해주세요.
Dependency Injection은 객체가 필요로 하는 의존성을 외부에서 주입받는 패턴으로, 결합도를 낮추고 테스트 용이성을 높입니다. Constructor Injection은 생성자를 통해 의존성을 주입하는 방식으로 필수 의존성을 명시적으로 표현하고 불변성을 보장하며, Property Injection은 setter나 속성을 통해 주입하는 방식으로 선택적 의존성에 적합하지만 의존성이 명확하지 않을 수 있습니다. DI 컨테이너는 의존성 생명주기 관리, 자동 의존성 해결, 순환 참조 감지 같은 기능을 제공하여 대규모 애플리케이션에서 유용하지만, 학습 곡선이 있고 런타임 오버헤드가 발생하며 과도한 추상화로 코드 추적이 어려워질 수 있습니다. 소규모 프로젝트에서는 수동 DI만으로도 충분할 수 있습니다.
- • DI는 의존성을 외부에서 주입받아 결합도 감소, 테스트 용이성 증가
- • Constructor Injection: 필수 의존성, 불변성 보장
- • Property Injection: 선택적 의존성, 명시성 부족
- • DI 컨테이너: 생명주기 관리와 자동 해결 제공하지만 복잡도 증가
Node.js에서 Service Locator 패턴과 Dependency Injection 패턴을 비교하고, 각각의 적절한 사용 시나리오를 설명해주세요.
아직 댓글이 없습니다. 첫 번째 댓글을 남겨보세요!