Node.js 인터뷰 질문 44
질문: Node.js의 process 객체는 무엇이며 어떤 주요 속성과 메서드를 제공하나요?
답변:
Node.js의 process
객체는 현재 실행 중인 Node.js 프로세스에 대한 정보와 제어 기능을 제공하는 전역 객체입니다. 이 객체를 통해 애플리케이션은 현재 프로세스의 환경과 상태를 검사하고, 프로세스 종료와 같은 작업을 수행할 수 있습니다.
process 객체의 주요 속성
1. 환경 관련 속성
1.1. process.env
현재 프로세스의 환경 변수를 담고 있는 객체입니다.
// 환경 변수 접근
console.log(process.env.NODE_ENV); // 'development', 'production' 등
console.log(process.env.HOME); // 사용자 홈 디렉토리
// 새로운 환경 변수 설정
process.env.MY_VARIABLE = "value";
환경 변수는 배포 환경, API 키, 데이터베이스 연결 문자열과 같은 민감한 정보를 저장하는 데 자주 사용되어 소스 코드에 직접 하드코딩하는 것을 방지합니다.
1.2. process.argv
명령줄 인수를 포함하는 배열입니다. 첫 번째 요소는 node
실행 파일 경로이고, 두 번째 요소는 JavaScript 파일 경로이며, 나머지는 추가 명령줄 인수입니다.
// node app.js arg1 arg2
console.log(process.argv);
// 출력:
// ['/usr/bin/node', '/path/to/app.js', 'arg1', 'arg2']
// 명령줄 인수 처리
const args = process.argv.slice(2); // 'node'와 스크립트 경로 제외
console.log(args); // ['arg1', 'arg2']
1.3. process.execPath
Node.js 실행 파일의 절대 경로입니다.
console.log(process.execPath); // '/usr/bin/node'
1.4. process.cwd()
현재 작업 디렉토리를 반환합니다.
console.log(process.cwd()); // '/path/to/current/directory'
2. 플랫폼 정보
2.1. process.platform
프로세스가 실행 중인 플랫폼을 나타내는 문자열입니다(예: 'darwin', 'win32', 'linux').
console.log(process.platform);
// macOS에서: 'darwin'
// Windows에서: 'win32'
// Linux에서: 'linux'
// 플랫폼별 조건부 로직
if (process.platform === "win32") {
console.log("Windows에서 실행 중");
} else if (process.platform === "darwin") {
console.log("macOS에서 실행 중");
} else if (process.platform === "linux") {
console.log("Linux에서 실행 중");
}
2.2. process.arch
프로세서 아키텍처를 나타내는 문자열입니다(예: 'x64', 'arm', 'ia32').
console.log(process.arch); // 'x64', 'arm64' 등
2.3. process.version
Node.js 버전 문자열입니다.
console.log(process.version); // 'v16.13.0' 등
2.4. process.versions
Node.js와 그 종속성의 버전 정보를 포함하는 객체입니다.
console.log(process.versions);
/*
{
node: '16.13.0',
v8: '9.4.146.24-node.14',
uv: '1.42.0',
...
}
*/
3. 메모리 사용량
3.1. process.memoryUsage()
현재 프로세스의 메모리 사용량에 대한 정보를 포함하는 객체를 반환합니다.
console.log(process.memoryUsage());
/*
{
rss: 30932992, // 상주 세트 크기: 프로세스에 할당된 총 메모리
heapTotal: 6537216, // V8에 할당된 총 힙 크기
heapUsed: 4339128, // V8이 실제 사용 중인 힙 크기
external: 8272, // C++ 객체와 같은 V8 외부 메모리
arrayBuffers: 9898 // ArrayBuffer 및 SharedArrayBuffer에 할당된 메모리
}
*/
메모리 누수 디버깅이나 애플리케이션의 메모리 사용량 모니터링에 유용합니다.
4. 프로세스 ID
4.1. process.pid
현재 프로세스의 ID입니다.
console.log(process.pid); // 12345 등
4.2. process.ppid
부모 프로세스의 ID입니다.
console.log(process.ppid); // 12344 등
process 객체의 주요 메서드
1. 프로세스 제어
1.1. process.exit([code])
현재 프로세스를 종료합니다. 종료 코드가 제공되지 않으면 성공(0)을 나타내는 코드로 종료됩니다.
// 성공적인 종료
process.exit(0);
// 실패 종료
process.exit(1);
// 프로세스 종료 전에 정리 작업 수행
function cleanUp() {
console.log("자원 정리 중...");
// 데이터베이스 연결 닫기, 파일 핸들 정리 등
}
// 비정상 종료 시 정리
process.on("uncaughtException", (err) => {
console.error("처리되지 않은 예외:", err);
cleanUp();
process.exit(1);
});
// 정상 종료 처리
function shutdownGracefully() {
console.log("정상 종료 중...");
cleanUp();
process.exit(0);
}
// Ctrl+C 처리
process.on("SIGINT", shutdownGracefully);
// SIGTERM 신호 처리
process.on("SIGTERM", shutdownGracefully);
1.2. process.kill(pid[, signal])
지정된 프로세스 ID에 신호를 보냅니다. 기본적으로 'SIGTERM' 신호를 보냅니다.
// 다른 프로세스 종료
process.kill(1234);
// 특정 신호 보내기
process.kill(1234, "SIGHUP");
2. 표준 I/O 스트림
Node.js는 세 개의 표준 I/O 스트림을 제공합니다:
2.1. process.stdin
표준 입력 스트림으로, 읽기 가능한 스트림입니다.
// 콘솔에서 사용자 입력 읽기
process.stdin.setEncoding("utf8");
console.log("이름을 입력하세요:");
process.stdin.on("data", (data) => {
const name = data.trim();
console.log(`안녕하세요, ${name}님!`);
process.exit();
});
2.2. process.stdout
표준 출력 스트림으로, 쓰기 가능한 스트림입니다.
// console.log는 내부적으로 process.stdout을 사용합니다
process.stdout.write("안녕하세요!\n");
// 진행 표시기 만들기
function showProgress(percent) {
process.stdout.clearLine();
process.stdout.cursorTo(0);
process.stdout.write(`진행률: ${percent}%`);
}
let progress = 0;
const interval = setInterval(() => {
progress += 10;
showProgress(progress);
if (progress >= 100) {
process.stdout.write("\n");
clearInterval(interval);
}
}, 1000);
2.3. process.stderr
표준 오류 스트림으로, 쓰기 가능한 스트림입니다.
// 오류 메시지 출력
process.stderr.write("오류가 발생했습니다!\n");
3. 즉시 실행 타이머
3.1. process.nextTick(callback[, ...args])
다음 이벤트 루프 틱이 시작되기 전에 콜백을 실행합니다. 이는 setTimeout(fn, 0)
보다 더 빠르게 실행됩니다.
console.log("시작");
process.nextTick(() => {
console.log("nextTick 콜백");
});
console.log("종료");
// 출력:
// 시작
// 종료
// nextTick 콜백
nextTick
은 I/O 작업 이후 및 새로운 I/O 작업 시작 전에 콜백을 실행하기 위해 이벤트 루프의 현재 단계가 완료된 후 즉시 실행됩니다.
3.2. setImmediate vs process.nextTick
setImmediate
와 process.nextTick
의 차이점:
console.log("시작");
setImmediate(() => {
console.log("setImmediate 콜백");
});
process.nextTick(() => {
console.log("nextTick 콜백");
});
console.log("종료");
// 출력:
// 시작
// 종료
// nextTick 콜백
// setImmediate 콜백
process.nextTick
은 현재 단계가 완료된 직후, 다음 이벤트 루프 단계가 시작되기 전에 실행됩니다. 반면 setImmediate
는 다음 이벤트 루프 틱의 체크 단계에서 실행됩니다.
4. 이벤트 처리
process
객체는 EventEmitter이며 여러 중요한 이벤트를 발생시킵니다:
4.1. 'exit' 이벤트
프로세스가 종료될 때 발생합니다.
process.on("exit", (code) => {
console.log(`종료 코드 ${code}로 프로세스 종료`);
// 비동기 작업을 여기서 수행하지 마세요!
// 여기서는 동기 작업만 안전하게 수행할 수 있습니다.
});
// 동기 정리 작업을 처리하는 좋은 장소
4.2. 'uncaughtException' 이벤트
처리되지 않은 예외가 발생했을 때 트리거됩니다.
process.on("uncaughtException", (err, origin) => {
console.error("처리되지 않은 예외:", err);
console.error("예외 출처:", origin);
// 정리 후 프로세스 종료가 권장됨
process.exit(1);
});
// 이것은 예외 발생
nonExistentFunction();
⚠️ 주의: uncaughtException
은 프로세스가 불안정한 상태일 수 있으므로 정리 후 프로세스를 다시 시작하는 것이 좋습니다.
4.3. 'unhandledRejection' 이벤트
Promise 거부가 처리되지 않았을 때 발생합니다.
process.on("unhandledRejection", (reason, promise) => {
console.error("처리되지 않은 거부:", promise);
console.error("이유:", reason);
// 이 이벤트는 로깅 목적으로 사용하고,
// 프로세스를 종료하지 않는 것이 좋습니다.
});
// 이것은 처리되지 않은 거부를 발생시킵니다
new Promise((resolve, reject) => {
reject(new Error("문제 발생!"));
});
4.4. POSIX 신호 이벤트
POSIX 신호 이벤트를 수신할 수 있습니다(예: 'SIGINT', 'SIGTERM').
// Ctrl+C 처리
process.on("SIGINT", () => {
console.log("Ctrl+C가 눌렸습니다!");
// 정상 종료 수행
process.exit(0);
});
// SIGTERM 처리 (예: kill 명령에 의해 전송됨)
process.on("SIGTERM", () => {
console.log("SIGTERM 신호를 받았습니다");
// 정상 종료 수행
process.exit(0);
});
console.log("Ctrl+C를 눌러 종료하세요...");
실용적인 활용 사례
1. 환경 기반 구성
// config.js
const env = process.env.NODE_ENV || "development";
const config = {
development: {
port: 3000,
database: "mongodb://localhost:27017/dev_db",
logLevel: "debug",
},
production: {
port: process.env.PORT || 8080,
database: process.env.DATABASE_URL,
logLevel: "info",
},
test: {
port: 3001,
database: "mongodb://localhost:27017/test_db",
logLevel: "error",
},
};
module.exports = config[env];
2. 명령줄 인수 파싱
// app.js
function parseArgs() {
const args = process.argv.slice(2);
const options = { port: 3000, verbose: false };
for (let i = 0; i < args.length; i++) {
if (args[i] === "--port" && i + 1 < args.length) {
options.port = parseInt(args[i + 1], 10);
i++;
} else if (args[i] === "--verbose") {
options.verbose = true;
} else if (args[i] === "--help") {
console.log("사용법: node app.js [--port PORT] [--verbose] [--help]");
process.exit(0);
}
}
return options;
}
const options = parseArgs();
console.log("옵션:", options);
// 사용 예: node app.js --port 8080 --verbose
3. 정상 종료 처리
// server.js
const http = require("http");
const server = http.createServer((req, res) => {
res.writeHead(200);
res.end("Hello World");
});
let connections = [];
// 활성 연결 추적
server.on("connection", (connection) => {
connections.push(connection);
connection.on("close", () => {
connections = connections.filter((curr) => curr !== connection);
});
});
// 서버 시작
server.listen(3000, () => {
console.log("서버가 포트 3000에서 실행 중입니다.");
});
// 정상 종료 함수
function shutdown() {
console.log("정상 종료 중...");
// 새 연결 수락 중지
server.close(() => {
console.log("모든 요청이 완료되었습니다.");
process.exit(0);
});
// 기존 연결에 제한 시간 설정
if (connections.length) {
console.log(`${connections.length}개의 연결 종료 중...`);
connections.forEach((conn) => conn.end());
// 일정 시간 후 강제 종료
setTimeout(() => {
connections.forEach((conn) => conn.destroy());
}, 5000);
}
// 10초 후 강제 종료
setTimeout(() => {
console.log("정상 종료에 실패했습니다. 강제 종료합니다.");
process.exit(1);
}, 10000);
}
// 종료 신호 처리
process.on("SIGTERM", shutdown);
process.on("SIGINT", shutdown);
4. 다중 프로세스 통신
child_process
모듈과 함께 사용하면 프로세스 간 통신을 설정할 수 있습니다:
주 프로세스 (parent.js):
const { fork } = require("child_process");
// 자식 프로세스 생성
const child = fork("./child.js");
// 자식 프로세스에 메시지 보내기
child.send({ hello: "world" });
// 자식 프로세스로부터 메시지 받기
child.on("message", (message) => {
console.log("자식으로부터 받은 메시지:", message);
});
// 5초 후 자식 프로세스 종료
setTimeout(() => {
child.kill(); // SIGTERM 보내기
}, 5000);
자식 프로세스 (child.js):
// 부모 프로세스로부터 메시지 받기
process.on("message", (message) => {
console.log("부모로부터 받은 메시지:", message);
// 부모 프로세스에 응답 보내기
process.send({ received: true, pid: process.pid });
});
// 정상 종료 처리
process.on("SIGTERM", () => {
console.log("종료 중...");
process.exit(0);
});
5. CPU 사용량 모니터링
function monitorCPU() {
const startUsage = process.cpuUsage();
// 1초마다 CPU 사용량 측정
setInterval(() => {
const usage = process.cpuUsage(startUsage);
// 마이크로초에서 밀리초로 변환
const userMS = usage.user / 1000;
const systemMS = usage.system / 1000;
console.log(
`CPU 사용량: 사용자 ${userMS.toFixed(2)}ms, 시스템 ${systemMS.toFixed(
2
)}ms`
);
}, 1000);
}
monitorCPU();
주요 고려 사항 및 모범 사례
안전한 프로세스 종료:
- 항상 정상 종료 핸들러를 구현하여 리소스를 정리하세요.
- 처리되지 않은 예외나 거부를 로깅하고 적절하게 처리하세요.
환경 변수:
.env
파일과dotenv
패키지를 사용하여 환경 변수를 관리하세요.- 민감한 정보는 항상 환경 변수에 저장하고 소스 코드에 포함하지 마세요.
메모리 관리:
- 정기적으로
process.memoryUsage()
를 사용하여 메모리 사용량을 모니터링하세요. - 메모리 제한을 설정하려면
--max-old-space-size
플래그를 사용하세요.
- 정기적으로
크로스 플랫폼 호환성:
process.platform
을 사용하여 플랫폼별 코드를 작성하세요.- 경로 처리 시
path
모듈을 사용하여 플랫폼 간 일관성을 보장하세요.
신호 처리:
- 언제나
SIGTERM
과SIGINT
신호 핸들러를 구현하여 정상 종료를 보장하세요. - 필요한 정리 작업을 할 수 있도록 신호를 무시하지 마세요.
- 언제나
요약
Node.js의 process
객체는 현재 실행 중인 Node.js 프로세스와 상호 작용하는 강력한 방법을 제공합니다. 주요 특징 및 활용법은 다음과 같습니다:
- 환경 및 플랫폼 정보: 환경 변수, 명령줄 인수, 플랫폼 정보에 접근
- 프로세스 제어: 프로세스 종료, 신호 처리, 상태 보고
- 표준 I/O 스트림: stdin, stdout, stderr 스트림 제어
- 즉시 실행 타이머: process.nextTick()을 통한 이벤트 루프 제어
- 이벤트 처리: 프로세스 종료, 미처리 예외, 신호 등의 이벤트 처리
process
객체를 효과적으로 활용하면 Node.js 애플리케이션의 다양한 측면을 제어하고, 환경에 따른 구성, 정상 종료, 그리고 시스템 리소스 사용량 모니터링과 같은 중요한 작업을 수행할 수 있습니다.