
Node.js는 비동기 I/O를 지원하기 때문에, 웹 서버 개발에 적합합니다. Node.js는 하나의 스레드에서 동작하지만, I/O 작업을 이벤트 루프와 콜백 함수를 통해 비동기적으로 처리하기 때문에, CPU 연산이 많지 않은 작업에 높은 성능을 보입니다. I/O 작업이란 파일 읽기/쓰기, 네트워크 통신, 데이터베이스 접근 등으로, 데이터를 입력하고 출력하는 작업을 말합니다.
대용량 데이터 처리는 I/O 작업 중 하나로, 많은 양의 데이터를 읽고 쓰는 작업을 말합니다. 예를 들어, 로그 파일 분석, 비디오 인코딩/디코딩, 이미지 변환 등이 있습니다. 대용량 데이터 처리를 할 때는, 메모리에 모든 데이터를 한꺼번에 올리면 메모리 부족이나 성능 저하가 발생할 수 있습니다. 따라서, 데이터를 작은 조각으로 나누어 순차적으로 처리하는 것이 좋습니다. 이렇게 데이터를 작은 조각으로 나누어 처리하는 방식을 스트리밍이라고 합니다.
스트리밍은 Node.js에서 기본적으로 제공하는 stream 모듈을 통해 구현할 수 있습니다. stream 모듈은 다음과 같은 네 가지 종류의 스트림을 제공합니다.
- Readable: 읽기 가능한 스트림으로, 데이터 소스로부터 데이터를 읽어옵니다. 예를 들어, 파일 읽기, HTTP 요청 등이 있습니다.
- Writable: 쓰기 가능한 스트림으로, 데이터를 목적지에 씁니다. 예를 들어, 파일 쓰기, HTTP 응답 등이 있습니다.
- Duplex: 양방향 스트림으로, 읽기와 쓰기가 모두 가능합니다. 예를 들어, TCP 소켓 등이 있습니다.
- Transform: 변환 스트림으로, 읽기와 쓰기가 모두 가능하며, 데이터를 읽을 때마다 변환 로직을 적용합니다. 예를 들어, 압축/해제, 암호화/복호화 등이 있습니다.
스트림을 사용하면 다음과 같은 이점이 있습니다.
- 메모리 효율성: 데이터를 작은 조각으로 나누어 처리하기 때문에, 메모리에 많은 양의 데이터를 올리지 않아도 됩니다.
- 시간 효율성: 데이터를 처음부터 끝까지 다 읽고 쓰지 않고, 필요한 만큼만 읽고 쓰기 때문에, 시간을 절약할 수 있습니다.
- 파이프라인: 여러 개의 스트림을 연결하여 파이프라인을 구성할 수 있습니다. 파이프라인은 하나의 스트림의 출력을 다른 스트림의 입력으로 전달하는 방식으로, 데이터를 여러 단계에 걸쳐 처리할 수 있습니다.
다음은 Node.js에서 스트림을 사용하여 대용량 데이터를 처리하는 예시 코드입니다. 이 코드는 fs 모듈을 사용하여 로컬 파일을 읽고 쓰는 작업을 수행합니다. 파일을 읽는 스트림과 파일을 쓰는 스트림을 생성하고, 두 스트림을 파이프로 연결하여, 파일의 내용을 복사하는 작업을 합니다.
// fs 모듈을 임포트합니다.
const fs = require('fs');
// 읽기 스트림을 만듭니다. input.txt 파일의 데이터를 읽습니다.
const inputStream = fs.createReadStream('input.txt');
// 쓰기 스트림을 만듭니다. output.txt 파일에 데이터를 씁니다.
const outputStream = fs.createWriteStream('output.txt');
// 읽기 스트림과 쓰기 스트림을 파이프로 연결합니다.
inputStream.pipe(outputStream);
// 작업이 끝나면 콘솔에 메시지를 출력합니다.
outputStream.on('finish', () => {
console.log('파일 복사 완료');
});
아래는 스트림 문법으로 간단하게 비디오 스트리밍 서버를 만드는 예제입니다.
// http 모듈을 불러옵니다.
const http = require('http');
// fs 모듈을 불러옵니다.
const fs = require('fs');
// path 모듈을 불러옵니다.
const path = require('path');
// 비디오 파일의 경로를 지정합니다.
const videoPath = path.join(__dirname, 'video.mp4');
// 서버를 생성합니다.
const server = http.createServer((req, res) => {
// 비디오 파일의 정보를 가져옵니다.
const stat = fs.statSync(videoPath);
// 비디오 파일의 크기를 구합니다.
const fileSize = stat.size;
// 요청 헤더에서 range 값을 가져옵니다.
const range = req.headers.range;
// range 값이 있으면
if (range) {
// range 값에서 시작 바이트와 끝 바이트를 파싱합니다.
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
// 전송할 바이트의 크기를 구합니다.
const chunkSize = end - start + 1;
// 읽기 스트림을 생성합니다. 비디오 파일의 일부분만 읽습니다.
const readStream = fs.createReadStream(videoPath, { start, end });
// 응답 헤더를 설정합니다.
res.writeHead(206, {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunkSize,
'Content-Type': 'video/mp4',
});
// 읽기 스트림과 쓰기 스트림을 파이프로 연결합니다.
readStream.pipe(res);
} else {
// range 값이 없으면
// 응답 헤더를 설정합니다.
res.writeHead(200, {
'Content-Length': fileSize,
'Content-Type': 'video/mp4',
});
// 읽기 스트림을 생성합니다. 비디오 파일의 전체 내용을 읽습니다.
const readStream = fs.createReadStream(videoPath);
// 읽기 스트림과 쓰기 스트림을 파이프로 연결합니다.
readStream.pipe(res);
}
});
// 서버를 3000번 포트에서 실행합니다.
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
'개발' 카테고리의 다른 글
Node.js 도커 컨데이너화 및 컨테이너 오케스트레이션 (0) | 2023.06.16 |
---|---|
Node.js 이메일 발송 및 템플릿 처리 (0) | 2023.06.16 |
node js 캐싱 성능최적화 (Redis, Memcached) (0) | 2023.06.16 |
React 서버리스 함수 개발 및 배포 (0) | 2023.06.15 |
Node js 웹서버 부하 분산 및 클러스터링 (0) | 2023.06.15 |