<정의>
스트림
스트림(Stream)은 대용량의 데이터를 효율적으로 처리하기 위한 추상화된 인터페이스이다.
파일을 읽거나 쓸 때 한 덩어리로 처리하지않고 작은 단위로 시간을 절약하는 방법으로,
한 방향으로 흐르는 데이터의 큰 흐름이라고 볼 수 있다.
스트림은 운영체제에 의해 생성되며, 가상의 연결고리로써 데이터가 흐르는 중간 다리 역할을 한다.
또한 스트림은 데이터를 작은 청크(chunk) 단위로 처리하므로,
메모리 사용을 최소화하고 데이터 처리를 효율적으로 할 수 있다.
참고로 개발 시 스트림을 만들면, RAM에 쌓이기 때문에 다 쓰고 반드시 닫아줘야 한다.
강물이 넘쳐흐르면 안되기 때문이다.
스트림은 이벤트 기반의 API를 사용하여
데이터 읽기, 쓰기, 변환 등의 작업을 비동기적으로 수행한다.
<스트림의 종류>
- Readable Stream: 데이터를 읽을 수 있는 스트림이다. 예를 들어, 파일을 읽거나 HTTP 요청의 본문을 읽을 때 사용된다. (readFile 등도 이에 포함)
const fs = require('fs');
// 파일을 읽는 Readable Stream 생성
const readableStream = fs.createReadStream('example.txt', 'utf8');
readableStream.on('data', (chunk) => {
console.log('Received chunk:', chunk);
});
readableStream.on('end', () => {
console.log('No more data.');
});
readableStream.on('error', (err) => {
console.error('Error:', err);
});
2. Writable Stream: 데이터를 쓸 수 있는 스트림이다. 예를 들어, 파일에 쓰거나 HTTP 응답의 본문을 쓸 때 사용된다.
const fs = require('fs');
// 파일에 쓰는 Writable Stream 생성
const writableStream = fs.createWriteStream('output.txt');
writableStream.write('Hello, World!\n');
writableStream.write('Writing some more data...\n');
writableStream.end('This is the end.\n');
writableStream.on('finish', () => {
console.log('All data has been written.');
});
writableStream.on('error', (err) => {
console.error('Error:', err);
});
3. Duplex Stream: 읽기와 쓰기를 모두 할 수 있는 스트림이다. TCP 소켓이 여기에 해당된다.
4. Transform Stream: 데이터를 읽으면서 변환하여 쓸 수 있는 스트림이다. 데이터 압축 또는 암호화 작업에 사용된다.
입력된 텍스트를 모두 대문자로 변환하여 출력하는 예시를 보자.
const { Transform } = require('stream');
class UpperCaseTransform extends Transform {
_transform(chunk, encoding, callback) {
this.push(chunk.toString().toUpperCase());
callback();
}
}
const upperCaseTransform = new UpperCaseTransform();
process.stdin.pipe(upperCaseTransform).pipe(process.stdout);
<스트림의 작동 방식>
Node.js의 스트림은 이벤트 기반으로 작동한다. 주요 이벤트는 다음과 같다:
- data: 스트림이 데이터를 청크 단위로 제공할 때 발생한다. 데이터를 읽을 수 있을 때마다 발생하는 이벤트. 스트림에서 읽은 데이터를 처리할 때 사용한다.
- end: 스트림이 더 이상 읽을 데이터가 없을 때 발생한다. 스트림에서 데이터를 모두 읽었을 때 발생하는 이벤트. 데이터를 모두 읽었다는 사실을 인지하고 이후 작업이 필요할때 사용한다.
- error: 스트림 작업 중 에러가 발생할 때 발생한다.
- finish: 스트림이 모든 데이터를 다 썼을 때 발생한다.
-이벤트용 함수 = on()
<스트림 etc 예시>
-파일 내용을 버퍼의 청크로 쪼개어 읽기
const fs = require('fs')
//1hello.js파일을 읽어서 ReadStream객체를 생성함
const readStream = fs.createReadStream("./1hello.js",
{
highWaterMark: 10, //기본 64*1024
});
// const readStream = fs.createReadStream("C:\\\\Users\\\\HPE\\\\Downloads\\\\example\\\\cqrs-pattern\\\\docker-compose.yml");
readStream.on('data', (chunk)=>{
// console.log(chunk)
console.log(`-----2-1.data이벤트-----`);
console.log(`data:>>>${chunk.toString()}<<<`)
})
readStream.on("end", ()=>{
console.log(`-----2-2.end이벤트-----`);
console.log("finished")
})
readStream.on("error", (err)=>{
console.log(`-----2.error이벤트-----`);
console.log(`Error reading the file: ${err}`)
})
-2개의 스트림을 연결하는 pipe 파일복붙 - 파일 복제하기
console.log(`-----3. 2개의 스트림을 연결하는 pipe 파일복붙-----`);
const fs = require('fs')
//1hello.js파일을 읽어서 ReadStream객체를 생성함
const readStream = fs.createReadStream("./1hello.js");
// const readStream = fs.createReadStream("1hello.js");
const writeStream = fs.createWriteStream("../1hello.txt")
readStream.pipe(writeStream)
-파일 삭제
console.log(`-----4. 파일삭제-----`);
const fs = require('fs')
fs.rm("./1hello.txt", (err)=>{
if(err){
console.log(err)
}
})
-동기 처리 & 비동기 처리(nonblocking I/O)
const fs = require('fs')
fs.readdir("./", (err, files)=>{
console.log(`-----5-1. 비동기 디렉토리 읽기-----`);
if(err){
console.log(err)
}else{
console.log("비동기", files)
}
})
console.log(`-----5-2. 동기 디렉토리 읽기-----`);
const files = fs.readdirSync("./")
console.log("동기", files)
<스트림 장점>
- 메모리 효율성: 스트림은 데이터를 청크 단위로 처리하므로, 전체 데이터를 메모리에 올리지 않아도 된다. 대용량 파일이나 데이터 스트림을 처리할 때 유리하다.
- 성능: 스트림은 비동기적으로 데이터를 처리하여, 다른 작업이 병렬로 수행될 수 있도록 한다. 전체적인 응답 시간을 줄이고, 서버의 처리 능력을 향상시킨다.
- 파이프라이닝: 스트림을 사용하면 데이터 소스와 목적지 사이에 여러 스트림을 파이프라인으로 연결하여 복잡한 데이터 처리 파이프라인을 쉽게 구성할 수 있다.
<스트림 단점>
- 복잡성: 스트림을 사용한 프로그래밍은 이벤트 기반으로 작동하기 때문에, 콜백 지옥(callback hell)이나 코드의 가독성이 떨어질 수 있다.
- 에러 처리: 스트림에서 발생하는 에러는 별도의 이벤트 리스너를 통해 처리해야 하므로, 에러 처리가 복잡해질 수 있다.
- 스트림 백프레셔: 스트림 간의 데이터 흐름이 불균형할 경우, 백프레셔(backpressure) 문제가 발생할 수 있다. 이는 데이터 생산 속도와 소비 속도의 불균형으로 인해 발생하는 문제이다. 이를 적절히 관리하지 않으면 성능 저하나 메모리 문제를 초래할 수 있다.
Node.js의 스트림은 대용량 데이터 처리를 효율적으로 수행할 수 있는 강력한 도구다.
스트림을 사용하면 메모리 사용을 최소화하고, 비동기적으로 데이터를 처리하여 성능을 향상시킬 수 있다.
하지만 스트림을 다루는 것은 다소 복잡할 수 있고, 에러 처리와 백프레셔 관리에 신경 써야 한다.
적절히 사용하면 스트림은 고성능 애플리케이션을 개발하는 데 매우 유용한 도구가 될 것!
'Backend-dev > nodeJS & express' 카테고리의 다른 글
nodejs로 라우팅 서버 실습해보기! (0) | 2024.07.30 |
---|---|
[javascript] express로 사용자 req 분석하기 (0) | 2024.05.27 |
[javascript] Buffer(버퍼)란? | 장,단점과 노드js를 활용한 예시 (0) | 2024.05.26 |
[javascript] Node.js와 express 비교 (0) | 2024.05.25 |
[javascript] 콜 스택과 이벤트 루프 | 동기/비동기 처리 차이? (0) | 2024.05.25 |