우선 스트리밍 서비스가 이루어지는 과정에 대해서 인지하고 시작하자. 더 재밌어진다.
stream을 활용하기 위한 외부 패키지가 많이 존재하지만 여기서는 node의 내장 stream 모듈만을 살펴보도록하자.
node에는 네이티브 stream 객체들 많다. 쓰면 읽어야 하니 Readable stream과 Writable Stream이 쌍으로 있습니다. 이 외에 Duplex 스트림과 Transform 스트림이 존재합니다.
Stream이 필요한 이유
큰 파일을 fs.readFile로 보내려고 하면 이벤트 루프가 blocking 되므로 스트림을 사용하면 좋다고 아래 포스트에서 정리한 바가 있습니다. 10분 걸려도 재생되지 않던 비디오가 스트리밍으로 서비스하니 접속 즉시 비디오를 감상할 수 있었죠.
위와 비슷한 작업을 작업 관리자를 켜서 노드 프로세스가 얼마나 많은 메모리를 소비하게 되는지 수치적으로 확인해보겠습니다.
우선 큰 용량의 파일을 준비합니다. 찾기 귀찮다면 아래처럼 만들어도 됩니다.
백만줄 쓰니까 41.993KB 나오네요.
const fs = require("fs");
const file = fs.createWriteStream("../jonnyCrazy.txt");
// 1e6 = 1 * 10^6 = 1000000
for (let i = 0; i <= 1e6; i++) {
file.write("All work and no play makes Jack a dull boy\n");
}
file.end();
fs.readFile로 서빙해보겠습니다.
const express = require("express");
const fs = require("fs");
app = express();
app.get("/", (req, res) => {
fs.readFile("./jonnyCrazy.txt", (err, data) => {
if (err) return res.json({ err });
res.end(data);
});
});
app.listen(4001, () => console.log("http://localhost:4001"));
일단 blocking 되는 것은 당연지사고 메모리 사용이 50.5MB로 치솟았습니다.
참고로 단순히 문자열만 서빙하는 node의 경우 9.7MB를 소비합니다.
이를 스트림을 통해 서빙해보도록하겠습니다.
ReadStream을 생성해서 pipe로 서빙합니다.
app.get("/", (req, res) => {
const stream = fs.createReadStream("./jonnyCrazy.txt");
stream.pipe(res);
});
app.listen(4001, () => console.log("http://localhost:4001"));
17.0MB 정도 메모리를 소비하는 것을 확인할 수 있습니다.
blocking이더라도 정보를 주기적으로 받아오기 때문에 이용자 입장에서는 반응이 빠른 것으로 느낍니다.
Stream 객체
node에는 Readable, Writable, Duplex, Transform 스트림이 있다고 언급한 바 있습니다.
각 스트림의 타입에 대해 알아보기 위해 공식 문서를 좀 긁어와보겠습니다.
Writable streams : Writable streams are an abstraction for a destination to which data is written.
const myStream = getWritableStreamSomehow();
myStream.write('some data');
myStream.write('some more data');
myStream.end('done writing data');
- 읽기 가능한(readable) 스트림은 소비할수 있는 데이터를 추상화한 것입니다. 예를들어 fs.createReadStream 메소드가 그렇죠.
- 쓰기 가능한 (writable) 스트림은 데이터를 기록할수 있는 종착점을 추상화한 것입니다. 예를 fs.createWriteStream 메소드가 있죠.
- 듀플렉스(duplex) 스트림은 읽기/쓰기 모두 가능합니다. 예를 들어 TCP 소켓이 있죠.
- 트랜스폼(transform) 스트림은 기본적으로 듀플렉스 스트림입니다. 데이터를 읽거나 기록할 때 수정/변환될수 있는 데이터죠. 예를들어 gzip을 이용해 데이터를 압축하는 zlib.createGzip 스트림이 있습니다. 입력은 쓰기 가능한 스트림이고 출력은 읽기 가능한 스트림인 트랜스폼 스트림을 생각할 수 있을 겁니다. 트랜스폼 스트림이 "스트림을 통해(through streams)"라고 불리는 것을 들어 봤을 겁니다.
참고한 글)
https://www.freecodecamp.org/news/node-js-streams-everything-you-need-to-know-c9141306be93/
https://jeonghwan-kim.github.io/node/2017/07/03/node-stream-you-need-to-know.html
https://jeonghwan-kim.github.io/node/2017/08/07/node-stream-you-need-to-know-2.html
https://jeonghwan-kim.github.io/node/2017/08/12/node-stream-you-need-to-know-3.html
https://nodejs.dev/learn/nodejs-streams
https://programmingsummaries.tistory.com/363
'Node, Nest, Deno > 🚀 Node.js (+ Express)' 카테고리의 다른 글
winston으로 로깅을 해보자 (0) | 2021.02.27 |
---|---|
더 나은 node.js 기반 server를 위한 아키텍쳐 + 팁들! (0) | 2021.02.15 |
fs 모듈로 Binary 파일 처리부터 Buffer와 Stream까지 (3) | 2021.01.28 |
node.js에서 child_process로 다른 프로세스 생성하기 (0) | 2021.01.27 |
node.js 서비스 로그 관리 : pm2 log management (0) | 2021.01.11 |