일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- scrum
- 특징
- node.js
- 자바
- 소켓
- 지식 그래프
- 스크럼 마스터
- socket.io
- nodejs
- node
- Groovy
- java
- Django
- RDF
- C++
- benchmark
- 이벤트 루프
- 스레드
- express
- 스크럼
- 예제
- Knowledge Graph
- 개발자
- Router
- Stream
- 노드
- 파헤쳐보자
- 노드js
- Python
- ngrinder
- Today
- Total
라봉이의 개발 블로그
[Node.js][노드] stream(스트림) 이란?? 본문
Node.js에서 stream이란 "스트리밍 데이터로 작업하기 위한 추상적인 인터페이스"라고 공식 문서에 나와있다.
뭔 소리일까..?
일단 stream이란 개념을 짚고 넘어가야 될 듯 싶다.
stream이란 일종의 추상적인 개념인데 입출력 기기나 프로세스, 파일 등 어디로 가는 지, 어디로 나왔는 지 상관없이 통일된 방식으로 데이터를 다루기 위한 가상의 개념이다.
그러니까 stream을 정의하기란 너무나 모호하다.
Node.js에서 많은 Object들이 stream Object 이다. 예를 들어서 http 서버의 request나 process.stdout도 stream 인스턴스이다.
stream들은 읽을 수 있거나(readable), 쓸 수 있거나(writable) 혹은 둘 다(both)가 될 수 있다.
모든 stream들은 EventEmitter의 인스턴스이다. 따라서 이벤트 핸들러를 작성할 수 있다.
Stream 타입 종류
Readable: 읽을 수 있는 스트림(ex. fs.createReadStream())
Writable: 쓸 수 있는 스트림(ex. fs.createWriteStream())
Duplex: 읽고 쓸 수 있는 스트림(ex. net.Socket)
Transform: 데이터를 쓰고 읽을 때 데이터를 수정하거나 변형할 수 있는 Duplex 스트림(ex.zlib.createDeflate()) // 잘 모르겠지만 압축파일과 관련이 있는 듯
CreateWriteStream 예제 코드
const fs = require('fs');
const file = fs.createWriteStream('./test.txt'); // stream 객체로 생성
for(let i = 0; i <= 1e6; i++) { // 1 * 10^6번동안 계속 write
file.write('Hello World!!\n'); // 문자열을 스트림에 씀
}
file.end(); // stream에게 더 이상 데이터를 전달하지 않겠다는 메서드.
재밌는 건 다음과 같이 코딩했을 때이다.
const fs = require('fs');
const file = fs.createWriteStream('./test.txt');
for(let i = 0; i <= 5e6; i++) {
file.write('Hello World.\n');
}
console.log("write end");
setTimeout(() => {
console.log("end");
file.end();
}, 5000);
작업관리자:
stream을 write 했을 때 메모리가 1GB 가까이 급격히 증가했던 점과 콘솔 창에 "end"가 적히는 시점부터 'test.txt' 파일 크기가 켜졌던 점, 마지막으로 end가 찍히고도 프로그램이 종료되지 않은 점이다.
차근차근 추리를 시작해보자.
file.write를 했을 때는 스트림에 데이터가 누적되어서 메모리가 급격히 증가했던 것 같다.
end가 적히는 시점, 즉 file.end()가 실행된 뒤부터 파일의 크기가 커졌던 점을 생각해보면 그때부터 file I/O 스레드가 돌면서 파일에 stream 데이터를 write 하기 시작했다는 점을 알 수 있다.
really end가 찍히고도 끝나지 않았던 것은 file I/O가 돌고있는 스레드가 아직 완료되지 않았기 때문에 프로세스가 완전히 종료되지 않은 것으로 보인다.
이 부분에 대해선 공부해야될 부분이 많은 것 같다.
메모리가 증가한 것에 대해 이것저것 찾아보다가 다음과 같은 설명을 보았다.
file.write를 할 때 highWaterMark(내부 버퍼에 저장할 최대 바이트)에 지정한 값보다 큰 버퍼를 쓰려고 할 땐, write()가 false를 리턴한다.
스트림이 빠져나가는 동안 write()를 호출하면 청크가 버퍼링되고 false가 반환되고, 현재 버퍼링된 모든 청크가 빠져나가면 drain 이벤트가 발생한다.
만약 스트림이 다 빠져나가지 않았는데 write()가 계속 발생되면 메모리가 다시 반환되지 않을 수 있다.
그래서 메모리 누수가 발생했던 것이다.
메모리 누수를 막기위해서는 다음과 같이 write()가 false일 때 스트림에 모든 데이터가 빠져나간 뒤(drain 이벤트 발동) test 함수가 다시 동작할 수 있도록 만들어야 한다.(적합한 코드는 아니다.)
const fs = require('fs');
let file = fs.createWriteStream('./test.txt');
let ok = true;
let i = 0;
function test() {
for(; i <= 2e6 && ok; i++) {
ok = file.write('Hello World.\n');
console.log(ok);
}
if(ok == false) {
ok = true;
file.once('drain', test); // drain 이벤트가 발생했을 때 일회 리스너 함수 test를 등록
console.log("call drain");
}
if(i == 2e6) {
console.log(i,": end");
file.end();
}
}
test();
CreateReadStream 예제 코드
const fs = require('fs');
const file = fs.createReadStream('./test.txt');
let chunk;
file.on('readable', () => { // stream에 데이터가 남아있는 경우 자동으로 발생하는 이벤트
// 가끔 다 읽지 않았을 때 chunk가 null일 경우가 있다.
while (null !== (chunk = file.read(13))) { // read의 buffer 크기: 13
console.log(chunk.toString(), "\nline");
}
});
file.on('end', () => { // stream에 데이터가 더 이상 남아있지 않는 경우 발생하는 이벤트
console.log('end');
});
readable 이벤트는 Readable 스트림에 읽을 데이터가 남아있을 때 발생하는 이벤트이다.
이 이벤트 안에 read() 함수를 사용해서 데이터를 읽으면 된다.
'Node.js' 카테고리의 다른 글
Node.js에서 async await를 사용해야 하는 이유 (0) | 2018.09.15 |
---|---|
chalk: 로그에 색을 입혀보자. (pm2에서의 색 나오지 않는 오류) (0) | 2018.08.07 |
nodejs fs 모듈 open 함수, stat 함수, read 함수. 파일 중간 위치에서 파일 읽기 (0) | 2018.05.21 |
[nodejs][socket.io] nodejs socket.io 사용기-2. room 사용, 현재 연결된 socket 찾기, disconnect 이벤트 (4) | 2018.05.13 |
[nodejs][socket.io] nodejs socket.io 사용기-1. socket.io란? 간단한 예제 코드 살펴보기 (0) | 2018.05.13 |