Coroutine Cold Stream vs Hot Stream
코루틴에서 Scope와 몇 가지 연산자 등의 학습을 거쳐 flow와 channel이라는 것을 학습하게 되었는데 이 2개의 정확한 차이점은 무엇일까? stream은 정확히 무엇인가?.. 둘 다 무언가 값을 방출하는 것까지는 알겠는데 말이다. 여러 포스팅을 살펴보면 flow는 cold 스트림이고 channel 은 hot 스트림이라고 말한다.
그렇다면 여기서 말하는 스트림이라는 것은 대체 뭘까?
flow 와 channel에 대해서 잘 이해하기 위해서는 이 “stream”이라는 것부터 알아야 될 듯해 작성하게 되었습니다.
1. Stream
우리는 hot, cold stream을 정의하는데 많은 혼란을 겪는다. 우리는 Flow는 Cold Stream, Channel 은 Hot Stream이라고 말하지만 이 두 구성요소를 구분하는 선이 그렇게 명확하지 않음을 알 수 있다.
2. 주요 차이점
스트림이 cold 인지 hot 인지를 정의하는데 도움이 되는 3개의 차이점은 다음과 같다
- 1. Where the data is produced (데이터가 생성되는 위치)
- 데이터는 스트림 내부 또는 외부에서 생성될 수 있다. 즉, 스트림을 사용하든 사용하지 않든 데이터를 존재할 수도 존재하지 않을 수도 있다.
- 2. How many receives, at the same time, can get the data (동시에 얼마나 많은 리시버가 데이터를 얻을 수 있는가, 유니캐스트, 멀티캐스트 와도 관련이 있다.)
- 3. Laziness (게으름)
- 스트림이 값을 방출하기 시작할 때 곧바로 시작할 수 있는가 아니면 내가 요청할 때 시작할 것인가
이 3가지 중에서도 특히 마지막 방식이 cold, hot 스트림을 구분 짓는데 가장 편한 방법이 될 것이다
3. Cold Stream
- Cold 스트림에서는 모든 데이터가 스트림 내부에서 생성된다고 말할 수 있다.
- Cold 스크림에는 구독자가 오로지 1명뿐이며, 해당 구독자에게만 값을 내보낸다. 또한 해당 구독자는 스트림을 초기화하는 구독자이다. 따라서 Cold 스트림에는 unicast 메커니즘이 있다고 볼 수 있다.
- 스크림이 수신하는(receive) 각각의 새로운 구독에 대해서는 해당 코드의 새로운 실행이 트리거 되며 이것은 이전 구독과는 독립적이다. 즉 동일한 스트림의 여러 인스턴스를 가질 수 있으며 이들은 각각 독립적이다.
- 일부 값을 생성하기 위해 실행될 코드 라인은 호출하는 주체에 따라 달라지지 않고, 모든 구독자에 대해 동일하다는 점을 유의해야 한다
- 마지막으로 Cold Stream 은 Lazy Stream (게으른 스트림)이라고 말할 수 있다. 누군가가 구독할 때만 값의 방출이 시작되기 때문이다. 다시 말해서 누군가가 “내가 듣고 있으니까 방출해 줘”라고 말해줘야만 시작한다는 것이다.
Cold 스트림은 CD로 비유할 수 있다
- 각 청취자 당 CD 1장 -> 구독자 1명당 스트림 1개
- 청취차가 재생을 중단하면 CD가 멈춘다 -> 구독자가 연결을 끊으면 스트림은 종료된다
- 음악을 듣기 시작하는 시점에 관계없이, 음악은 모든 청취자에게 동일하다 -> 방출되는 데이터는 구독하는 시점에 관계없이 모든 구독자에게 동일하다
4. Hot Stream
- Hot Stream에서는 모든 데이터가 스트림 외부에서 생성된다고 말할 수 있다. 이 데이터는 스트림 없이 존재할 수 있으며, 일반적으로 외부 컴포넌트에서 가져온다.
- Hot 스트림은 0개 이상의 구독자를 가질 수 있으며 동시에 모든 구독자에게 값을 내보낸다. 따라서 Hot 스트림에는 multicast 메커니즘이 있다고 볼 수 있다.
- Hot 스트림은 모든 구독자에 대해 하나의 방출만이 있으며 초기화하지 않고 연결하고 듣기 시작할 수 있다. 연결하는 순간에 따라서 다른 데이터를 받을 수 있다. 만약에 아무도 듣지 않는다면? 아무것도 일어나지 않는다. 데이터가 손실되지만 아무도 신경 쓰지 않는다.
- 마지막으로 Hot 스트림은 Eager Stream (열망? 열정? 스트림)이라고 말할 수 있다. 구독자가 있든 없든 상관없이 값의 방출은 항상 시작된다. Cold 스트림과 달리 Hot 스트림은 누군가가 “방출되고 있는 스트림을 들어봐야지.”라고 말할 뿐이다.
Hot 스트림은 라디오 방송국에 비유할 수 있다.
- 모든 청취자를 위한 1개의 방송국이 있다 -> 모든 구독자를 위한 1개의 스트림이 있다
- 아무도 듣지 않아도 라디오는 계속해서 콘텐츠를 생성한다 -> 구독자가 없어도 스트림은 데이터를 방출한다
- 라디오를 듣는 순간에 따라 청취자가 듣는 음악은 달라진다 -> 구독자가 구독을 시작하는 순간에 따라 받는 데이터는 달라진다
5. In-between streams
일부 Cold 스트림은 Cold에서 언급된 정의만큼 ‘차갑지’ 않다. 이를 통해 구독자가 수신하는 데이터는 순간에 따라서 달라질 수도 있다. 어떠한 경우 Cold 스크림은 Hot 스트림의 기능과 동일할 수도 있다.
Hot Observable VS Cold Observable
데이터가 Observable 자체에 의해 생성되면 이를 콜드 Observable이라고 부릅니다. 데이터가 Observable 외부에서 생성되면 이를 핫 Observable이라고 부릅니다.
COLD는 Observable이 생산자를 생성하는 경우입니다.
// COLD
var cold = new Observable((observer) => {
var producer = new Producer();
// 관찰자가 여기서 프로듀서의 말을 듣도록 합니다
});
HOT은 관찰 가능 항목이 생산자 위에 닫힐 때입니다.
// HOT
var producer = new Producer();
var hot = new Observable((observer) => {
// 관찰자가 여기서 생산자의 말을 듣도록 합니다
});
Observable은 단지 함수일 뿐입니다!
Observable은 관찰자를 생산자와 연결하는 기능이 전부입니다. 반드시 생산자를 설정할 필요는 없으며 생산자의 말을 듣기 위해 관찰자를 설정하고 일반적으로 해당 청취자를 제거하기 위한 분해 메커니즘을 반환합니다. 구독 행위는 Observable을 함수처럼 "호출"하고 이를 관찰자에게 전달하는 행위입니다.
'Producer'란 무엇인가요?
생산자는 관찰 가능한 값의 소스이다. 웹 소켓일 수도 있고, DOM 이벤트일 수도 있고, 반복자일 수도 있고, 배열을 반복하는 것일 수도 있다. 기본적으로 프로듀서는 값을 가져와 observer.next(value)에 전달하는 데 사용하는 모든 것이다.
Cold Observable: 내부 생성된 생산자
구독 중에 기본 생산자가 생성되고 활성화되면 관찰 가능 항목은 "Cold"입니다. 즉, Observable이 함수인 경우 해당 함수를 호출하여 생산자가 생성되고 활성화됩니다.
- 생산자를 창조한다
- 생산자를 활성화합니다
- 프로듀서를 감지하기 시작합니다
- 유니캐스트
아래 예제는 Observable을 구독할 때 호출되는 구독자 함수 내부에서 WebSocket을 생성하고 수신하기 때문에 "Cold"입니다.
const source = new Observable((observer) => {
const 소켓 = new WebSocket('ws://someurl');
소켓.addEventListener('message', (e) => 관찰자.next(e));
return ( ) => 소켓.close();
});
따라서 위의 'source'를 구독하는 모든 항목은 자체 WebSocket 인스턴스를 갖게 되며, 구독을 취소하면 해당 소켓을 'close()'합니다. 이는 생산자가 한 명의 관찰자에게만 보낼 수 있기 때문에 우리 소스가 실제로 유니캐스트라는 것을 의미합니다.
Hot Observable: 외부에서 생성된 생산자
기본 생산자가 구독 외부에서 생성되거나 활성화된 경우 Observable은 "Hot"합니다.
- 생산자에 대한 언급을 공유합니다
- 프로듀서의 말을 듣기 시작합니다
- 멀티캐스트
WebSocket 생성을 관찰 가능 외부로 이동하면 "핫"해집니다.
const socket = new WebSocket('ws://someurl');const source = new Observable((observer) => {
socket.addEventListener('message', (e) =>observer.next(e));
});
이제 source를 구독하는 모든 항목은 동일한 WebSocket 인스턴스를 공유하므로 모든 리스너들에게 멀티캐스트 됩니다. 하지만 오류, 완료, 구독 취소 등의 작업으로 인해 더 이상 소켓이 닫히지 않습니다. 그래서 우리가 정말로 원하는 것은 "cold"를 관찰 가능하게 "hot"으로 만드는 것입니다.
왜 Hot Observable을 쓰는가?
Cold Observable을 보여주는 위의 첫 번째 예에서 모든 Cold Observable을 항상 보유하는 데 몇 가지 문제가 있을 수 있음을 알 수 있습니다. 우선, 웹 소켓 연결과 같은 일부 희소한 리소스를 생성하는 Observable을 두 번 이상 구독하는 경우 해당 웹 소켓 연결을 반복해서 생성하고 싶지 않을 것입니다. Observable에 대한 하나 이상의 구독을 생성하는 것은 쉽습니다.
콜드 옵저버블
Observable 내부에서 데이터가 생성되면 Observable을 "cold"라고 부릅니다. Observable은 무언가가 구독할 때만 값을 실행한다는 점에서 게으릅니다. 각 구독자에 대해 Observable은 새로운 실행을 시작하므로 데이터가 공유되지 않습니다. Observable이 서로 다른 많은 값을 생성하는 경우 거의 동일하게 구독하는 두 Observable이 두 개의 다른 값을 받는 일이 발생할 수 있습니다. 우리는 이 동작을 "유니캐스팅"이라고 부릅니다.
import * as Rx from "rxjs";
const observable = Rx.Observable.create((observer) => {
observer.next(Math.random());
});
// subscription 1
observable.subscribe((data) => {
console.log(data); // 0.24957144215097515 (random number)
});
// subscription 2
observable.subscribe((data) => {
console.log(data); // 0.004617340049055896 (random number)
});
보시다시피 데이터는 Observable 내부에서 생성되어 차갑게 만듭니다. 동시에 구독하는 두 개의 구독이 있습니다. Observable은 모든 구독자에 대해 새로운 실행을 수행하고 Observable은 난수를 생성하므로 구독자가 수신하는 데이터는 다릅니다.
해당 코드가 항상 바람직한 것은 아닙니다. 다음과 같이 변경해 보겠습니다.
import * as Rx from "rxjs";
const random = Math.random()
const observable = Rx.Observable.create((observer) => {
observer.next(random);
});
// subscription 1
observable.subscribe((data) => {
console.log(data); // 0.11208711666917925 (random number)
});
// subscription 2
observable.subscribe((data) => {
console.log(data); // 0.11208711666917925 (random number)
});
위 코드에서 한 일은 데이터 생산자를 Observable 밖으로 옮기는 것입니다. 여전히 두 명의 구독자가 있고 Observable은 여전히 두 번 실행되지만 데이터는 Observable 외부에서 생성되므로 구독은 동일한 데이터를 받게 됩니다.
방금 Cold Observable을 Hot Observable로 바꾸었기 때문에 해당 코드를 살펴보고 도움이 되길 바랍니다.
핫 옵저버블
Observable 내부에서 데이터가 생성되면 Observable은 콜드(cold) 상태이고 데이터가 Observable 외부에서 생성되면 Observable은 핫(hot) 상태입니다. 방금 살펴본 것처럼 Hot Observable은 여러 구독자 간에 데이터를 공유할 수 있습니다. 우리는 이 동작을 "멀티캐스팅"이라고 부릅니다.
난수를 생성하는 것보다 좋은 사용 예제는 DOM 이벤트입니다. 클릭 동작을 추적하고 여러 구독자가 좌표를 사용하여 작업을 수행한다고 가정해 보겠습니다.
import * as Rx from "rxjs";
const observable = Rx.Observable.fromEvent(document, 'click');
// subscription 1
observable.subscribe((event) => {
console.log(event.clientX); // x position of click
});
// subscription 2
observable.subscribe((event) => {
console.log(event.clientY); // y position of click
});
데이터는 Observable 자체 외부에서 생성되며 구독자 유무에 관계없이 데이터가 생성됩니다. 단 데이터가 생성될 때 구독자가 없으면 데이터는 손실됩니다.
Cold is better than hot?
여기까지 Stream에 대해 학습해봤습니다. 마지막 팁으로 다음과 같은 경우를 제외하면 Cold Observable는 일반적으로 사용하기 좋습니다.
- 여러 가입자가 동일한 데이터를 얻을 수 있는지 확인하려고 할 때
- 각 Observable 실행에서 무언가의 새로운 인스턴스를 생성하는 경우, 웹소켓 연결을 가정해 보겠습니다. 각 구독자에 대해 새 연결을 생성하는 대신 모든 구독자에게 공유하면 됩니다. 연결의 인스턴스화를 Observable 외부로 이동하면 hot으로 변경됩니다.
출처
https://medium.com/@apfhdznzl/flow%EC%99%80-channel-cold-stream%EA%B3%BC-hot-stream-c42c64cf4996
'CS' 카테고리의 다른 글
Connection Pool 테스트와 고찰(2) (0) | 2023.11.07 |
---|---|
Connection Pool 테스트와 고찰(1) (0) | 2023.11.07 |
API 헬스체크 (0) | 2023.08.17 |
Connection pool (0) | 2022.12.06 |
WebFlux (0) | 2022.11.06 |