Programing/JavaScript

[JavaScript] Node.js에서 스트림(Streams)이해하기

유니얼 2024. 12. 29. 01:57
728x90

Node.js는 비동기 I/O 처리를 효율적으로 수행하기 위해 **스트림(Stream)**이라는 강력한 개념을 제공합니다. 스트림은 대규모 데이터를 처리하는 데 매우 유용하며, 파일 읽기/쓰기, HTTP 요청/응답, 데이터 전송 등 다양한 작업에서 사용됩니다. 이 글에서는 Node.js의 스트림 개념, 종류, 그리고 사용 예제를 다룹니다.


1. 스트림(Stream)이란?

스트림은 데이터를 조각(청크, Chunk) 단위로 읽고 쓰는 방식을 의미합니다. 스트림을 사용하면 데이터를 한 번에 모두 메모리에 로드하지 않고, 점진적으로 처리할 수 있어 메모리 사용을 최소화할 수 있습니다.

스트림의 주요 특징

  • 비동기 처리: 데이터를 처리하는 동안 애플리케이션이 멈추지 않음.
  • 메모리 효율성: 대규모 데이터를 처리할 때 전체를 로드하지 않음.
  • 유연성: 읽기, 쓰기, 변환, 그리고 파이프라인 형태로 데이터를 처리 가능.

2. Node.js 스트림의 종류

Node.js 스트림은 네 가지 기본 유형으로 나뉩니다.

1) Readable Stream

  • 데이터를 읽는 데 사용.
  • 예: 파일 읽기, HTTP 요청, 소켓 데이터 수신.
const fs = require('fs');
const readableStream = fs.createReadStream('example.txt');

readableStream.on('data', (chunk) => {
  console.log('Data chunk:', chunk.toString());
});

2) Writable Stream

  • 데이터를 쓰는 데 사용.
  • 예: 파일 쓰기, HTTP 응답, 소켓 데이터 송신.
const fs = require('fs');
const writableStream = fs.createWriteStream('output.txt');

writableStream.write('Hello, World!\n');
writableStream.end();

3) Duplex Stream

  • 데이터를 읽고 쓰는 데 모두 사용.
  • 예: 네트워크 소켓.
const { Duplex } = require('stream');
const duplexStream = new Duplex({
  read(size) {
    this.push('Hello from Duplex Stream!');
    this.push(null); // 스트림 끝
  },
  write(chunk, encoding, callback) {
    console.log('Writing:', chunk.toString());
    callback();
  },
});

duplexStream.on('data', (chunk) => console.log(chunk.toString()));
duplexStream.write('Hello, Duplex!');
duplexStream.end();

4) Transform Stream

  • 데이터를 변환하면서 읽고 씀.
  • 예: 압축, 암호화.
const { Transform } = require('stream');
const transformStream = new Transform({
  transform(chunk, encoding, callback) {
    this.push(chunk.toString().toUpperCase());
    callback();
  },
});

process.stdin.pipe(transformStream).pipe(process.stdout);

3. 스트림 이벤트

Node.js 스트림은 이벤트 기반으로 동작하며, 주로 다음과 같은 이벤트를 사용합니다:

 

이벤트  이름설명
data 새로운 데이터 청크가 들어올 때 발생
end 모든 데이터가 처리된 후 발생
error 스트림 처리 중 에러가 발생했을 때 발생
finish 쓰기 스트림에서 모든 데이터가 기록된 후 발생

Readable Stream 이벤트 예제

const fs = require('fs');
const readableStream = fs.createReadStream('example.txt');

readableStream.on('data', (chunk) => {
  console.log('Chunk:', chunk.toString());
});

readableStream.on('end', () => {
  console.log('No more data to read.');
});

readableStream.on('error', (err) => {
  console.error('Error:', err);
});

4. 스트림의 활용 사례

1) HTTP 요청/응답 처리

Node.js에서 HTTP 요청(Request)와 응답(Response)은 각각 Readable StreamWritable Stream으로 동작합니다.

const http = require('http');

http.createServer((req, res) => {
  if (req.method === 'POST') {
    let body = [];
    req.on('data', (chunk) => body.push(chunk));
    req.on('end', () => {
      body = Buffer.concat(body).toString();
      res.end(`Received: ${body}`);
    });
  } else {
    res.end('Send a POST request.');
  }
}).listen(3000, () => console.log('Server is running on port 3000.'));

2) 파일 복사

Readable Stream과 Writable Stream을 조합해 파일을 복사할 수 있습니다.

const fs = require('fs');

const readableStream = fs.createReadStream('source.txt');
const writableStream = fs.createWriteStream('destination.txt');

readableStream.pipe(writableStream);

3) 데이터 변환

Transform Stream을 사용해 데이터를 변환할 수 있습니다. 아래는 문자열을 대문자로 변환하는 예제입니다.

const { Transform } = require('stream');

const transformStream = new Transform({
  transform(chunk, encoding, callback) {
    this.push(chunk.toString().toUpperCase());
    callback();
  },
});

process.stdin.pipe(transformStream).pipe(process.stdout);

 

5. 에러 처리

스트림은 비동기적으로 동작하므로 에러가 발생할 수 있습니다. 모든 스트림에 에러 이벤트 리스너를 등록해야 합니다.

const fs = require('fs');

const readableStream = fs.createReadStream('nonexistent.txt');
readableStream.on('error', (err) => {
  console.error('Error:', err.message);
});

 

6. 스트림의 장점과 단점

장점

  1. 메모리 효율성: 대규모 데이터를 한 번에 로드하지 않고 조각 단위로 처리.
  2. 성능 향상: 데이터가 처리되는 동안 다른 작업을 병렬로 수행 가능.
  3. 유연성: 읽기, 쓰기, 변환을 조합해 다양한 처리가 가능.

단점

  1. 복잡성: 이벤트 기반으로 동작하므로 익숙하지 않은 경우 코드가 복잡해질 수 있음.
  2. 에러 처리 필요: 모든 스트림에서 에러 이벤트를 처리해야 함.

결론

Node.js의 스트림은 데이터 처리의 효율성을 극대화하는 강력한 도구입니다. 이를 통해 대규모 파일 읽기/쓰기, HTTP 요청/응답 처리, 데이터 변환 등 다양한 작업을 메모리 효율적으로 수행할 수 있습니다.

스트림은 Node.js의 기본 기능을 이해하는 핵심 요소 중 하나이므로, 실제 프로젝트에서 이를 적극 활용해 보세요. 추가로 Express.js나 Koa와 같은 프레임워크에서도 스트림을 잘 이해하고 있으면 더욱 유용하게 사용할 수 있습니다.

추가 학습 리소스

https://nodejs.org/api/stream.html#organization-of-this-document

 

Stream | Node.js v23.5.0 Documentation

 

nodejs.org

https://nodejs.org/en/learn/modules/anatomy-of-an-http-transaction

 

Node.js — Anatomy of an HTTP Transaction

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

반응형