들어가며

리액티브 프로그래밍은 현대의 수요 증가와 불확실성에 대응하기 위한 새로운 소프트웨어 아키텍처와 프로그래밍 기법입니다. 이 글에서는 리액티브 프로그래밍의 필요성에 대해 다루고, 리액티브 시스템의 기본 원리, 적합한 비즈니스 사례, 적합한 프로그래밍 기술, 그리고 스프링이 리액티브로 전환되는 이유에 대해 알아보겠습니다.

책의 내용을 바탕으로 알아보는 과정에서 부족하거나 이해가 안되는 설명의 경우에는 직접 찾은 내용을 추가했습니다.

1. 왜 리액티브인가?

우리는 과거에는 톰캣과 같은 서버를 사용하여 웹 요청을 처리했습니다. 하지만 이러한 전통적인 방식은 한계가 있습니다. 예를 들어, 스레드 풀을 구성하고 초당 일정 수의 요청을 처리하는 등의 방식으로 서버를 구성합니다. 그러나 이러한 방식은 대량의 요청이 동시에 발생하는 경우에는 제대로 대응하기 어렵습니다.

예를 들어, 블랙 프라이데이와 같은 특정 이벤트 기간에는 사용자의 요청이 급증하게 됩니다. 이런 경우 스레드 부족으로 인해 요청이 누락될 수 있습니다. 또한, 서버의 부하 부담 능력이 한정되어 있어서 대량의 요청을 처리하기에는 한계가 있습니다.

톰캣은 thread per request 정책을 가지고 있습니다. 따라서 새로운 요청이 들어올 경우 새로운 스레드가 필요합니다. 자바에서는 스레드를 생성하는데 약 1MB의 메모리가 필요합니다. 동시에 수천, 수만개의 요청이 몰릴경우 메모리 부족으로인해 서버가 다운되거나 요청이 누락되어 서비스 장애가 발생할 수 있으므로 대량의 요청을 처리하기에는 한계가 있습니다.

그렇다면 이러한 문제에 대해 어떻게 대응해야 할까요? 수요와 응답에 미칠 수 있는 모든 영향을 고려해야합니다. 즉 어떠한 환경에서도 높은 응답성을 달성할 수 있어야 합니다. 높은 응답성을 달성하기 위해서는 "탄력성"과 "복원력"이 필요합니다.

1.1 탄력성

탄력성이란 사용자의 수요에 따라서 자원을 증가, 감소 시킴으로써 수요에 대응할 수 있는 능력을 뜻합니다. 이를 통해서 사용자의 수요가 많든 적든 일정한 레이턴시를 보장함으로써 사용자의 모든 수요를 충족시킬 수 있습니다. 이는 수요를 모니터링하고 적절하게 자원을 스케일 아웃 또는 스케일 업을 적용함으로써 달성할 수 있습니다.

1.2 복원력

복원력이란 만약 시스템에 장애가 발생했을 때에도 응답성을 유지할 수 있는 능력을 뜻합니다. 이는 두 가지를 통해 달성할 수 있습니다. 첫째로 시스템의 기능 요소를 격리해 장애가 전파되지 않도록해야합니다. 둘째로 복제 데이터베이스 처럼 장애가 발생한 기능 요소를 대체할 수 있는 예비 시스템이 존재해야합니다.

1.3 기존 아키텍쳐의 탄력성 한계

기존 아키텍쳐의 특징은 동기적인(순차적인) 처리 과정이 존재한다는 것입니다. 이는 아무리 인스턴스를 확장해도 처리가능한 한계가 존재한다는 것을 의미합니다. 이는 이론적 관점에서 암달의법칙과 건터의 보편적 확장성 모델로 설명할 수 있습니다.

암달의 법칙은 프로그래밍 요소에 동기적인 부분이 존재하는한 아무리 멀티코어로 병렬화를 진행해도 한계가 존재한다는 것을 의미합니다. 병렬코어를 추가하면 추가할수록 프로그램 처리 속도 증가율을 점점 더 낮아지게 되고 결국 한계에 봉착하게 됩니다.

"Universal scalability model"은 시스템이 대규모로 확장되는 능력을 지칭하는 개념입니다. 이 모델은 시스템이 증가하는 작업 부하나 사용자 수에 따라 선형적으로 확장할 수 있는지 여부를 나타냅니다. 일반적으로 시스템은 사용자나 작업 부하가 증가함에 따라 성능이 저하될 수 있습니다. 이를 해결하기 위해 시스템은 확장 가능한 아키텍처와 설계 원칙을 적용할 수 있습니다. Universal scalability model은 이러한 아키텍처와 원칙을 사용하여 시스템의 성능이 선형적으로 증가할 수 있는 모델을 말합니다.

2.메시지 기반 통신

리액티브 시스템에서는 메시지 기반 통신이 중요한 역할을 합니다. 전통적인 블로킹 방식의 단점을 극복하기 위해 비동기적인 메시지 기반 통신을 사용합니다. 이는 분산 시스템 간의 자원을 효율적으로 사용할 수 있도록 도와줍니다.

메시지 기반 통신은 컴퓨터 시스템 또는 네트워크 간에 데이터를 교환하기 위해 사용되는 통신 방식입니다. 메시지 기반 통신은 송신자가 메시지를 작성하고 수신자에게 보내는 방식으로 동작합니다. 메시지는 일련의 데이터로 구성되며, 송신자는 메시지를 생성하고 목적지 주소와 함께 수신자에게 전송합니다. 수신자는 메시지를 받아들이고 필요한 처리를 수행한 후 응답 메시지를 송신자에게 보낼 수도 있습니다. 메시지 기반 통신은 느슨한 결합(loose coupling)을 제공하므로, 송신자와 수신자 간의 시간적 또는 공간적 제약이 없습니다. 또한, 메시지 큐 등의 중간 매개체를 사용하여 메시지를 저장하고 전달하는 기능을 제공할 수 있습니다. 이는 시스템의 확장성과 유연성을 향상시키는 데 도움이 됩니다.

메시지 기반 통신은 메시지 브로커를 통해 이루어지며, 메시지 대기열을 모니터링하여 부하와 탄력성을 제어합니다. 이를 통해 응답성을 달성하기 위해서는 탄력성과 복원력이 필요하며, 메시지 기반 접근 방식을 채택하여 구성 요소를 독립적으로 격리시킴으로써 유지 보수 및 확장을 용이하게 합니다. 이러한 원칙은 리액티브 선언문의 핵심 개념으로써 사용됩니다.

리액티브 선언문은 높은 응답성을 달성하기 위해 탄력성, 복원력 그리고 메시지 기반 통신의 관계 및 필요성에 대해서 설명한 선언문입니다. 리액티브 아키텍쳐의 기초이기 때문에 직접 읽어보는 것이 좋습니다. https://www.reactivemanifesto.org/

3. 왜 리액티브 스프링인가?

리액티브 프로그래밍은 다양한 플랫폼과 언어에서 사용될 수 있습니다. 그중에서도 JVM 기반의 언어인 자바와 스칼라는 리액티브 프로그래밍을 위한 프레임워크와 라이브러리가 많이 개발되어 있습니다. 스프링의 경우, 리액티브 프로그래밍을 지원하기 위해 스프링 5 버전부터 리액티브 스프링 프레임워크를 도입하였습니다.

스프링 이외의 다른 프레임워크로는 vert.x와 Akka 등이 있습니다. vert.x는 노드를 대체하기 위한 강력한 프레임워크이지만, 스프링보다 연혁이 낮습니다. Akka는 스칼라를 주로 사용하는 프레임워크로서, 자바 개발자에게는 다소 익숙하지 않을 수 있습니다.

4. 서비스 레벨에서의 반응성

스프링을 사용하여 리액티브 시스템을 구현하는 것은 몇 가지 제한이 있습니다. 스프링 클라우드는 몇 가지 문제를 해결하고 분산 시스템의 구축을 단순화해주었습니다. 그러나 명령형 프로그래밍은 여전히 블로킹 작업과 스레드 낭비를 유발할 수 있습니다. 콜백을 사용하면 논블로킹을 구현할 수 있지만, 콜백 지옥과 멀티스레드의 복잡성이 코드에 드러납니다. Java 8의 CompletableFuture는 완전히 비동기적인 처리를 지원하지만, 스프링 4와는 호환되지 않는다는 한계가 있습니다. 스프링에서 제공하는 ListenalbeFuture는 자체적으로 멀티스레딩을 지원하지만, 스레드가 추가될수록 메모리 사용량이 증가하는 단점이 있습니다.

+ Recent posts