스트림의 백프레셔
데이터 처리 중에 발생하는 일반적인 문제 중 하나는 백프레셔라고 하며, 데이터 전송 중에 버퍼 뒤에 데이터가 쌓이는 것을 의미합니다. 전송의 수신 측에서 복잡한 작업을 수행하거나 어떤 이유로든 속도가 느린 경우, 들어오는 소스의 데이터가 막히는 것처럼 축적되는 경향이 있습니다.
이 문제를 해결하려면 한 소스에서 다른 소스로 데이터가 원활하게 흐르도록 보장하는 위임 시스템이 있어야 합니다. 다양한 커뮤니티에서 이 문제를 프로그램에 맞게 고유하게 해결했으며, 유닉스 파이프와 TCP 소켓이 좋은 예이며 종종 흐름 제어라고도 합니다. Node.js에서는 스트림이 채택된 솔루션입니다.
이 가이드의 목적은 백프레셔가 무엇인지, 그리고 스트림이 Node.js 소스 코드에서 이를 정확히 어떻게 처리하는지 자세히 설명하는 것입니다. 가이드의 두 번째 부분에서는 스트림을 구현할 때 애플리케이션 코드가 안전하고 최적화되도록 보장하기 위한 권장 모범 사례를 소개합니다.
Node.js에서 backpressure
, Buffer
및 EventEmitters
의 일반적인 정의와 Stream
에 대한 경험이 있다고 가정합니다. 해당 문서를 읽어보지 않았다면 API 문서를 먼저 살펴보는 것이 좋습니다. 이 가이드를 읽으면서 이해력을 넓히는 데 도움이 될 것입니다.
데이터 처리의 문제점
컴퓨터 시스템에서 데이터는 파이프, 소켓 및 신호를 통해 한 프로세스에서 다른 프로세스로 전송됩니다. Node.js에서는 Stream
이라는 유사한 메커니즘을 찾을 수 있습니다. 스트림은 훌륭합니다! Node.js에 많은 기여를 하고 거의 모든 내부 코드베이스가 해당 모듈을 활용합니다. 개발자로서 여러분도 스트림을 사용하는 것이 좋습니다!
const readline = require('node:readline');
const rl = readline.createInterface({
output: process.stdout,
input: process.stdin,
});
rl.question('스트림을 사용해야 하는 이유는 무엇입니까? ', answer => {
console.log(`아마도 ${answer}일 수도 있고, 스트림이 훌륭하기 때문일 수도 있습니다!`);
});
rl.close();
스트림을 통해 구현된 백프레셔 메커니즘이 훌륭한 최적화인 이유를 보여주는 좋은 예는 Node.js의 스트림 구현에서 내부 시스템 도구를 비교하는 것입니다.
한 시나리오에서는 큰 파일(약 -9GB)을 가져와서 익숙한 zip(1)
도구를 사용하여 압축합니다.
zip The.Matrix.1080p.mkv
완료하는 데 몇 분이 걸리지만 다른 셸에서 다른 압축 도구인 gzip(1)
을 래핑하는 Node.js 모듈 zlib
를 사용하는 스크립트를 실행할 수 있습니다.
const gzip = require('node:zlib').createGzip();
const fs = require('node:fs');
const inp = fs.createReadStream('The.Matrix.1080p.mkv');
const out = fs.createWriteStream('The.Matrix.1080p.mkv.gz');
inp.pipe(gzip).pipe(out);
결과를 테스트하려면 각 압축 파일을 열어보십시오. zip(1)
도구로 압축된 파일은 파일이 손상되었다는 알림을 표시하지만 Stream으로 완료된 압축은 오류 없이 압축 해제됩니다.
참고
이 예에서는 .pipe()
를 사용하여 한쪽 끝에서 다른 쪽 끝으로 데이터 소스를 가져옵니다. 그러나 적절한 오류 처리기가 연결되어 있지 않습니다. 데이터 청크가 제대로 수신되지 않으면 Readable 소스 또는 gzip
스트림이 소멸되지 않습니다. pump
는 파이프라인 중 하나가 실패하거나 닫히면 파이프라인의 모든 스트림을 적절하게 소멸시키는 유틸리티 도구이며 이 경우에 필수적입니다!
pump
는 Node.js 8.x 이하 버전에서만 필요합니다. Node.js 10.x 이상 버전에서는 pump
를 대체하기 위해 pipeline
이 도입되었습니다. 이것은 오류를 전달하고 파이프라인이 완료되면 적절하게 정리하고 콜백을 제공하는 스트림 간에 파이프하는 모듈 메서드입니다.
다음은 파이프라인을 사용하는 예입니다.
const { pipeline } = require('node:stream');
const fs = require('node:fs');
const zlib = require('node:zlib');
// 파이프라인 API를 사용하여 일련의 스트림을 쉽게 파이프할 수 있습니다.
// 함께 연결하고 파이프라인이 완전히 완료되면 알림을 받습니다.
// 잠재적으로 거대한 비디오 파일을 효율적으로 gzip하는 파이프라인:
pipeline(
fs.createReadStream('The.Matrix.1080p.mkv'),
zlib.createGzip(),
fs.createWriteStream('The.Matrix.1080p.mkv.gz'),
err => {
if (err) {
console.error('파이프라인 실패', err);
} else {
console.log('파이프라인 성공');
}
}
);
stream/promises
모듈을 사용하여 async / await
로 파이프라인을 사용할 수도 있습니다.
const { pipeline } = require('node:stream/promises');
const fs = require('node:fs');
const zlib = require('node:zlib');
async function run() {
try {
await pipeline(
fs.createReadStream('The.Matrix.1080p.mkv'),
zlib.createGzip(),
fs.createWriteStream('The.Matrix.1080p.mkv.gz')
);
console.log('파이프라인 성공');
} catch (err) {
console.error('파이프라인 실패', err);
}
}
너무 많은 데이터, 너무 빠른 속도
Readable
스트림이 Writable
스트림에 데이터를 너무 빨리, 소비자가 처리할 수 있는 것보다 훨씬 더 많이 제공하는 경우가 있습니다!
이러한 경우 소비자는 나중에 소비하기 위해 모든 데이터 청크를 대기열에 넣기 시작합니다. 쓰기 대기열이 점점 길어지고, 이로 인해 전체 프로세스가 완료될 때까지 더 많은 데이터를 메모리에 보관해야 합니다.
디스크에 쓰는 것이 디스크에서 읽는 것보다 훨씬 느리므로 파일을 압축하여 하드 디스크에 쓰려고 할 때 쓰기 디스크가 읽기 속도를 따라가지 못해 역압이 발생합니다.
// 스트림은 은밀히 "이봐, 이봐! 잠깐만, 너무 많아!"라고 말하고 있습니다.
// 데이터가 들어오는 데이터 흐름에 맞춰 쓰려고 할 때
// 데이터 버퍼의 읽기 측에 데이터가 쌓이기 시작합니다.
inp.pipe(gzip).pipe(outputFile);
이것이 역압 메커니즘이 중요한 이유입니다. 역압 시스템이 없으면 프로세스가 시스템 메모리를 모두 사용하므로 다른 프로세스의 속도를 늦추고 완료될 때까지 시스템의 많은 부분을 독점합니다.
이로 인해 다음과 같은 결과가 발생합니다.
- 다른 모든 현재 프로세스의 속도 저하
- 매우 과로한 가비지 수집기
- 메모리 고갈
다음 예에서는 .write()
함수의 반환 값을 제거하고 true
로 변경하여 Node.js 코어에서 역압 지원을 효과적으로 비활성화합니다. 'modified'
바이너리에 대한 모든 참조에서 return ret;
줄 없이, 대신 return true;
로 대체된 노드 바이너리를 실행하는 것에 대해 이야기하고 있습니다.
가비지 수집에 대한 과도한 드래그
빠른 벤치마크를 살펴보겠습니다. 위와 동일한 예제를 사용하여 몇 번의 시간 테스트를 실행하여 두 바이너리의 중간 시간을 얻었습니다.
시험 (#) | `node` 바이너리 (ms) | 수정된 `node` 바이너리 (ms)
=================================================================
1 | 56924 | 55011
2 | 52686 | 55869
3 | 59479 | 54043
4 | 54473 | 55229
5 | 52933 | 59723
=================================================================
평균 시간: | 55299 | 55975
둘 다 실행하는 데 약 1분이 걸리므로 차이가 거의 없지만, 의심이 맞는지 확인하기 위해 자세히 살펴보겠습니다. Linux 도구 dtrace
를 사용하여 V8 가비지 수집기에서 무슨 일이 일어나고 있는지 평가합니다.
GC(가비지 수집기) 측정 시간은 가비지 수집기가 수행하는 단일 스윕의 전체 주기를 나타냅니다.
대략적인 시간 (ms) | GC (ms) | 수정된 GC (ms)
=================================================
0 | 0 | 0
1 | 0 | 0
40 | 0 | 2
170 | 3 | 1
300 | 3 | 1
* * *
* * *
* * *
39000 | 6 | 26
42000 | 6 | 21
47000 | 5 | 32
50000 | 8 | 28
54000 | 6 | 35
두 프로세스가 동일하게 시작되고 동일한 속도로 GC를 작동시키는 것처럼 보이지만, 몇 초 후에 제대로 작동하는 역압 시스템이 있으면 데이터 전송이 끝날 때까지 4-8밀리초의 일정한 간격으로 GC 부하를 분산시키는 것이 분명해집니다.
그러나 역압 시스템이 없으면 V8 가비지 수집이 지연되기 시작합니다. 일반 바이너리는 1분 안에 약 75번 GC를 호출하는 반면, 수정된 바이너리는 36번만 호출합니다.
이것은 증가하는 메모리 사용량으로 인해 축적되는 느리고 점진적인 부채입니다. 데이터가 전송될 때 역압 시스템이 없으면 각 청크 전송에 더 많은 메모리가 사용됩니다.
할당되는 메모리가 많을수록 GC는 한 번의 스윕에서 더 많은 것을 처리해야 합니다. 스윕이 클수록 GC는 무엇을 해제할 수 있는지 결정해야 하고, 더 큰 메모리 공간에서 분리된 포인터를 검색하는 데 더 많은 컴퓨팅 성능이 필요합니다.
메모리 고갈
각 바이너리의 메모리 소비량을 확인하기 위해 각 프로세스를 /usr/bin/time -lp sudo ./node ./backpressure-example/zlib.js
로 개별적으로 측정했습니다.
다음은 일반 바이너리의 출력입니다.
.write()의 반환 값을 준수하는 경우
=============================================
실제 58.88
사용자 56.79
시스템 8.79
87810048 최대 상주 세트 크기
0 평균 공유 메모리 크기
0 평균 비공유 데이터 크기
0 평균 비공유 스택 크기
19427 페이지 재확보
3134 페이지 폴트
0 스왑
5 블록 입력 작업
194 블록 출력 작업
0 메시지 전송
0 메시지 수신
1 신호 수신
12 자발적 컨텍스트 전환
666037 비자발적 컨텍스트 전환
가상 메모리가 차지하는 최대 바이트 크기는 약 87.81MB입니다.
이제 .write()
함수의 반환 값을 변경하면 다음과 같은 결과를 얻습니다.
.write()의 반환 값을 준수하지 않는 경우:
==================================================
실제 54.48
사용자 53.15
시스템 7.43
1524965376 최대 상주 세트 크기
0 평균 공유 메모리 크기
0 평균 비공유 데이터 크기
0 평균 비공유 스택 크기
373617 페이지 재확보
3139 페이지 폴트
0 스왑
18 블록 입력 작업
199 블록 출력 작업
0 메시지 전송
0 메시지 수신
1 신호 수신
25 자발적 컨텍스트 전환
629566 비자발적 컨텍스트 전환
가상 메모리가 차지하는 최대 바이트 크기는 약 1.52GB입니다.
백프레셔를 위임하기 위한 스트림이 없으면 할당되는 메모리 공간이 훨씬 더 커집니다. 동일한 프로세스 간에 엄청난 차이가 있습니다!
이 실험은 Node.js의 백프레셔 메커니즘이 컴퓨팅 시스템에 얼마나 최적화되고 비용 효율적인지 보여줍니다. 이제 작동 방식을 자세히 살펴보겠습니다!
백프레셔는 이러한 문제를 어떻게 해결합니까?
한 프로세스에서 다른 프로세스로 데이터를 전송하는 데는 여러 가지 기능이 있습니다. Node.js에는 .pipe()
라는 내장 함수가 있습니다. 다른 패키지도 사용할 수 있습니다! 궁극적으로 이 프로세스의 기본 수준에는 데이터 소스와 소비자라는 두 개의 개별 구성 요소가 있습니다.
.pipe()
가 소스에서 호출되면 소비자에게 전송할 데이터가 있음을 알립니다. 파이프 함수는 이벤트 트리거에 적절한 백프레셔 클로저를 설정하는 데 도움이 됩니다.
Node.js에서 소스는 Readable
스트림이고 소비자는 Writable
스트림입니다(둘 다 Duplex 또는 Transform 스트림으로 상호 교환할 수 있지만 이 가이드의 범위를 벗어남).
백프레셔가 트리거되는 시점은 정확히 Writable
의 .write()
함수의 반환 값으로 좁힐 수 있습니다. 이 반환 값은 물론 몇 가지 조건에 따라 결정됩니다.
데이터 버퍼가 highwaterMark
를 초과했거나 쓰기 큐가 현재 사용 중인 모든 시나리오에서 .write()
는 false
를 반환합니다.
false
값이 반환되면 백프레셔 시스템이 작동합니다. 수신 Readable
스트림이 데이터를 보내는 것을 일시 중지하고 소비자가 다시 준비될 때까지 기다립니다. 데이터 버퍼가 비워지면 'drain'
이벤트가 발생하고 수신 데이터 흐름이 재개됩니다.
큐가 완료되면 백프레셔는 데이터를 다시 보낼 수 있도록 합니다. 사용 중인 메모리 공간이 스스로 비워지고 다음 데이터 배치를 준비합니다.
이를 통해 .pipe()
함수에 대해 주어진 시간에 고정된 양의 메모리를 효과적으로 사용할 수 있습니다. 메모리 누수나 무한 버퍼링이 없으며 가비지 수집기는 메모리의 한 영역만 처리하면 됩니다!
그렇다면 백프레셔가 그렇게 중요한데 왜 들어본 적이 없을까요? 그 이유는 간단합니다. Node.js가 이 모든 것을 자동으로 처리해 주기 때문입니다.
정말 대단하죠! 하지만 사용자 지정 스트림을 구현하는 방법을 이해하려고 할 때는 그다지 좋지 않습니다.
NOTE
대부분의 시스템에서 버퍼가 가득 찼을 때를 결정하는 바이트 크기가 있습니다(이는 시스템마다 다릅니다). Node.js를 사용하면 사용자 지정 highWaterMark
를 설정할 수 있지만 일반적으로 기본값은 16kb(objectMode 스트림의 경우 16384 또는 16)로 설정됩니다. 해당 값을 높이고 싶은 경우에 그렇게 하되 주의하십시오!
.pipe()
의 수명 주기
백프레셔를 더 잘 이해하기 위해 Readable
스트림이 Writable
스트림으로 파이프될 때의 수명 주기에 대한 흐름 차트가 있습니다.
+===================+
x--> 파이핑 함수 +--> src.pipe(dest) |
x 는 .pipe 메서드 중 |===================|
x 에 설정됩니다. | 이벤트 콜백 |
+===============+ x |-------------------|
| 데이터 | x 데이터 흐름 외부에 | .on('close', cb) |
+=======+=======+ x 존재하지만, 중요한 | .on('data', cb) |
| x 이벤트와 해당 콜백을 | .on('drain', cb) |
| x 연결합니다. | .on('unpipe', cb) |
+---------v---------+ x | .on('error', cb) |
| Readable 스트림 +----+ | .on('finish', cb) |
+-^-------^-------^-+ | | .on('end', cb) |
^ | ^ | +-------------------+
| | | |
| ^ | |
^ ^ ^ | +-------------------+ +=================+
^ | ^ +----> Writable 스트림 +---------> .write(chunk) |
| | | +-------------------+ +=======+=========+
| ^ | |
| | | +------------------v---------+
^ | +-> if (!chunk) | 이 chunk가 너무 큰가? |
^ | | emit .end(); | 큐가 사용 중인가? |
| | +-> else +-------+----------------+---+
| ^ | emit .write(); | |
| ^ ^ +--v---+ +---v---+
| | ^-----------------------------------< 아니오 | | 예 |
^ | +------+ +---v---+
^ | |
| ^ emit .pause(); +=================+ |
| ^---------------^-----------------------+ false 반환; <-----+---+
| +=================+ |
| |
^ 큐가 비어 있을 때 +============+ |
^------------^-----------------------< 버퍼링 | |
| |============| |
+> emit .drain(); | ^버퍼^ | |
+> emit .resume(); +------------+ |
| ^버퍼^ | |
+------------+ 큐에 chunk 추가 |
| <---^---------------------<
+============+
NOTE
데이터를 조작하기 위해 여러 스트림을 함께 연결하는 파이프라인을 설정하는 경우 Transform 스트림을 구현할 가능성이 높습니다.
이 경우 Readable
스트림의 출력은 Transform
에 들어가 Writable
로 파이프됩니다.
Readable.pipe(Transformable).pipe(Writable);
백프레셔가 자동으로 적용되지만 Transform
스트림의 들어오는 highwaterMark
와 나가는 highwaterMark
를 모두 조작할 수 있으며 백프레셔 시스템에 영향을 미칩니다.
백프레셔 지침
Node.js v0.10 이후로 Stream 클래스는 해당 함수의 밑줄 버전(._read()
및 ._write()
)을 사용하여 .read()
또는 .write()
의 동작을 수정하는 기능을 제공했습니다.
Readable 스트림 구현 및 Writable 스트림 구현에 대해 문서화된 지침이 있습니다. 이들을 읽었다고 가정하고 다음 섹션에서는 좀 더 자세히 살펴보겠습니다.
사용자 지정 스트림 구현 시 준수해야 할 규칙
스트림의 황금률은 항상 백프레셔를 존중하는 것입니다. 모범 사례로 간주되는 것은 모순되지 않는 관행입니다. 내부 백프레셔 지원과 충돌하는 동작을 피하도록 주의하면 좋은 관행을 따르고 있다고 확신할 수 있습니다.
일반적으로,
- 요청받지 않은 경우
.push()
하지 마십시오. - false를 반환한 후에는
.write()
를 호출하지 말고 대신 'drain'을 기다리십시오. - 스트림은 Node.js 버전과 사용하는 라이브러리에 따라 변경됩니다. 주의하고 테스트하십시오.
NOTE
3번 사항과 관련하여 브라우저 스트림을 구축하는 데 매우 유용한 패키지는 readable-stream
입니다. Rodd Vagg는 이 라이브러리의 유틸리티를 설명하는 훌륭한 블로그 게시물을 작성했습니다. 간단히 말해서 Readable 스트림에 대한 일종의 자동화된 정상적인 저하를 제공하고 이전 버전의 브라우저 및 Node.js를 지원합니다.
Readable 스트림에 특정한 규칙
지금까지 .write()
가 백프레셔에 미치는 영향과 Writable 스트림에 많은 초점을 맞췄습니다. Node.js의 기능으로 인해 데이터는 기술적으로 Readable에서 Writable로 다운스트림으로 흐르고 있습니다. 그러나 데이터, 물질 또는 에너지의 전송에서 관찰할 수 있듯이 소스는 목적지만큼 중요하며 Readable 스트림은 백프레셔가 처리되는 방식에 매우 중요합니다.
이러한 프로세스는 모두 효과적으로 통신하기 위해 서로 의존합니다. Readable이 Writable 스트림이 데이터 전송을 중단하도록 요청하는 시기를 무시하면 .write()
의 반환 값이 올바르지 않은 경우만큼 문제가 될 수 있습니다.
따라서 .write()
반환 값을 존중하는 것 외에도 ._read()
메서드에서 사용되는 .push()
의 반환 값도 존중해야 합니다. .push()
가 false 값을 반환하면 스트림은 소스에서 읽기를 중단합니다. 그렇지 않으면 일시 중지 없이 계속됩니다.
다음은 .push()
를 사용하는 잘못된 관행의 예입니다.
// 이것은 푸시에서 반환된 값을 완전히 무시하므로 문제가 됩니다.
// 대상 스트림에서 백프레셔에 대한 신호일 수 있습니다!
class MyReadable extends Readable {
_read(size) {
let chunk;
while (null == (chunk = getNextChunk())) {
this.push(chunk);
}
}
}
또한 사용자 지정 스트림 외부에서는 백프레셔를 무시하는 데 함정이 있습니다. 좋은 관행의 이 반대 예에서는 애플리케이션 코드가 사용 가능한 경우('data'
이벤트로 신호됨)마다 데이터를 강제로 전송합니다.
// 이것은 Node.js가 설정한 백프레셔 메커니즘을 무시하고,
// 대상 스트림이 준비되었는지 여부에 관계없이
// 데이터를 무조건적으로 밀어냅니다.
readable.on('data', data => writable.write(data));
다음은 Readable 스트림과 함께 .push()
를 사용하는 예입니다.
const { Readable } = require('node:stream');
// 사용자 지정 Readable 스트림 만들기
const myReadableStream = new Readable({
objectMode: true,
read(size) {
// 스트림에 일부 데이터 푸시
this.push({ message: 'Hello, world!' });
this.push(null); // 스트림의 끝 표시
},
});
// 스트림 소비
myReadableStream.on('data', chunk => {
console.log(chunk);
});
// 출력:
// { message: 'Hello, world!' }
쓰기 가능한 스트림에 특정한 규칙
.write()
는 몇 가지 조건에 따라 true 또는 false를 반환할 수 있다는 것을 기억하십시오. 운 좋게도 자체 쓰기 가능 스트림을 구축할 때 스트림 상태 머신은 콜백을 처리하고 백프레셔를 처리하고 데이터 흐름을 최적화할 시기를 결정합니다. 그러나 쓰기 가능 항목을 직접 사용하려는 경우 .write()
반환 값을 존중하고 다음 조건에 세심한 주의를 기울여야 합니다.
- 쓰기 대기열이 사용 중이면
.write()
는 false를 반환합니다. - 데이터 청크가 너무 크면
.write()
는 false를 반환합니다(제한은 변수 highWaterMark로 표시됨).
이 예제에서는 .push()
를 사용하여 단일 객체를 스트림에 푸시하는 사용자 지정 읽기 가능 스트림을 만듭니다. 스트림이 데이터를 소비할 준비가 되면 ._read()
메서드가 호출되고 이 경우 즉시 스트림에 일부 데이터를 푸시하고 null
을 푸시하여 스트림의 끝을 표시합니다.
const stream = require('stream');
class MyReadable extends stream.Readable {
constructor() {
super();
}
_read() {
const data = { message: 'Hello, world!' };
this.push(data);
this.push(null);
}
}
const readableStream = new MyReadable();
readableStream.pipe(process.stdout);
그런 다음 'data' 이벤트를 수신하고 스트림에 푸시되는 각 데이터 청크를 로깅하여 스트림을 소비합니다. 이 경우 스트림에 단일 데이터 청크만 푸시하므로 하나의 로그 메시지만 표시됩니다.
쓰기 가능한 스트림에 특정한 규칙
.write()
는 몇 가지 조건에 따라 true 또는 false를 반환할 수 있다는 것을 기억하십시오. 운 좋게도 자체 쓰기 가능 스트림을 구축할 때 스트림 상태 머신은 콜백을 처리하고 백프레셔를 처리하고 데이터 흐름을 최적화할 시기를 결정합니다.
그러나 쓰기 가능 항목을 직접 사용하려는 경우 .write()
반환 값을 존중하고 다음 조건에 세심한 주의를 기울여야 합니다.
- 쓰기 대기열이 사용 중이면
.write()
는 false를 반환합니다. - 데이터 청크가 너무 크면
.write()
는 false를 반환합니다(제한은 변수 highWaterMark로 표시됨).
class MyWritable extends Writable {
// 이 쓰기 가능 항목은 JavaScript 콜백의 비동기 특성 때문에 유효하지 않습니다.
// 마지막 이전의 각 콜백에 대한 return 문이 없으면
// 여러 콜백이 호출될 가능성이 큽니다.
write(chunk, encoding, callback) {
if (chunk.toString().indexOf('a') >= 0) callback();
else if (chunk.toString().indexOf('b') >= 0) callback();
callback();
}
}
._writev()
를 구현할 때 주의해야 할 사항도 있습니다. 함수는 .cork()
와 결합되지만 작성할 때 흔히 발생하는 실수가 있습니다.
// 여기에 .uncork()를 두 번 사용하면 C++ 레이어에서 두 번 호출되어
// 코르크/엉코르크 기술이 쓸모없게 됩니다.
ws.cork();
ws.write('hello ');
ws.write('world ');
ws.uncork();
ws.cork();
ws.write('from ');
ws.write('Matteo');
ws.uncork();
// 이 작업을 수행하는 올바른 방법은 다음 이벤트 루프에서 발생하는 process.nextTick()을 활용하는 것입니다.
ws.cork();
ws.write('hello ');
ws.write('world ');
process.nextTick(doUncork, ws);
ws.cork();
ws.write('from ');
ws.write('Matteo');
process.nextTick(doUncork, ws);
// 전역 함수로.
function doUncork(stream) {
stream.uncork();
}
.cork()
는 원하는 만큼 여러 번 호출할 수 있지만 다시 흐르게 하려면 .uncork()
를 같은 횟수만큼 호출하도록 주의해야 합니다.
결론
스트림은 Node.js에서 자주 사용되는 모듈입니다. 이는 내부 구조에 중요하며, 개발자가 Node.js 모듈 생태계를 확장하고 연결하는 데에도 중요합니다.
이제 백프레셔를 염두에 두고 Writable
및 Readable
스트림을 직접 문제 해결하고 안전하게 코딩하고, 동료 및 친구와 지식을 공유할 수 있기를 바랍니다.
Node.js로 애플리케이션을 구축할 때 스트리밍 기능을 개선하고 강화하는 데 도움이 되는 다른 API 기능에 대한 자세한 내용은 Stream
에서 확인하십시오.