모종닷컴

Reactive Streams & Reactive Programming 본문

Programming

Reactive Streams & Reactive Programming

모종 2023. 2. 18. 20:19
반응형

Reactive Streams가 뭐야?

요즘 ChatGPT가 유행이죠 ChatGPT에게 질문 먼저 던지고 시작하겠습니다.

  • 비동기 및 이벤트 기반 시스템에서 구성 요소 간의 효율적인 비동기 스트림 처리를 위한 표준화된 API
  • 대량의 데이터를 처리하기 위한 효율적인 방법

뭔가 석연치 않은 대답으로 보입니다. 그냥 위키에 정의된 Reactive Streams를 찾아보았습니다.

리액티브 스트림 정의

"Reactive Streams is an initiative to provide a standard for asynchronous stream processing with non-blocking back pressure"

리액티브 스트림은 논블럭킹 back-pressure로 비동기 스트림 처리를 위한 표준을 제공하기 위한 이니셔티브(계획? 방법?)

리액티브 스트림의 목적

The main goal of Reactive Streams is to govern the exchange of stream data across an asynchronous boundary – like passing elements on to another thread or thread-pool – while ensuring that the receiving side is not forced to buffer arbitrary amounts of data

어떤 element를 다른 스레드 또는 스레드 풀로 전달하는 것과 같이 경계를 넘어 스트림 데이터 교환을 제어하는 동시에 수신 측이 임의의 양의 데이터를 버퍼링하지 않도록 하는 것

처음 위 목적을 들었을 때는 잘 이해가 가지 않았는데 어느 정도 알아보고 난 뒤에 보니 이는 곧 "back-pressure"를 의미하는 것이라 생각했습니다.

Back-Pressure

back-pressure는 데이터를 제공하는 publisher와 이 데이터를 받아 처리하는 subscriber 간의 데이터 양을 조절하기 위한 방법정도라고 생각했습니다. 

예시를 들기위해 간단하게 그림을 좀 그려봤습니다.

  • Subscriber는 택배를 싣기 위해 원하는 택배 개수를 Publihser에게 알립니다. 
  • Publisher는 지속적으로 해당 개수만큼 택배를 Subscriber에게 전달합니다.
  • Subscriber는 받은 택배를 컨베이어 벨트를 이용해 택배차에 싣습니다.

이제 back-pressure를 이해하기 위해 약간의 상황을 설명하겠습니다.

  1. Subscriber는 최초에 Publisher에게 1개씩 택배를 달라고 요구를 합니다. 
  2. 한 개씩 전달받아 싣다보니 조금 여유가 생긴 것을 느낀 Subscriber는 Publisher에게 5개씩 전달해 달라는 요청을 합니다.
  3. Publihser가 5개씩 택배를 전달하고 Subscriber는 택배에 싣는 과정에서 한번에 전달받는 택배양이 많다 보니 조금씩 버퍼가 생긴다는 것을 알아챕니다.
  4. Subscriber는 택배를 다시 3개씩 전달해줄 것을 Publihser에게 요청합니다.
  5. 컨베이어 벨트가 3개이므로 밀리지도 여유있지도 않게 딱 들어맞습니다.

 

리액티브 스트림은 왜 고안이 되었을까요?

인터넷이 발달하면서 인터넷을 사용하는 유저는 급격하게 늘어나기 시작했습니다. 애플리케이션 관점에서 본다면 이런 유저가 늘어난다는 것은 한 번에 처리해야 하는 작업이 늘어난 것입니다. 

전통적인 API 모델 = Transaction Per Request

전통적인 API 모델은 유저의 요청이 들어왔을 때 웹서버에서 스레드가 할당이 되어 요구되는 작업을 처리합니다. 이 부분에서 몇 가지 문제점이 일어나기 시작합니다. 웹 서버에는 지정된 개수의 스레드 풀이 존재합니다. tomcat을  예시로 기본적인 스레드풀의 개수는 200개입니다.

그렇다면 300명의 동시 요청이 들어왔을 때 어떻게 될까요? 이 경우 한 번에 처리할 수 있는 TPS는 200개이기에 100개의 요청은 tomcat의 어느 한 버퍼에서 스레드가 이용가능해질 때까지 대기를 하게 됩니다. 이 버퍼도 마찬가지로 사이즈가 존재하는데 디폴트가 100입니다. 만약 400개의 요청이 동시에 들어왔다면 200개는 작업을 처리 중이고, 100개는 버퍼에 쌓이고, 남은 100개는 어떻게 될까요? 제 로컬에서 테스트 환경을 꾸려서 확인해 본 결과 accept 된 이후에 톰캣에서 해당 요청을 reset 시켜버립니다.

문제 1

  • 100명의 대기 인원은 앞선 유저들보다 훨씬 느린 응답을 받을 수밖에 없습니다.
  • 스레드 풀 개수 + 버퍼 수를 넘어서 들어온 요청은 서비스를 이용할 수 없습니다.

여러 방법이 있겠지만 전통 API모델에서 이러한 이슈를 처리하는 방법은 스레드 개수를 늘리거나, 서버를 스케일업 하는 방법이 있을 겁니다. 서버 한대를 더 띄우자니 리소스가 낭비될 수 있을 것 같습니다. 예로 평소에는 TPS 100을 버틸 수 있도록 설계되었다가 이벤트나 우연히 TPS 100을 넘기는 요청이 들어와서 서버를 늘렸는데, 막상 늘린 이후로는 TPS가 기존처럼 100 이하로 계속 들어오기 때문에 돈만 아깝게 되네요.

문제 2

그럼 스레드 풀 사이즈를 계속 늘리면 해결이 될까요? 아쉽게도 이 역시도 무조건 정답이라고 볼 수 없을 것 같습니다. 스레드를 늘리게 되면 아래와 같은 점들을 고려해야 할 수 있습니다.

  1. context swithching이 빈번해지면서 이 문맥 교환에서 오버헤드가 걸리기 시작합니다.
  2. 스레드가 일정 개수 이상 늘어나다 보면 성능 저하가 오게 됩니다.
  3. Thread Pool Hell 현상이 일어날 수 있습니다.
  4. 스레드는 곧 메모리이므로 이용 자원이 고갈될 수 있습니다.

 

리액티브 API 모델

리액티브 시스템에서는 위와 같은 모델링을 통해 이러한 문제를 해결하고자 하였습니다. 제한된 스레드 풀 사이즈를 만들고, 작업을 처리하는 Event Loop를 적용하였습니다. 좀 더 깊이 있게 들어가면 굉장히 복잡한 구조가 되는데 아래와 같은 사진이 됩니다.

Reactor Netty 구조

아직 저도 이 구조에 대해서 전부다 보지는 못했지만 전통적인 구조와 다른 핵심 부분은 다음과 같습니다.

  • 제한된 작업 스레드 개수를 생성
  • 완전 비동기 & 논블럭킹 구조
  • 이벤트 기반 모델

이러한 특징을 통해 전통적인 모델과 비교해서 더 빠른 성능을 보여주지는 못하지만 적은 수의 스레드로도 효율적인 리스소를 사용하도록 설계되었고, 애플리케이션의 수용력을 늘리는 것입니다. 이는 곧 서비스 품질을 높이고 유저에게 일정한 서비스 경험을 제공하도록 해줄 것입니다.

리액티브 선언문

마지막으로 리액티브 선언문을 보면서 포스팅을 마치려고 합니다. 이 선언문에는 4개의 핵심 가치를 언급하며 리액티브 시스템은 이러해야 한다고 되어있습니다. 이 선언문의 핵심 가치들을 생각하며 저도 이번 프로젝트에 임해야겠습니다.

Responsive

  • 시스템은 적절한 시간 안에 응답을 줄 수 있어야 한다.
  • 지속적이면서 빠른 응답을 제공해야 한다.
  • 지속적인 서비스 질을 제공해야 한다.

Resilient

  • 시스템은 탄력성을 지녀야 하며, 장애가 발생하더라도 올바른 응답을 줄 수 있어야 한다.
  • replication, containment, isolation, delegation 등을 통해 시스템은 탄력성을 지녀야 한다.
  • 시스템의 각 구성 요소를 서로 격리하여 하나의 구성 요소의 장애가 시스템 전체에 영향을 미치지 않아야 하며 시스템에 전체에 영향을 미치지 않으면서 복구될 수 있도록 보장해야 한다.

 

Elastic

  • 시스템은 다양한 워크로드에도 응답성을 제공해야 한다.
  • 할당된 자원을 증가시키거나 감소시킴으로써 입력 속도(요청수)의 변화에 반응할 수 있다.
  • 시스템에 특정 경합 지점이나 중앙 병목 현상이 없어야 한다. 

 

Message Driven

  • 느슨한 결합, 분리 및 위치 투명성을 보장하는 구성 요소 간의 경계를 설정하기 위해 비동기 메시지 전달
  • back-pressure

 

반응형