리액티브 프로그래밍에서 중요한 키워드 4가지,
non-blocking
, streaming
, push
, back pressure
를 중심으로 알아가보자.
(인용글 파티)
우선 스트리밍 처리에 대해 알아보자.
이건 line 공식 블로그에 너무 좋은 설명이 있어서 일부를 가져와 공유해본다.
아래 그림은 전통적인 데이터 처리 방식과 스트리밍 처리 방식을 비교한 그림이다.
왼쪽의 전통적인 데이터 처리 방식에서는, 데이터 처리 요청이 오면 페이로드(payload)를 모두 애플리케이션의 메모리에 저장한 후에 다음 처리를 해야 합니다. 이 방식의 문제점은 전달된 데이터는 물론 저장소에서 조회한 데이터까지 모든 데이터가 애플리케이션의 메모리에 적재되어야만 응답 메시지를 만들 수 있다는 것입니다.
순간적으로 많은 요청이 몰리면서 다량의 GC(Garbage Collection)가 발생, 서버가 정상적으로 응답하지 못하는 경우가 종종 나타납니다.
그런데 많은 양의 데이터를 처리하는 애플리케이션에 스트림 처리 방식을 적용하면, 크기가 작은 시스템 메모리로도 많은 양의 데이터를 처리할 수 있습니다. 입력 데이터에 대한 파이프 라인을 만들어 데이터가 들어오는 대로 물 흐르듯이 구독(subscribe)하고, 처리한 뒤, 발행(publish)까지 한 번에 연결하여 처리할 수 있습니다. 이렇게 하면 서버는 많은 양의 데이터도 탄력적으로 처리할 수 있습니다.
출처 : Armeria로 Reactive Streams와 놀자! - 1
우리는 주로 알고리즘 문제와 같이 절차를 명시하여 순서대로 실행되는 명령형 프로그래밍(Imperative Programming)을 한다. 기존의 명령형 프로그래밍은 데이터의 소비자가 데이터를 요청한 후 받은 결과값을 일회성으로 수신하는 방식이다. 즉, 데이터가 필요할 때마다 결과값을 매번 요청하는 점에서 비효율적이다.
반면 리액티브 프로그래밍(Reactive Programming)에는 하나의 데이터를 발행하는 publisher(발행자)가 있고, 그 데이터를 구독하는 subscriber(소비자)가 있다. 즉, 해당 publisher는 새로운 데이터가 들어오면 데이터의 subscriber에게 지속적으로 데이터를 전달(발행)한다. (이를 ‘데이터 스트림’이라고 한다.)
(△ 리액티브 스트림 (Reactive Stream))
즉, Reactive Programming이란 데이터의 흐름을 먼저 정의하고 데이터가 변경되었을 때 연관된 작업이 실행되는 방식을 말한다. 프로그래머가 어떠한 기능을 직접 정해서 실행하는 것이 아닌, 시스템에 이벤트가 발생했을 때 알아서 처리되는 것이다.
마찬가지로 논블로킹(non-blocking)은 데이터가 변경될 때 이벤트를 발생시켜 데이터를 계속해서 전달하도록 하는 프로그래밍 방식이다.
즉, 작업을 기다리기보단 완료되거나 데이터를 사용할 수 있게 되면 반응한다는 점에서, 리액티브 = 논블로킹이라고 할 수 있겠다.
기존의 프로그래밍 방식(명령형 프로그래밍)을 Pull 방식, Reactive 프로그래밍 방식을 Push 방식이라고도 한다. Pull 방식은 데이터를 사용하는 곳(Consumer)에서 데이터를 직접 가져와서 사용한다면, Push 방식은 데이터의 변화가 발생한 곳에서 새로운 데이터를 Consumer에게 전달한다.
pull 방식 (동기 방식)
push 방식 (비동기 방식)
그럼 리액티브가 push 방식이라고 하는데, 만약 발행자가 구독자의 처리 상태를 고려하지 않고 데이터를 무작정 push해서 밀어넣는다면? 만약 서버라면 오버 플로(overflow)가 발생할 수도 있다.
그래서 리액티브와 관련한 중요한 메커니즘이 하나 더 있는데, 바로 ‘논블로킹 백프레셔(back pressure)’이다.
블로킹 백프레셔는 동기식 명령형 코드에서 호출자를 강제로 기다리게 하는 블로킹 호출이고,
논블로킹 백프레셔는 비동기식 리액티브 코드에서 발행자 속도가 구독자 속도를 넘지 않도록 이벤트 속도(데이터 생산 속도)를 제어하는 것을 말한다.
이렇게만 설명하면 잘 모르겠으니, 다음 인용문을 읽어보자.
Java에서 많이 사용되는
List
가 가변 길이 자료 구조형입니다. 예를 들어 SQL로 많은 양의 데이터를 질의하면 DBMS는 발행자가 되고 여러분의 서버가 구독자가 되어List
자료 구조형에 데이터를 전부 담으려고 하다가 다량의 GC가 발생하면서 서버가 정상적으로 응답할 수 없는 상태에 이를 수 있습니다.
이 문제를 어떻게 해결할 수 있을까요? 발행자가 데이터를 전달할 때 구독자가 필요한 만큼만 전달하면 해결할 수 있지 않을까요? 이게 바로 백 프레셔의 기본 원리입니다.
- 출처 : Armeria로 Reactive Streams와 놀자! - 1
결국 위와 같은 문제를 해결할 수 있는 ‘논블로킹 백프레셔’가 곧 리액티브를 쓰는 주 목적인 셈이다.
앞서 리액티브 프로그래밍의 목적이 '논블로킹 백프레셔'라고 언급했지만,
아래 글을 통해 더 자세히 알아보자.
그렇다면 리액티브 프로그래밍은 어떤 목적에서 생겨났을까요?
서버에서의 리액티브 프로그래밍의 탄생은 리소스의 효율적 사용을 위함에 있었습니다. 리액트 이전에는 멀티쓰레드로써 병렬처리를 했지만, 쓰레드의 확장만으로는 CPU와 메모리의 제한이 있기에 비동기와 논블로킹의 새로운 프로그래밍 모델이 제안되었습니다.
논블로킹은 일반적인 쓰레드를 통한 비동기 작업과는 다르게, 쓰레드를 점유하지 않고 작업을 수행하여 하나의 쓰레드 내에서 동시에 많은 작업을 수행 할 수 있습니다. 이와 같이 작업을 하는데 있어서 너무 많은 트래픽이 몰릴경우 문제가 발생 하거나 성능이 제대로 나오지 않을수 있기에 Back-pressure (배압, 역압)을 통해 요청의 갯수를 제한하여 ‘고가용성’을 보장해 줍니다.
좋은 성능을 보인다기 보다는 ‘고가용성’의 단어를 쓴 이유는 non-blocking을 통해 애플리케이션의 실행속도의 퍼포먼스가 좋아진다는 아니기 때문입니다. 오히려 속도적인 성능은 더 나빠질 수도 있습니다. 다만, 작은 고정된 수의 스레드와 적은 메모리로 최대한의 효율을 내면서 확장 할수 있다는 의미입니다.
출처 : Spring5 리액티브 (Web flux)
여기서 고가용성(高可用性, HA, High Availability)이란 서버와 네트워크, 프로그램 등의 정보 시스템이 상당히 오랜 기간 동안 지속적으로 정상 운영이 가능한 성질을 말한다.
정리하자면,
R2DBC 시리즈의 첫 번째 게시글에서 'R2DBC는 관계형 DB에서 reactive programming을 가능하게 해주는 데이터베이스 접근 API다.' 라고 설명했다.
즉, 위에서 언급한 핵심 메커니즘과 고가용성 때문에 리액티브 프로그래밍을 구현하고자 할 때, 선택할 수 있는 데이터베이스 접근 API 중 하나인 셈이다.
그렇다면 어떻게 reactive programming을 가능하게 해주는가?
R2DBC는 '리액티브 라이브러리'를 사용하여 리액티브 스트림을 구현해 리액티브 프로그래밍을 가능하게 한다.
-> 대표적으로 스프링 데이터 R2DBC가 선택한 핵심 리액티브 라이브러리 '리액터'가 있으며, 다른 리액티브 라이브러리를 사용해도 리액티브 스트림 스펙으로 상호작용할 수 있다.
다만, 스프링 데이터 R2DBC 레포지토리의 일반적인 룰은, 순수한 Publisher를 입력으로 받아 내부적으로 리액터 타입으로 맞추고, 이걸 사용해서 Mono나 Flux를 반환한다.
-> 따라서 어떤 Publisher든 입력으로 전달하고 연산할 수 있지만, 다른 리액티브 라이브러리를 사용하려면 출력 형식을 맞춰줘야 한다.
[참고]
- 리액터 : 리액티브 스트림 라이브러리
리액티브 스트림은 컴포넌트 상호 작용에서 중요한 역할을 한다. 하지만 이건 라이브러리와 기반 구조에 사용되는 컴포넌트엔 유용해도, 어플리케이션 API에서 다루기엔 너무 저수준이다. 어플리케이션은 비동기 로직을 만들기 위한 풍부한 고수준 함수형 API가 필요하다. (자바 8 스트림 API와 비슷하지만 테이블만을 위한 게 아니다) 이게 바로 리액티브 라이브러리가 하는 일이다.
프로젝트 리액터는 스프링 데이터 R2DBC가 선택한 리액티브 라이브러리다. 리액터는 Mono와 Flux API 타입을 제공한다. ReactiveX vocabulary of operators에 정리된 풍부한 연산자를 사용해 데이터 시퀀스를 0~1개는 Mono, 0~N개는 Flux로 표현할 수 있다. 리액터는 리액티브 스트림 라이브러리이기 때문에 모든 연산자는 논블로킹 back pressure를 지원한다. 리액터는 특히 서버 사이드 자바에 초점을 두고 스프링과 긴밀히 협력해서 개발됐다.
스프링 데이터 R2DBC는 프로젝트 리액터를 핵심 라이브러리로 사용하지만, 다른 리액티브 라이브러리를 써도 리액티브 스트림 스펙으로 상호작용할 수 있다. 스프링 데이터 R2DBC 레포지토리의 일반적인 룰은, 순수한 Publisher를 입력으로 받아 내부적으로 리액터 타입으로 맞추고, 이걸 사용해서 Mono나 Flux를 반환한다. 따라서 어떤 Publisher든 입력으로 전달하고 연산할 수 있지만, 다른 리액티브 라이브러리를 사용하려면 출력 형식을 맞춰줘야 한다. 스프링 데이터는 가능만 하다면 투명한 방식으로 RxJava나 다른 리액티브 라이브러리에 맞게 바꿔준다.
- 출처 :
https://godekdls.github.io/Spring%20Data%20R2DBC/reactiveapi/
[출처]
Armeria로 Reactive Streams와 놀자! - 1
Reactive API
Spring5 리액티브 (Web flux)
https://godekdls.github.io/Spring%20Data%20R2DBC/reactiveapi/
리엑티브 프로그래밍(Reactive programming)