global
노드 전역 객체로 브라우저의 window같은 역할을 수행한다. 생략이 가능하다. (global.require() -> require)
console 객체
console.log(...);
일반적인 사용자 로그를 찍을 수 있다.
console.time(key);
console.timeEnd(key);
프로그램이 진행된 시간을 로그로 나타낼 수 있다. key값은 동일해야한다.
console.table(object or array)
배열(Array) 또는 객체(Object)를 테이블 형태로 출력하는 메서드.
데이터를 **행(row)과 열(column)**로 정리해 가독성을 높이고, 특히 배열이나 객체의 구조를 직관적으로 확인하고 싶을 때 매우 유용하다.
console.error(...);
에러 및 경고 메세지를 출력한다. 기존 log와 다른점은 아래의 이미지를 참고하면 되겠다.

console.dir(object, { color: , depth:});
객체의 내부속성을 Tree구조로 보여주는 console이다. 옵션으로는 color와 depth가 있는데, color는 터미널에서 가독성있게 색상을 보여줄지 여부, depth는 어느정도 단계까지 보여줄지를 나타내는 속성이다.
console.trace();
해당 위치의 스택트레이스를 가져온다. 에러가 날만한 위치에 trace를 두고 실제 테스트환경에서 디버깅되도록 하면 좋은 전략이 될 것 같다.
const string = 'abc';
const number = 1;
const boolean = true;
const obj = {
outside: {
inside: {
key: 'value',
}
}
}
console.time('전체 시간');
console.log('평범한 로그, 쉼표를 이용해 구분이 가능하다');
console.log(string, number, boolean);
console.error('에러 출력');
console.table([{
name: '제로',
birth: 1994
}, {
name: 'hero',
birth: 1998
}]);
console.dir(obj, {
color: false,
depth: 2,
});
console.dir(obj, {
color: true,
depth: 1,
});
console.time('시간 측정');
for (let index = 0; index < 100000; index++) {
}
console.timeEnd('시간 측정');
function b() {
console.trace('에러 위치 추적');
}
function a() {
b();
}
a();
console.timeEnd('전체 시간');

위에서 쓸만한건 console.time, console.dir이 쓸만해보인다.
console.time(name); 은 같은 이름으로만 진행해야만 시간이 측정된다.
console.dir(object, option); 은 객체내의 속성을 Tree 구조로 보여준다. option에는 객체를 몇 단계까지 확장해서 표시할 것인지를 나타내는 depth와 터미널에서 가독성을 높이기 위해 컬러를 사용할지 여부를 묻는 color가 있다.
타이머 API
setTimeout(callback, 밀리초);
밀리초 뒤에 callback을 실행한다.
setInterval(callback, 밀리초);
밀리초 마다 callback이 반복적으로 실행된다. (Loop)
setImmediate(callback);
콜백이 즉시 실행된다.
clearTimeout(timeout 변수);
실행한 timeout을 중지시킨다.
clearInterval(interval 변수);
실행한 interval을 중지시킨다.
clearImmediate(immediate 변수);
실행한 Immeditate 를 중지시킨다.
const timeout = setTimeout(() => {
console.log('1.5초 뒤에 실행');
}, 1500);
const interval = setInterval(() => {
console.log('1초마다 실행');
}, 1000);
const timeout2 = setTimeout(() => {
console.log('실행되지 않습니다.');
}, 3000);
setTimeout(() => {
clearTimeout(timeout2);
clearInterval(interval);
}, 2500);
const immediate = setImmediate(() => {
console.log('즉시 실행');
});
const immediate2 = setImmediate(() => {
console.log('실행되지 않습니다.') ;
});
clearImmediate(immediate2);

process
현재 실행 중인 노드 프로세스에 대한 정보를 담고 있다.
process.version
설치된 노드 버전 출력
process.arch
프로세서 아키텍쳐 정보 (arm, ia32 등)
process.platform
운영체제 플랫폼 정보
process.pid
현재 노드가 실행 중인 프로세스의 아이디
process.uptime()
프로세스가 실행된 후 흐른 시간 (초)
process.execPath
노드 실행파일의 절대경로를 반환 (C:\\Program Files\\nodejs\\node.exe)
process.cwd()
스크립트 위치가 아닌 Node.js 프로세스가 실행된 디렉토리 기준 절대경로 반환
(애플리케이션 실행 위치를 기준으로 상대 경로를 절대 경로로 변환할 때 유용하다.)
const fs = require('fs');
const path = require('path');
const filePath = path.resolve(process.cwd(), 'config.json');
const config = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
console.log(config);
process.cpuUsage()
Node.js 애플리케이션의 CPU 사용량을 측정하기 위한 메서드
process.memoryUsage()
메모리 사용량을 측정하는 도구로, CPU 사용량과 함께 분석할 때 유용하다.
process.nextTick(callback)
다른 콜백 함수들보다 우선 순위가 높은 함수
process.exit(코드)
프로세스 종료. 0이거나 없으면 정상 종료. 그 외에는 비정상 종료를 의미한다.
실무에서 쓰일만한 팁
성능 측정 및 코드 최적화
특정 코드 블록이나 함수가 CPU를 얼마나 사용하는지 측정하여 병목현상을 식별하고 최적화한다.
function heavyComputation() {
let sum = 0;
for (let i = 0; i < 1e8; i++) {
sum += i;
}
return sum;
}
const startUsage = process.cpuUsage();
heavyComputation();
const endUsage = process.cpuUsage(startUsage);
console.log(`User CPU Time: ${endUsage.user / 1000}ms`);
console.log(`System CPU Time: ${endUsage.system / 1000}ms`);

실시간 애플리케이션 모니터링
실시간 애플리케이션에서 CPU 사용량을 지속적으로 모니터링하여 과부하 상태를 감지한다.
setInterval(() => {
const usage = process.cpuUsage();
console.log(`CPU Usage - User: ${usage.user / 1000}ms, System: ${usage.system / 1000}ms`);
}, 5000);
부하 테스트
서버 부하 테스트 도중 특정 요청의 CPU 사용량을 측정하여 처리 성능을 분석한다.
const http = require('http');
const server = http.createServer((req, res) => {
const startUsage = process.cpuUsage();
// CPU 집약적인 작업
for (let i = 0; i < 1e7; i++) {}
const endUsage = process.cpuUsage(startUsage);
console.log(`Request CPU Time: ${endUsage.user / 1000}ms`);
res.end('Done');
});
server.listen(3000, () => console.log('Server is running on port 3000'));
os
os.cpus()
시스템의 CPU 코어 정보를 반환한다.
실무에서는 CPU 코어 수에 따라 작업을 분배하여 성능을 최적화하거나 클러스터(cluster)와 함께 사용해 멀티프로세스 애플리케이션을 구축할 수 있다.
os.freemem()
시스템에서 사용 가능한 **여유 메모리(Free Memory)**를 반환한다.
반환값은 바이트(byte) 단위의 숫자이다.
애플리케이션 실행 중 사용 가능한 메모리를 지속적으로 확인하여 메모리 부족 상태를 감지할 수 있다.
os.totalmem()
시스템의 총 메모리(Total Memory) 용량을 반환한다.
반환값은 바이트(byte) 단위의 숫자이다.
총 메모리와 여유 메모리를 비교하여 시스템의 사용 가능 리소스를 평가할 수 있다.
path
path 모듈은 파일 및 디렉토리 경로를 다루기 위한 다양한 유틸리티를 제공한다.
경로 문자열을 조작하거나 파싱할 때 매우 유용하며, OS 간 경로 구분자(\ 또는 /) 차이를 자동으로 처리해 준다.
path.join()
여러 경로를 결합하여 하나의 경로를 만든다. 자동으로 적절한 구분자를 사용한다.
console.log(path.join('/Users', 'developer', 'project', 'index.js'));
// "/Users/developer/project/index.js" (Windows에서는 "\")
path.resolve()
상대 경로를 절대 경로로 변환한다. 현재 작업 디렉토리(process.cwd())를 기준으로 동작한다.
console.log(path.resolve('index.js'));
// "/Users/developer/project/index.js" (현재 디렉토리 기준)
console.log(path.resolve('/Users', 'developer', 'project', 'index.js'));
// "/Users/developer/project/index.js"
url

const { URL } = require('url');
const myUrl = new URL('https://example.com:8080/path?name=developer&age=30#section');
console.log(myUrl);
/*
URL {
href: 'https://example.com:8080/path?name=developer&age=30#section',
origin: 'https://example.com:8080',
protocol: 'https:',
username: '',
password: '',
host: 'example.com:8080',
hostname: 'example.com',
port: '8080',
pathname: '/path',
search: '?name=developer&age=30',
searchParams: URLSearchParams { 'name' => 'developer', 'age' => '30' },
hash: '#section'
}
*/
const { URL } = require('url');
const myUrl = new URL('...);
url을 구조분해 할당으로 받아온 후 new URL을 하여 url 문자열을 분해하여 객체로 만들어준다.

console.log(myUrl.searchParams.get('name')); // 'developer'
console.log(myUrl.searchParams.get('age')); // '30'
myUrl.searchParams.append('lang', 'en');
console.log(myUrl.href);
// 'https://example.com:8080/path?name=developer&age=30&lang=en'
myUrl.searchParams.delete('age');
console.log(myUrl.href);
// 'https://example.com:8080/path?name=developer&lang=en'
url 객체 내의 searchParams를 이용해서 쿼리파라미터의 속성에 접근할 수 있다.
dns
dns 모듈은 **DNS(Domain Name System)**를 사용하여 IP 주소를 조회하거나, 역방향 조회를 수행하는 등의 작업을 지원한다.
const dns = require('dns').promises;
dns.lookup()
도메인 이름을 IP 주소로 변환한다.
const dns = require('dns').promises;
(async () => {
try {
const result = await dns.lookup('example.com');
console.log(`IP Address: ${result.address}, Family: IPv${result.family}`);
} catch (err) {
console.error('Error during lookup:', err);
}
})();
result.address: 조회된 IP 주소.
result.family: IP 버전 (IPv4: 4, IPv6: 6)
dns.resolve()
특정 DNS 레코드 조회
const dns = require('dns').promises;
(async () => {
try {
const addresses = await dns.resolve('example.com', 'A');
console.log('A Records:', addresses);
} catch (err) {
console.error('Error during resolve:', err);
}
})();
첫 번째 인자: 도메인 이름.
두 번째 인자: 조회할 레코드 유형 (A, AAAA, MX, TXT 등).
crypto
다양한 암호화 기능을 제공하는 모듈로, 암호화된 데이터 처리, 해시 생성, 디지털 서명, 대칭 및 비대칭 암호화 등을 수행할 수 있다. 보안 및 암호화 작업에서 매우 유용하며, 대체로 TLS/HTTPS와 같은 네트워크 통신이나 데이터 보호에 활용된다.
crypto.createHash();
createHash 메서드를 사용하여 데이터의 고유 해시값을 생성할 수 있다. 이는 비밀번호 저장 또는 데이터 무결성 검증에 유용하다.
const crypto = require('crypto');
const hash = crypto.createHash('sha256').update('Hello, World!').digest('hex');
console.log(`Hash: ${hash}`);
Hash: a591a6d40bf420404a011733cfb7b190d62c65bf0bcda32b58c68e6f7d7f274b
- update(data): 해시할 데이터를 추가.
- digest(encoding): 결과를 반환. (hex, base64, binary 등)
crypto.createHmac();
HMAC(Hash-based Message Authentication Code)는 해시와 비밀 키를 결합하여 메시지 인증을 수행합니다.
const hmac = crypto.createHmac('sha256', 'secret-key').update('Hello, World!').digest('hex');
console.log(`HMAC: ${hmac}`);
HMAC: 3e4326d03857c6a518e0a84d62b144f890c7e4f6456e295ef1af8b582a4c5f3c
대칭암호화(AES)
const algorithm = 'aes-256-cbc';
const key = crypto.randomBytes(32); // 256-bit 키
const iv = crypto.randomBytes(16); // 초기화 벡터
// 암호화
const encrypt = (text) => {
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
return encrypted;
};
// 복호화
const decrypt = (encrypted) => {
const decipher = crypto.createDecipheriv(algorithm, key, iv);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
};
const text = 'Hello, World!';
const encryptedText = encrypt(text);
const decryptedText = decrypt(encryptedText);
console.log(`Encrypted: ${encryptedText}`);
console.log(`Decrypted: ${decryptedText}`);
Encrypted: 5a7d2c573b5a4b3e32d...
Decrypted: Hello, World!
비대칭 암호화(RSA)
// RSA 키 쌍 생성
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048, // 키 길이 (2048비트)
});
// 암호화
const encryptedData = crypto.publicEncrypt(publicKey, Buffer.from('Hello, World!'));
console.log(`Encrypted: ${encryptedData.toString('base64')}`);
// 복호화
const decryptedData = crypto.privateDecrypt(privateKey, encryptedData);
console.log(`Decrypted: ${decryptedData.toString()}`);
PBKDF2: 비밀번호 해싱
PBKDF2(Password-Based Key Derivation Function 2)는 비밀번호를 안전하게 해싱하는 데 사용됩니다.
crypto.pbkdf2('password', 'salt', 100000, 64, 'sha256', (err, derivedKey) => {
if (err) throw err;
console.log(`Derived Key: ${derivedKey.toString('hex')}`);
});
실무 활용 사례
비밀번호 저장
비밀번호는 단순 해시 대신 PBKDF2, bcrypt, 또는 argon2 같은 방식을 사용해 저장합니다.
crypto.pbkdf2('user-password', 'random-salt', 100000, 64, 'sha256', (err, derivedKey) => {
if (err) throw err;
console.log(`Stored Password Hash: ${derivedKey.toString('hex')}`);
});
API 요청의 서명
HMAC을 사용해 요청 데이터를 서명하고 서버에서 검증합니다.
const hmac = crypto.createHmac('sha256', 'api-secret').update('request-data').digest('hex');
console.log(`Request Signature: ${hmac}`);
파일 무결성 검증
파일의 해시값을 생성하여 데이터 변조를 감지합니다.
const fs = require('fs');
const hash = crypto.createHash('sha256');
const fileStream = fs.createReadStream('file.txt');
fileStream.on('data', (chunk) => hash.update(chunk));
fileStream.on('end', () => {
console.log(`File Hash: ${hash.digest('hex')}`);
});
util
util 모듈은 다양한 유틸리티 함수들을 제공하며, 특히 비동기 함수 변환, 형식화 출력, 객체 검사 등에서 유용하게 사용된다. 주로 디버깅이나 개발 도구를 작성할 때 활용된다.
const util = require('util');
util.promisify
- 콜백 기반 함수를 Promise 기반 함수로 변환합니다.
- Node.js의 전통적인 콜백 패턴을 현대적인 async/await로 쉽게 전환할 수 있습니다.
const fs = require('fs');
const util = require('util');
// fs.readFile 콜백 버전을 Promise로 변환
const readFileAsync = util.promisify(fs.readFile);
(async () => {
try {
const data = await readFileAsync('example.txt', 'utf-8');
console.log(data);
} catch (err) {
console.error('Error reading file:', err);
}
})();
util.callbackify
- Promise 기반 함수를 콜백 기반 함수로 변환합니다.
- 주로 기존 코드가 콜백 패턴을 사용할 때 유용합니다.
const util = require('util');
// Promise 기반 함수
async function asyncFunction() {
return 'Hello, World!';
}
// 콜백 기반으로 변환
const callbackFunction = util.callbackify(asyncFunction);
callbackFunction((err, result) => {
if (err) throw err;
console.log(result); // "Hello, World!"
});
worker_threads
worker_threads 모듈은 멀티 스레드 작업을 지원하여 CPU 집약적인 작업이나 병렬 처리를 효율적으로 수행할 수 있도록 한다. Node.js는 기본적으로 싱글 스레드 이벤트 루프를 기반으로 하지만, **worker_threads**를 사용하면 다중 스레드 환경에서 작업을 분산 처리할 수 있다.
const { Worker, isMainThread, parentPort } = require('worker_threads');
- Main Thread: Node.js의 기본 실행 스레드.
- Worker Thread: Worker 클래스를 사용해 생성한 스레드로, Main Thread와 독립적으로 실행됩니다.
- Main Thread:
- Worker를 생성하고 작업을 위임합니다.
- worker.postMessage()를 통해 데이터를 Worker에게 전달합니다.
- Worker Thread:
- parentPort.on('message', callback)으로 Main Thread로부터 메시지를 수신합니다.
- 처리 결과를 parentPort.postMessage()를 통해 Main Thread로 전달합니다.
isMainThread 분기 방식
// single_file.js
const { Worker, isMainThread, parentPort } = require('worker_threads');
if (isMainThread) {
// 메인 스레드 로직
const worker = new Worker(__filename);
worker.on('message', (msg) => console.log('From worker:', msg));
worker.postMessage('Hello from Main');
} else {
// 워커 스레드 로직
parentPort.on('message', (msg) => {
console.log('From main:', msg);
parentPort.postMessage('Hello from Worker');
});
}
장점
파일이 하나라서, 모든 코드가 한 곳에 모여 있음.
작업 환경에 따라 파일을 여러 개로 관리하지 않아도 됨.
단점
코드가 길어질수록 메인 스레드 로직과 워커 로직이 뒤섞여 가독성이 떨어질 수 있음.
워커 스레드가 __filename(현재 파일 경로)을 사용하기 때문에 코드 구조가 조금 복잡해 보일 수 있음.
별도 워커 파일 분리 방식
// main.js
const { Worker } = require('worker_threads');
const worker = new Worker('./worker.js');
worker.on('message', (msg) => console.log('From worker:', msg));
worker.postMessage('Hello from Main');
// worker.js
const { parentPort } = require('worker_threads');
parentPort.on('message', (msg) => {
console.log('From main:', msg);
parentPort.postMessage('Hello from Worker');
});
장점
- 메인 스레드와 워커 스레드의 코드가 명확하게 분리되어 가독성이 좋음.
- 워커 로직만 보는 사람이 isMainThread 같은 분기 로직 없이 바로 코드를 이해하기 편함.
- 규모가 커질수록 로직을 여러 워커 파일로 분산 관리하기 쉽다.
단점
파일이 여러 개로 늘어날 수 있음.
작은 예제 수준에서는 파일을 나누는 게 오히려 번거롭게 느껴질 수 있음.
어떤 방식을 택해야 할까?
프로토타이핑이나 간단한 기능 테스트:
- isMainThread로 분기하는 단일 파일 방식도 충분히 괜찮습니다.
규모가 큰 프로젝트나 여러 워커가 필요한 상황:
- 별도 파일을 만들어 워커 코드를 나누는 편이 가독성, 유지보수 측면에서 유리합니다.
팀 컨벤션, 프레임워크, 빌드 도구와의 연계 상황도 고려**해서 결정하는 것이 좋습니다.
예: 번들러(Webpack 등)나 특정 프레임워크에서 워커 파일을 별도로 다루는 방법이 공식화되어 있을 수 있습니다.
worker_threads 장점 및 사용사례
장점
CPU 집약적인 작업:
CPU를 많이 사용하는 연산(예: 암호화, 해시 계산, 데이터 분석)을 메인 스레드와 분리하여 처리 가능.
이벤트 루프 차단 방지:
CPU 집약적인 작업이 메인 스레드 이벤트 루프를 차단하지 않도록 함.
병렬 처리:
여러 Worker를 생성하여 작업을 병렬로 처리함으로써 성능 향상.
사용 사례
대규모 데이터 처리 (예: 이미지 처리, 비디오 인코딩)
암호화 및 해시 계산
머신러닝 모델 실행
백그라운드 데이터 처리
child_process
child_process 모듈은 **자식 프로세스(Child Process)**를 만들어 외부 명령어 실행이나 다른 Node.js 파일 실행 등을 가능하게 해준다. 이를 통해 멀티프로세스 기반의 작업을 수행할 수도 있고, 시스템 명령어(예: ls, pwd, dir)를 Node.js 코드에서 직접 실행할 수 있다.
const childProcess = require('child_process');
spawn()
외부 명령어(예: ls, grep, node)를 실행하고, 실시간으로 I/O 스트림을 주고받을 수 있습니다.
비동기 방식으로 작동합니다. 출력(표준 출력, 표준 에러)은 스트림 형태로 연결됩니다.
const { spawn } = require('child_process');
const ls = spawn('ls', ['-l', '/usr']); // ls -l /usr 실행
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
- ls.stdout.on('data'): 프로세스가 출력하는 결과를 실시간으로 받을 수 있음.
- ls.stderr.on('data'): 에러 출력을 받을 수 있음.
- ls.on('close'): 프로세스가 종료될 때 호출, 종료 코드를 확인 가능.
spawn() 장점
출력이 실시간 스트림으로 연결되므로, 대량 데이터를 처리하거나 실시간 로그가 필요한 상황에 적합합니다.
별도의 메모리에 결과를 다 담아두지 않으므로, 결과가 매우 큰 경우에도 안정적입니다.
exec()
외부 명령어를 실행한 후, 최종 결과(표준 출력 전체)를 버퍼에 담아서 콜백으로 한 번에 넘겨줍니다.
출력이 많은 경우, 버퍼를 전부 차지할 수 있으므로 제한(maxBuffer)에 주의해야 합니다.
const { exec } = require('child_process');
exec('ls -l /usr', (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
console.log(`stdout: ${stdout}`);
console.error(`stderr: ${stderr}`);
});
- 장점: 콜백 하나로 간단하게 처리 가능. 명령의 최종 결과만 필요할 때 편함.
- 단점: 출력이 많으면 기본 버퍼(maxBuffer) 제한에 걸릴 수 있음. 기본값은 약 1MB 정도.
execFile()
파일(실행 파일이나 스크립트)을 직접 실행합니다.
exec()와 비슷하지만, 명령어를 해석하지 않고 파일 경로와 인자를 바로 전달합니다.
보안 면에서 execFile()이 exec()보다 안전할 수 있습니다(명령어에 쉘 해석이 없음).
const { execFile } = require('child_process');
execFile('node', ['--version'], (error, stdout, stderr) => {
if (error) {
console.error('execFile error:', error);
return;
}
console.log(`Node version: ${stdout}`);
});
- 'node' 파일을 실행시키면서 '--version' 인자를 전달
쉘 해석 없이 직접 실행하므로, exec()에 비해 보안상 안전
fork()
Node.js 모듈(자바스크립트 파일)을 자식 프로세스로 실행하고,
IPC(Inter-Process Communication) 채널을 자동으로 생성하여 메시지를 주고받을 수 있습니다.
워커 스레드와는 달리, 프로세스 레벨에서 메모리가 분리됩니다.
대표적으로 클러스터링이나 멀티프로세스 환경을 구축할 때 사용됩니다.
Main file (main.js)
const { fork } = require('child_process');
const child = fork('./child.js');
// 자식에게 메시지 보내기
child.send({ hello: 'world' });
// 자식이 보낸 메시지 받기
child.on('message', (msg) => {
console.log('Message from child:', msg);
});
// 자식 프로세스가 종료되었을 때
child.on('exit', (code) => {
console.log(`Child exited with code ${code}`);
});
Child File (child.js)
process.on('message', (msg) => {
console.log('Message from parent:', msg);
// 메시지 응답
process.send({ foo: 'bar' });
});
실무 활용 사례
CLI 명령 실행 및 결과 처리
- 간단한 리눅스 명령어(예: ls, mkdir, rm)를 Node.js로 감싸서 호출
- spawn()을 통해 실시간 로그 수집 및 출력
백그라운드 파일 처리
- FFmpeg, ImageMagick, 7zip 등 타사 CLI 툴을 Node.js 코드에서 호출하여
미디어 변환, 압축, 데이터 처리에 활용
멀티프로세스 활용 (클러스터링)
- fork()를 통해 여러 Node.js 프로세스를 실행하고, 로드 밸런싱 구현 가능
- 대규모 서버에서 CPU 코어를 효율적으로 사용
보안상의 이유로 execFile() 활용
- 쉘 해석 없이 바이너리나 스크립트를 직접 실행
- 명령어 인자 주입 공격 등을 방지 가능
fs
fs 모듈은 Node.js에서 파일 시스템과 상호작용하기 위한 모듈이다. 로컬 파일을 읽고 쓰고 삭제하거나, 디렉토리를 생성하고, 파일 정보를 확인하는 등의 작업을 수행할 수 있다.
const fs = require('fs');
파일 읽기
비동기 (콜백) 방식
fs.readFile('example.txt', 'utf-8', (err, data) => {
if (err) {
return console.error('Error reading file:', err);
}
console.log('File contents:', data);
});
비동기 (Promise) 방식
Node.js 10 이상에서 util.promisify 또는 fs.promises를 사용할 수 있습니다.
const fs = require('fs').promises;
(async () => {
try {
const data = await fs.readFile('example.txt', 'utf-8');
console.log('File contents:', data);
} catch (err) {
console.error('Error reading file:', err);
}
})();
동기 방식
try {
const data = fs.readFileSync('example.txt', 'utf-8');
console.log('File contents:', data);
} catch (err) {
console.error('Error reading file:', err);
}
동기 방식은 파일 읽기가 완료될 때까지 스레드를 차단하므로, 큰 파일에서는 주의가 필요합니다.
파일 쓰기
비동기 (콜백) 방식
const content = 'Hello, World!';
fs.writeFile('output.txt', content, 'utf-8', (err) => {
if (err) {
return console.error('Error writing file:', err);
}
console.log('File written successfully!');
});
비동기 (Promise) 방식
const fs = require('fs').promises;
(async () => {
try {
await fs.writeFile('output.txt', 'Hello, World!', 'utf-8');
console.log('File written successfully!');
} catch (err) {
console.error('Error writing file:', err);
}
})();
비동기 (Promise) 방식
const fs = require('fs').promises;
(async () => {
try {
await fs.writeFile('output.txt', 'Hello, World!', 'utf-8');
console.log('File written successfully!');
} catch (err) {
console.error('Error writing file:', err);
}
})();
동기 방식
try {
fs.writeFileSync('output.txt', 'Hello, World!', 'utf-8');
console.log('File written successfully!');
} catch (err) {
console.error('Error writing file:', err);
}
디렉토리 작업
디렉토리 생성 (mkdir)
비동기 (콜백) 방식
fs.mkdir('myFolder', (err) => {
if (err) {
return console.error('Error creating directory:', err);
}
console.log('Directory created successfully!');
});
비동기 (Promise) 방식
const fs = require('fs').promises;
(async () => {
try {
await fs.mkdir('myFolder');
console.log('Directory created successfully!');
} catch (err) {
console.error('Error creating directory:', err);
}
})();
Node.js 10 이상에서는 fs.mkdir(path, { recursive: true }) 옵션을 사용하면 하위 디렉토리를 한 번에 생성할 수 있습니다.
디렉토리 내 파일 목록 확인 (readdir)
fs.readdir('myFolder', (err, files) => {
if (err) {
return console.error('Error reading directory:', err);
}
console.log('Directory contents:', files);
});
디렉토리 삭제 (rmdir)
fs.rmdir('myFolder', (err) => {
if (err) {
return console.error('Error removing directory:', err);
}
console.log('Directory removed successfully!');
});
Node.js 12 이상에서는 fs.rmdir(path, { recursive: true }) 옵션을 사용하면 하위 파일·디렉토리도 일괄 삭제할 수 있습니다.
그러나 Node.js 14.14.0 이후로는 fs.rmdir의 recursive 옵션이 폐지(deprecated) 되어 fs.rm(path, { recursive: true }) 사용을 권장하고 있습니다.
파일/폴더 정보 확인
fs.stat() / fs.lstat()
파일 혹은 디렉토리에 대한 **상세 정보(Stats 객체)**를 조회합니다.
fs.stat('example.txt', (err, stats) => {
if (err) {
return console.error('Error getting stats:', err);
}
console.log(`Is file: ${stats.isFile()}`);
console.log(`Is directory: ${stats.isDirectory()}`);
console.log(`File size: ${stats.size} bytes`);
});
파일 삭제
비동기 (콜백) 방식
fs.unlink('unnecessary.txt', (err) => {
if (err) {
return console.error('Error deleting file:', err);
}
console.log('File deleted successfully!');
});
비동기 (Promise) 방식
const fs = require('fs').promises;
(async () => {
try {
await fs.unlink('unnecessary.txt');
console.log('File deleted successfully!');
} catch (err) {
console.error('Error deleting file:', err);
}
})();
스트림(Streams) I/O
파일을 읽고 쓸 때 스트림 방식을 사용할 수도 있습니다. 큰 파일이나 실시간 처리가 필요한 경우 성능 향상을 기대할 수 있습니다.
파일 읽기 스트림
const readStream = fs.createReadStream('largeFile.txt', { encoding: 'utf-8' });
readStream.on('data', (chunk) => {
console.log('Read chunk:', chunk);
});
readStream.on('end', () => {
console.log('File reading finished.');
});
readStream.on('error', (err) => {
console.error('Error reading file:', err);
});
파일 쓰기 스트림
const writeStream = fs.createWriteStream('output.txt');
writeStream.write('First line\n');
writeStream.write('Second line\n');
writeStream.end('Done writing.');
writeStream.on('finish', () => {
console.log('File write stream finished.');
});
writeStream.on('error', (err) => {
console.error('Error writing file:', err);
});
주의사항 및 팁
- 동기 메서드 남용 금지
- readFileSync, writeFileSync 등은 메인 스레드를 블로킹합니다.
- 필요한 경우에만 사용하고, 대부분은 비동기 방식을 권장합니다.
- 에러 처리
- 파일이 없거나 권한이 없으면 에러가 발생합니다.
- 항상 try/catch(동기) 혹은 콜백/프로미스 예외 처리를 해야 합니다.
- 대용량 파일 처리
- 대용량 파일은 스트림 I/O 방식(createReadStream, createWriteStream)을 사용하면 메모리 효율을 높일 수 있습니다.
- 권한 문제
- 운영체제나 사용자 권한에 따라 파일 접근이 제한될 수 있으므로 주의해야 합니다.
- 노드 버전
- fs 모듈은 버전에 따라 일부 옵션(recursive 등)이 추가/변경/폐지될 수 있으므로, 사용하려는 기능이 현재 Node.js 버전에서 지원되는지 확인하세요.
threadpool (*모듈 아님)
threadpool(스레드풀)은 libuv 라이브러리가 내부적으로 관리하는 백그라운드 스레드 풀을 말한다. Node.js는 기본적으로 싱글 스레드 이벤트 루프를 사용하지만, 일부 I/O 작업(파일 시스템, DNS 조회, 암호화 등) 또는 CPU 집약적인 작업을 비동기로 처리하기 위해 libuv 스레드 풀을 사용한다. 이 스레드 풀의 기본 크기는 4개이며, 이를 조정하기 위해 제공되는 환경변수가 바로 UV_THREADPOOL_SIZE 이다.
Node.js Threadpool(libuv 기반)
- 역할
- Node.js 이벤트 루프가 처리할 수 없는 일부 작업(네트워크 소켓을 제외한 블로킹 I/O, CPU 연산 등)을 백그라운드 스레드에 위임합니다.
- 예)
- crypto 모듈의 해시/암호화 연산
- fs 모듈의 파일 읽기/쓰기(특히 대용량)
- dns.lookup() (비동기)
- zlib 모듈의 압축/해제 등
- 기본 크기
- 기본적으로 4개의 스레드가 할당됩니다. (Node.js가 시작될 때 libuv가 4개의 작업자를 준비)
- 작동 원리
- 이벤트 루프가 “스레드 풀에서 처리해야 할 작업”을 등록하면, 스레드 풀 내의 유휴 스레드가 해당 작업을 처리한 뒤, 결과를 이벤트 루프로 다시 전달합니다.
- 메인 스레드는 계속해서 비동기로 들어오는 이벤트를 처리할 수 있습니다.
UV_THREADPOOL_SIZE 환경변수
- 설정 방식
- Node.js 프로세스 실행 시, 환경변수 UV_THREADPOOL_SIZE를 설정하면 스레드 풀의 크기를 조정할 수 있습니다.
- UV_THREADPOOL_SIZE=8 node app.js
- 이렇게 하면 8개의 스레드가 생성됩니다.
- 유효 범위
- 최대값이 정확히 표준으로 명시되어 있지 않으나, 일반적으로 128까지 설정 가능하다고 알려져 있습니다.
- 너무 크게 설정해도 스레드가 너무 많아져서 컨텍스트 스위칭 등 오버헤드가 커질 수 있습니다.
- 언제 늘려야 하나?
- CPU 집약적인 작업이나 파일/암호화 등의 비동기 I/O 작업이 동시에 다수 발생해, 스레드 풀이 4개로는 부족하다고 판단될 때
- 또는 비동기 fs 모듈 호출이 상당히 많아, 요청들이 스레드 풀 대기열에서 막힐 때
- 그럴 때 UV_THREADPOOL_SIZE 값을 늘리면 병렬 처리량이 증가할 수 있습니다.
- 주의 사항
- 스레드를 너무 많이 생성하면 CPU 코어보다 훨씬 많은 스레드를 관리하게 되어, 문맥 교환(Context Switching) 비용이 급증할 수 있습니다.
- 서버의 CPU 코어 수를 고려하여, 적절한 스레드풀 크기를 설정하는 것이 좋습니다(예: 8~16 정도로 테스트).
Threadpool vs Worker Threads
- Threadpool(libuv):
- Node.js 내부적으로 I/O나 CPU 작업을 처리하기 위해 사용되는 스레드 풀
- 개발자가 직접 “스레드”를 생성하는 것이 아니라, libuv가 제공하는 백그라운드 스레드를 활용
- Node.js에서 비동기로 콜백을 등록하면, libuv가 필요에 따라 스레드 풀을 할당해 작업 수행
- Worker Threads (worker_threads 모듈):
- Node.js v10.5.0부터 제공되는 사용자 정의 멀티 스레드 기능
- 개발자가 직접 새 스레드(new Worker())를 만들어, 그 안에서 자바스크립트 코드를 실행할 수 있음
- 스레드마다 개별 이벤트 루프가 존재
- CPU 집약적인 자바스크립트 코드를 메인 스레드와 분리할 수 있음
두 가지는 모두 멀티스레드 개념이긴 하지만, 활용 범위와 제어 방식이 다릅니다.
- Threadpool: libuv가 내부적으로 관리(주로 I/O, 암호화 등).
- Worker Threads: 개발자 직접 스레드를 제어(멀티스레드 자바스크립트 코딩).
어떤 경우에 UV_THREADPOOL_SIZE를 늘려야 할까?
- 대규모 파일 I/O가 동시에 다량 발생
- 예: 10개 이상의 파일을 동시에 읽고, 해시를 구하거나 압축을 수행하는 작업
- 기본 4개의 스레드만으론 작업 대기열이 길어져 처리 속도가 떨어질 수 있음
- 암호화(crypto) 연산이 빈번
- 예: 여러 요청에서 동시에 대칭/비대칭 암호화, 해싱, 서명 검증 등을 하는 경우
- 스레드 풀 크기를 늘리면 각 요청이 빠르게 할당되어 처리 시간을 단축할 수 있음
- 서버가 여러 코어(CPU)를 갖고 있음
- 8코어, 16코어 서버에서 4개의 스레드만 사용하기에는 아깝고, 병렬 처리를 더 늘릴 수 있음
- 그러나 지나치게 큰 값으로 설정하면 스레드 관리 오버헤드 발생
결론 및 견해
- Node.js는 싱글 스레드 이벤트 루프이지만, 내부적으로 libuv 스레드 풀을 사용해 I/O 등 일부 작업을 백그라운드에서 병렬로 처리합니다.
- **기본 스레드풀(4개)**로 충분한 경우가 많지만, 대규모 작업이 몰릴 때는 UV_THREADPOOL_SIZE를 늘려보는 것이 성능 개선에 도움이 될 수 있습니다.
- 단, 스레드풀 크기를 무작정 키우기보다는 서버 자원, 작업 패턴 등을 고려한 후, 적절한 수치를 선택하는 것이 중요합니다.
- (추가로) 만약 Node.js 레벨에서 자바스크립트 코드를 멀티스레드로 분산 처리해야 한다면, worker_threads 모듈을 고려해볼 수 있습니다.
events
events 모듈은 이벤트 기반 프로그래밍을 지원하기 위한 핵심 모듈이다. Node.js 내부에서 EventEmitter를 통해 비동기 방식으로 이벤트를 등록하고, 발생 시 이를 처리하는 방식을 사용한다.
const EventEmitter = require('events');
// 이벤트 방출기(Emitter) 객체 생성
const myEmitter = new EventEmitter();
emitter.on(eventName, listener)
- 특정 이벤트가 발생할 때 실행될 리스너(Listener) 함수를 등록합니다.
- 이벤트가 여러 번 발생하면, 등록된 리스너가 그만큼 반복해서 호출됩니다.
myEmitter.on('greet', (name) => {
console.log(`Hello, ${name}!`);
});
emitter.emit(eventName, [...args])
- 등록된 리스너 함수를 호출(“이벤트를 발생시킨다”고 표현)합니다.
- ...args를 통해 리스너에 인자를 전달할 수 있습니다.
myEmitter.emit('greet', 'Alice');
// 콘솔에 "Hello, Alice!" 출력
emitter.once(eventName, listener)
- 한 번만 실행될 리스너 함수를 등록합니다.
- 이벤트가 한 번 발생하고 나면, 자동으로 리스너가 제거됩니다.
myEmitter.once('welcome', () => {
console.log('This will run only once!');
});
myEmitter.emit('welcome'); // "This will run only once!"
myEmitter.emit('welcome'); // 추가 호출해도 리스너 실행 안 됨
emitter.off(eventName, listener) 또는 emitter.removeListener(eventName, listener)
특정 이벤트에 등록된 리스너를 제거합니다.
function greetHandler(name) {
console.log(`Hello again, ${name}!`);
}
myEmitter.on('greet', greetHandler);
myEmitter.emit('greet', 'Bob'); // "Hello again, Bob!"
myEmitter.off('greet', greetHandler);
// 또는 myEmitter.removeListener('greet', greetHandler);
myEmitter.emit('greet', 'Bob'); // 동작 안 함
emitter.removeAllListeners(eventName?)
특정 이벤트(또는 모든 이벤트)에 등록된 모든 리스너를 제거합니다.
myEmitter.removeAllListeners('greet');
// 이제 'greet' 이벤트에 등록된 모든 핸들러가 제거됨
기타 유용한 속성 및 메서드
- emitter.listenerCount(eventName): 해당 이벤트에 등록된 리스너 수를 반환.
- emitter.listeners(eventName): 해당 이벤트에 등록된 리스너 배열을 반환.
- EventEmitter.defaultMaxListeners / emitter.setMaxListeners(n): 이벤트당 기본 최대 리스너 수(기본 10개)를 조절.
실무에서의 활용
- 비동기 이벤트 흐름 관리
- 예: HTTP 서버에서 특정 요청이 들어올 때마다 이벤트를 생성/처리
- 애플리케이션 내부의 커스텀 이벤트
- 여러 모듈 간의 통신(“어떤 동작이 끝나면 알려준다” 등)에 활용
- 스트림(Streams)
- Node.js 스트림은 EventEmitter를 상속받아서 data, end, error 이벤트를 발생시키는 구조
- 오류 처리(‘error’ 이벤트)
- error 이벤트를 등록해두면, 그 객체에서 발생하는 비동기 에러를 한곳에서 처리 가능
- myEmitter.emit('error', new Error('Something went wrong!'));
주의사항
- 메모리 누수
- 이벤트 리스너를 너무 많이 등록하거나 등록하고 나서 제거하지 않으면, 메모리 누수가 발생할 수 있습니다.
- 특히 서버 측에서 발생하는 대규모 이벤트 처리 시, 리스너 제거나 최대 리스너 수 설정을 고려해야 합니다.
- error 이벤트 핸들러
- EventEmitter에 error 이벤트 핸들러를 등록하지 않은 채로 error 이벤트가 발생하면, Node.js 프로세스가 종료됩니다.
- 필요한 경우, 꼭 myEmitter.on('error', ...) 핸들러를 등록하세요.
- 최대 리스너 제한
- 기본적으로 한 이벤트에 10개 이상의 리스너를 등록하면 경고가 뜹니다.
- emitter.setMaxListeners(n)을 통해 조정 가능합니다.
- 이벤트명 관리
- 이벤트명을 문자열로 직접 지정하기 때문에, 오타나 이벤트 이름 중복 등은 코드 품질에 영향을 줄 수 있습니다.
이벤트 루프와의 관계
- Node.js 이벤트 루프는 콜 스택이 비어 있는 타이밍에, 대기열(queue)에 쌓여 있는 이벤트나 콜백을 한 번에 하나씩 실행합니다.
- EventEmitter는 콜백을 등록해두고, emit()을 호출하면 해당 이벤트가 이벤트 루프 대기열에 올라가서, 메인 스레드가 준비되었을 때 리스너들이 순차적으로 실행되는 구조입니다.
uncaughtException 이벤트
아무 곳에서도 처리되지 않은 에러가 발생하면, uncaughtException 이벤트가 방출된다. 이를 통해 프로세스가 강제로 죽기 전에 마지막에 에러를 로깅하거나 긴급조치를 할 수 있다.
process.on('uncaughtException', (err) => {
console.error('Uncaught Exception:', err.message);
// 로그를 남기거나, 외부로 알림을 보내거나, 간단한 cleanup 시도 등
// ...
// 그 후 프로세스 종료
process.exit(1);
});
왜 종료해야 할까?
- Node.js 애플리케이션은 예외가 처리되지 않은 채 남아있으면, 애플리케이션 상태가 불안정해질 수 있습니다.
- 문맥상 “어디서 에러가 난 것인지” 모르는 상황에서 계속 서비스를 운영하면, 데이터 손상 등이 발생할 위험이 높습니다.
- 따라서 ‘uncaughtException’가 발생하면 프로세스를 종료하는 것이 일반적으로 권장됩니다.
'Node.js' 카테고리의 다른 글
익스프레스 웹 서버 만들기 - express로 HTML 서빙하기 (0) | 2025.01.12 |
---|---|
익스프레스 웹 서버 만들기 - express 서버 사용해보기 (1) | 2025.01.12 |
Node Package Manager (npm) (0) | 2025.01.11 |
http 모듈로 서버 만들기 (0) | 2025.01.11 |
Node.js 기본 모듈 (CommonJS, ES모듈) (0) | 2025.01.09 |