최근 Reactive Programming 에 조금씩 관심을 기울여서 보고 있는데 강의를 보다가 하나 궁금한점이 생겼다.
아래 코드를 보면 Flux, Mono Type 으로 Return 하거나 RequestBody 에서 부터 받아서 사용하는데 이게 도대체 무슨차이지? 라는 생각이 들었다.
Kotlin Coroutine 이나 JavaScript 의 Promise Model 을 공부하면서 느낀건 결국 Reactive 세상에서 가장 중요한건 아래의 두가지라는 생각이 들었다.
- Thread 의 Blocking 을 최소화 하는 코드를 작성해라.
- Lazily 하게 값이 evaluate 되어야 한다.
위의 두가지가 제일 중요하기에 Fully Non-Blocking 이라고 불리는 Web-Flux 에서 Response 에 Mono 와 Flux 를 넘기는 이유가 있을 것이라고 생각했다. 일단 코드로 보는게 제일 편하니까 코드로 한번 보도록 하자.
Flux<Response> VS List<Response>
위 두가지 차이점에 대해서 일단한번 알아보자.
일단 Flux 와 List 는 큰 차이가 있는데 이 개념을 모르는 사람에게 쉽게 설명하기 위해서 Sequence<Response> like Flux<Response> 라고 생각하면 편하다. (사실 같지 않은데 개념적으로 Flux 도 Sequence 이기에 Flux 를 모르는 사람을 위해서 쉽게 설명한 내용이다.) 아래 코드를 보면 사실 이해가 더 빠를 것 이다.
위에 두가지 형태의 코드가 있는데 둘다 똑같이 IntStream 을 돌면서 newResponse(i*input) 형태를 만들고 Return 해주는 형태이다. Controller 에서 이를 호출하는 코드를 만들고 웹 브라우저에서 호출했을때 결과는 아래와 같다.
이 결과만 보았을때는 "왜 굳이 Flux<Response> 를 쓰는거야? 둘다 어차피 10초 기다리는데?" 라고 말할 수 있다. 근데 보통 다들 알겠지만 굳이 Flux<Response> 로 Return 하고 있지 않다는 사실을 알 것이다. 그래서 한번 왜 이렇게 Return 하는지 결과를 알아보도록 하자.
Lazy Evaluate
일단 Reactive Programming 에서는 대부분 Lazy 하게 값들이 평가된다. 보통 수요가 있을때(On-demand) 평가된다고 책들에서 많이 소개되는데 그렇다면 우리의 Flux<Response> 또한 기본적으로 Lazy 하게 평가되므로 수요가 있을때 평가될 것이다. 보통의 프로그래밍 언어에서 수요를 원하는 사람을 Consumer 라고 하고, Flux 와 같은 Data 를 생산하는 사람을 Producer 라고 한다.
그렇다면 여기서 한가지 궁금한점이 생긴다. 우리의 Flux 를 Consume 하고 싶은 사람은 누굴까? 바로 우리 서버에 요청을 보내는 Client 이다. 그게 브라우저가 될 수도 있고, 다른 서버가 될 수도 있다. 그래서 생각해보면 아까 10개의 원소를 1초의 딜레이를 두고 돌리는 로직도 사실 필요로 할때마다, 즉 1초 당 1개씩 브라우저는 필요로 할 것이라는 것이다. 그럼 생각해보면 10초를 기다리지 않고 끊으면 어떻게 될까? 기존에는 아래와 같이 로컬에 사진이 남고 있었다.
요청을 도중에 끊게 된다면?
그럼 이제 한번 요청을 보내다가 중간에 끊어보자.
확인해보면 요청을 도중에 끊게 될 경우 보면 1, 2 까지 하고 요청의 응답이 종료된다. 다만 브라우저도 요청을 도중에 끊은 것이므로 응답을 받지 못한다. 만약 이것을 NonBlocking 모델이아닌 기존의 MVC 로 처리하게 되면 어떻게 될까?
위와 같이 중간에 끊었음에도 불구하고 정상적으로 모든 요청이 처리된다. 이제 이러한 결과를 도출해봤을때 한가지 중요한 사실을 알게되는데, Flux 로 리턴하는 이유는 우리의 Response 가 Client 에 의해 Lazy Evaluate 된다는 사실이다. 개인적으로 이게 굉장히 중요하다고 생각한다. Fully NonBlocking 에 가까운 코드를 작성하기 위해서는 Framework 에서 요런기능을 지원해줘야 하는데, WebFlux 에서 아주 쉽게 사용할 수 있어서 상당히 놀랐다.
번외: 나는 Lazily 하게 평가도 하고 싶고, 근데 중간에 끊어도 이때까지 Data 는 받고 싶은데..
위와 같은 경우가 분명히 존재하는 케이스가 있을 것이라고 생각한다. 왜냐면 큰 데이터를 한번에 받으려면 Stream 을 사용하지 않으면 메모리에서 flush 하기 힘드므로, OOM 이 발생하거나 Full GC 가 터져버릴 수도 있다. 그래서 이러한 경우 보통 프로그래밍에서 Stream 을 이용하여 지연평가 하는 방식으로 많이 진행된다.
위와 같이 media_type 을 지정해주고 브라우저에서 요청을 보내고, 중간에 끊게되면 아래와 같은 결과를 확인할 수 있다.
요청을 보내고 2초뒤에 요청을 취소한 모습이다. 참 재미있는 기능인데 이걸 지금 알았다. 요걸 빨리 알았다면 Excel 다운로드 등등을 더 좋게 최적화 할 수 있었을 텐데.. 참고로 DB 에서 가져올때도 Stream 기능을 지원하는 것이 있는 거 같다.
Github
'Spring' 카테고리의 다른 글
어떻게 Service Layer 간 Transaction Session 을 공유할 수 있을까? (0) | 2022.06.06 |
---|---|
Spring-Cloud-Sleuth 비동기 요청시 서버간 TraceId 보존하는 방법 (0) | 2022.04.27 |
오늘자 삽질 - Spring Kafka (1) | 2022.04.13 |
기존에 있던 Object 를 Bean 으로 등록하는 방법 (0) | 2022.04.05 |
[JPA] Transactional read only 일때 성능상 이점 (0) | 2022.03.21 |