월간 지앤선

글 : 박승규 우아한형제들 개발자


2019년 3월 13(수) 우아한 형제들에서 진행된 우아한 테크 세미나에 참석하게 되었다. 이번 세미나의 연사는 이일민(토비)님으로, 스프링 리액티브 프로그래밍이란 주제로 진행되었다.

저서나 온라인을 통해 접하던 토비 님의 강연을 직접 들을 수 있어 설레고, 그 마음과 더불어 리액티브 프로그래밍에 대해 아직 많이 공부하지 못한 사람으로서 강연 내용을 이해하지 못하면 어떻게 하나 걱정이 되기도 했다. 하지만 토비님의 책을 통해 스프링을 공부해 왔기에, 리액티브에 대해 간략하게나마 이해할 수 있기를 기대하며 세미나에 참석했다.

리액티브는 최근 개발자들 사이에서 가장 많이 거론되는 주제라고 생각한다. 토비님께서는 이런 리액티브를 스프링에 어떻게 적용해야 하는지 그리고 경험을 통해 얻게 된 것들과 고민해야 하는 부분 혹은 주의해야 하는 부분을 공유해주셨다.

그럼 강연하신 내용에 대해 살펴 볼까요?


리액티브 프로그래밍이란 무엇인가?


스프링 리액티브 프로그래밍에 대해 본격적으로 이야기 하기 전, 다양한 프로그래머들의 이야기와 리액티브 선언서 등을 통해 리액티브가 무엇인지에 대해 생각해보고 우리가 집중할 부분은 무엇인지 생각해보는 시간을 가졌다.

  • 리액티브

- 외부의 자극에 대해서 반응

- 이벤트가 발생하면 그 이벤트의 결과로써 어떠한 동작이 발생

  • 리액티브란 단어는 과용되고 있다. 다양한 의미로 해석될 수 있다.

  • 리액티브 선언서

The Reactive Manifesto

다양한 의미로 해석될 수 있는 리액티브 프로그래밍에 대해서 토비님은 스프링 리드 개발자의 이야기로 이번 세미나에서 생각해야 하는 리액티브 프로그래밍 개념을 이야기해 주셨다.

Jürgen Höller : 우리는 리액티브 프로그래밍을 스프링 관점으로 제약 하여 생각해보자.


스프링 관점에서 보는 리액티브 프로그래밍?


Jürgen Höller가 이야기하는 스프링에서 리액티브 프로그래밍에 대한 관점은 두 가지이다.

  • 인프라 스트럭처에 대한 도전이다

- 서버, CPU, 메모리, 네트워크 등의 제한적인 부분에 대한 도전

  • 프로그래밍 모델을 전환하는 작업이다

- 인프라에 등장하는 문제를 프로그래밍 모델을 전환하여 문제를 해결하기 위해 접근하는 방법

따라서 "가용성과 응답성을 향상시키기 위해 프로그래밍 모델을 바꿔서 인프라를 활용하는 효율성과 성능을 얻는 것이다."라고 이야기를 하고 있다. 또한 이러한 프로그래밍 모델에서는 함수형 스타일의 프로그래밍을 기반으로 한다고 이야기하고 있다.

  • 함수형 스타일의 코드가 우리 코드로 들어온다.

  • Java8의 람다를 사용하면서 함수형 스타일을 경험하였는데, 더 본격적으로 쓰기 시작해야 한다.

이렇게 2가지 관점을 통해 리액티브 프로그래밍이 무엇인지, 어떤 목적을 위해 스프링을 통해 리액티브 프로그래밍을 해야 하는지 개념을 잡게 되었다. 단순히 서적 또는 레퍼런스를 통해 기술을 습득하려고만 하면 어느 순간 왜 이 기술을 사용하여야 하는지 목적을 잃어버린 채 기술 사용에 급급한 자신을 발견하게 되는데, 이 시간을 통해 리액티브 프로그래밍을 하려는 목적을 명확하게 할 수 있었다.


스프링 5(Spring boot 2.0)와 리액티브 프로그래밍이 어떻게 접목되어 있는가?


실제 스프링 5(Spring boot 2.0)에서는 리액티브 관련 기술이 어떻게 접목되어있는지 아래와 같이 정리 해주셨다.

스프링 5에서는 2가지의 기술 스택으로 되어 있다. (spring)

  • Reactive stack

- 완전히 새로 등장한c것

- 흔히 WbFlux라고 불리는 것이 Reactive Stack

- Servlet Stack 완전히 다르게 새롭게 개발되는 코드를 기반

- 존재의 목적이 Reactive 프로그래밍을 위해 존재 

  • Servlet stack

- 전통적인 방식 (Servlet 3 비동기 처리 기반, 리액티브 클라인트)

- 사실은 여기에다가 리액티브 프로그래밍 기술을 많이 넣었다.

- 전통적인 방식과 리액티브를 접목하여 구성한다.

- 전체 리액티브 기술 중 어느 정도를 포함

하지만 2가지의 기술 스택은 완전히 배타적이지 않고 공유되는 부분이 있기 때문에 2가지의 기술 스택으로 리액티브 프로그래밍은 모두 가능하다고 이야기를 해주셨다. 따라서 리액티브 프로그래밍 도입을 위해 무조건 Reactive Stack을 사용해야 되는 것은 아니다 라는 이야기를 해주셨다.

다만 차이점과 도입 기준에 대해서는,

  •  차이점

- Reactive Stack은 조금더 완전한 리액티브를 지원한다.

- Servlet Stack은 전통적인 프로그래밍 모델과 리액티브 방식을 접목하여 함께 사용할 수 있다.

  • 스택 도입 기준

- 완전히 새롭게 도입 : Reactive Stack

- 기존 MVC 모델에서 점진적으로 리액티브 하게 개선 : Servlet Stack

이와 같이 설명해 주셔서 현재 현업에서 어떤 스택으로 진행해야 될지 선택의 방향성을 이야기해 주셨다.

실제 현업에서 이미 진행되고 있는 스프링 프로젝트에 리액티브를 어떻게 도입해야 할지 고민하게 되는 부분이 있는데, 위의 기준으로 각자의 도입 방향을 결정하면 좋을 것 같다.


어떻게 인프라 도전이란 문제를 해결할 것인가?


이 부분의 가장 중요한 주제로 비동기논블록킹 IO에 대해서 이야기가 진행되었다.

  • 목적

- 리소스를 보다 효율적으로 사용하자

  • 자바 개발자의 동시성 (오랫동안)

- 동시성이란 '많은 스레드'를 사용하는 것을 의미했다.

- 기존 스프링 프레임워크를 사용하면 개발자는 크게 신경쓰지않고 동시성을 하고 있다.

하지만 점차 시스템이 복잡해 지면서 새로운 케이스가 발생하고 문제점도 발생하기 시작했다.

  • 자신(서버)이 단일 처리를 하는 것이 아니라 뒷단의 다양한 요소(외부 API 호출, 데이터 엑세스등)와 연계를 하여 취합하여 처리하는 케이스가 발생하기 시작함.

  • 기존 하나의 요청에 스레드 하나를 독점

- 하나의 스레드가 다양한 IO에 대해서 응답을 기다리면서 자원을 낭비

  • 스레드 풀의 딜레마

- CPU, 메모리가 충분하지만 스레드가 모자라서 처리율 저하

- 따라서 스레드를 늘리면 CPU, 메모리 부하로 성능 저하

- 서버를 늘리면 해결이 되지만.. (돈!)

- 왜 스레드풀을 소모하고 있는가

뒤에 많은 IO가 발생하면서..

 CPU 에서 컨텍스트 스위칭은 비싼 비용의 처리. 따라서 스레드를 늘려도 어느 순간 성능 저하

따라서 리소스의 효율적인 사용을 위해서는 제한된 자원(스레드, CPU, 메모리, 네트워크 등)을 효율적으로 사용하기 위해 새로운 프로그래밍 모델을 사용해야 한다.

  • 비동기

- 작업 요청 시점과 결과 받아 처리하는 시점의 불일치

- 비동기는 콜백이 반드시 존재

 결과를 처리할 로직을 담은 함수

  • 논블록킹 IO

- IO 작업을 수행하는 동안 스레드를 점유하지 않음.

 IO작업을 할수 있는 스레드를 최소한 하자.

 IO요청은 100개가 와도 처리하는 스레드는 1개로 하자.

- 동시에 많은 IO 작업을 처리할 수 있음.

해당 주제에서 말씀해주신 포인트는

비동기 / 논블록 IO 개념의 명확화

비동기 != 논블록킹 IO

또한 다양한 데모를 통해 비동기 / 논블로킹 IO에 대한 개념을 설명해 주셨다. 데모의 방식으로는 아래의 기술을 이용하여 비교/ 테스트가 진행되며 비동기 / 논블록킹 IO 동작 방식에 대해 진행해 주셨다.

  • RestTemplate

- 요청을 하면 응답올 때까지 기다리는 동기방식

  • 비동기 - 서로 의존관계가 없는 예제

- ForkJoinPool을 이용하여 각 RestTemplate 콜을 처리

  • 비동기 - 의존관계 있는 것

- 1번 작업스레드에 요청하고 해당 스레드의 응답값으로 스레드로 2번째 요청 호출

- 시간은 단축되지 않고 스레드 헬에 빠질수 잇다.

  • CompletableFuture

  • AsyncAwait

  • Async Non Blocking CompletableFuture

- Reator Netty에서 제공하는 httpClient

  • Async Non Blocking Web Client

- Webclient

따라서 리액티브 프로그래밍을 도입하기위해 비동기와 논블록킹 IO에 대해 기본적인 개념과 동작원리를 명확하게 숙지하여 사용해야 함을 느끼게 되었다. 당연히 공부를 해야겠죠?

리소스를 효율적으로 사용하기 위해 아래의 개념이 결합하여 함수형 스타일의 새로운 프로그래밍 모델로 개발하는 것이 스프링 리액티브 프로그래밍이라 보면 된다고 하셨다.

논블록킹 IO 필수

비동기 프로그래밍 모델 사용

스프링 리액티브는 위 2개와 백프레셔 + 스트리밍 포함


스프링 리액티브 프로그래밍 사용법 (Reactive Stream과 Reactor)


이전 주제들을 통해 스프링 리액티브 프로그래밍을 위한 필요 개념 및 준비과정으로 실제 사용할 수 있는 부분들에 대해서 이야기했다.

앞선 이야기들이 없었다면 급급하게 사용한 API에 만족했을 텐데, 실제 세미나가 끝나고 공부를 시작하기 앞서 어떤 부분의 기본 개념을 탄탄히 해야 할지 가이드가 잡힌 기분이었다.

이번 주제에서는 실제 사용법에 대해 이야기를 전해주셨으며 중요하다고 생각한 부분을 정리했다.

  • Reactive Streams

- 논블록킹 백프레셔를 이용하는 비동기 데이터 스트림 처리 표준

- 여러 개를 연동할수 있는 컨버터를 만들자  표준 인터페이스를 만들자.

- 비동기 논블록킹 + 데이터 스트리밍 + 백프레셔

- 백프레셔 개념 확실히 하기

이 부분에 대해서는 세미나가 끝난 이후에도 꼭 기억하기를 강조하셨다.

  • Ractive Streams 스펙 (아주 중요!)

- Publisher는 Subscriber의 요청에 따라 제한없이 데이터 제공

- Publisher.subscriber(Subscriber)를 해야 연결이 되고 이후에 비로소 아래의 프로토콜대로 시그널을 받음

- 시그널

 onSubscribe*는 항상 온다.  너가 나를 구독했어

 onNext라는 시그널에 데이터를 실어서 보낸다. (0~N)

 onError 에러 발행하고 종료

 onComplete 완료하고 종료

 무한한 스트리밍을 보낼때는 onError, onComplete가 오지 않을 수 있다.

이 부분을 필수로 이해해야지 바로 예제로 뛰어들 경우 실패를 경험하기 쉽다.

  • Project Reactor

- RX  UI쪽에서 발전된 기술

- 2가지 Publisher를 제공

 Mono(0..1)  void, T

 Flux(0…N)  Iterable

- WebFlux는 해당 Reactor를 기반으로 만들어졌다.

  • Mono / Flux 어떤 것을 쓸까?

- 잘모를 때는 Mono부터 마스터하자.

 만약 Flux처럼 여러ㅊ개를 전달하고 싶으면 Array Type을 던지면 된다.

- Mono< List< T > >와 Flux< T > 차이를 이해

 Mono  컬렉션을 한 시그널에 보내느냐

 Flux  각 시그널에 각 아이템을 보낸다.

위의 내용 이외에 아래의 내용에 대해서도 추가적으로 말씀을 주셨다. (해당 내용은 키워드만.. 자세한 내용은 공부!)

  • 연산자

  • 체인, 파이프라인, 조립라인

  • Map, FlatMap, Transform, Compose

- 이 4가지는 잘 이해하고 잘쓰자. 강조!!!!

  • 예외처리

  • 대체값

  • 스케줄러

  • 테스트

그리고 실제 구현 이후, 해당 Publisher가 동작을 하지 않을 때에는 아래의 내용을 꼭 확인하라고 말씀주셨다.

  • 동작을 안할 때

  1. subscriber 연결안하고 실행하려고 할 때

  2. 체인이 끊기지 않았나

가장 강조 하신 부분은

"여러분이 가장 많이 하시는 실수는 연산자를 이용하여 Publisher를 멋있게 구현하지만 Subscriber를 연결 하지 않아 동작하지 않는 상황이 발생한다.

설마라고 생각할 수 있지만, 기존의 프로그래밍 하던 모델이 머리 속에 있으면 실수할 수 있다."

기존의 프로그래밍 모델의 방식으로 실수 하지 않기를 바라시며 꼭 강조를 해주셨다. 또한 해당 API는 Marble Diagram 이해하기 쉽게 정리되어 있기 때문에 꼭 각각의 연산자의 동작원리를 이해한 후 사용하면 좋다는 말씀도 추가해주셨다.


스프링 리액티브 프로그래밍으로


마지막으로는 스프링 리액티브를 쓰지 말아야 될 이유, 기억할 것, 모델선택, 기존 MVC 코드를 리액티브로 전환하는 부분에 대해서 이야기 해주셨다.


  • 쓰지 말아야 하는 이유

- 스프링 개발자가 아니라면

- 스프링 MVC로 개발해서 별문제 없이 잘돌아간다면

- 블록킹 IO 작업이 있다면

- jpa, jdbc를 쓰고 있다면

  • 기억할 것

- 비동기-논블록킹 서비스를 만드는 것이 목적

- Publisher, Subscirber를 직접 개발자 만드는 경우 없음

- subscribe()는 스프링 MVC, WebFlux가 담당

 코드내 subscribe() 사용금지

- Publisher 생성은 리액티브 라이브러리가 담당

 WebClient와 ReactiveSpringData로 충분

  • 스택 선택

- 다운스트림에만 리액티브를 적용할 것이라면 서블릿 스택으로 충분

- 업-다운 풀 논블록킹 리액티브로 만드려면 리액티브 스택

  • 스프링 MVC  리액티브 코드 전환

- 컨트롤러 리턴타입 Mono로 변환

- 서비스 인터페이스 메서드의 리턴타입도 Mono로 변환

- 데이터 직접 생성한다면 just()

- RestTemplate  WebClinet

- 저장소  mongoldb, redis, cassandra, couchbase

- 전달값이 없어도 void가 아니라 Mono로

 then()  Mono로 연결

 체인이 끊기면 실행이 되지 않음

- 예외  onErrorxxx()

- 등등

그리고 많은 분들이 궁굼하시는 JPA/JDBC에 대해서는 리액티브 프로그래밍에 적합하지는 않지만 방법은 있다하지만 아직은 원할하게 사용가능한 프로젝트가 라이브 레벨로는 없는 상태이지만, 가능하도록 도전하는 것은 나쁘지 않다고 말씀주셨다.


마무리

이외의 아주 다양한 이야기를 개념부터 사용방법, 데모까지 스프링 리액티브를 어떻게 이해하고 사용하며 주의해야 하는 지에 대해서 직접 공부하고 적용하신 부분에 대해서 아주 상세하게 공유를 해주셨다.

참석후기로 작성된 내용은 아주 일부의 내용이지만 이 부분을 기반으로 어떤 식으로 학습을 하며 접근해야 할지, 적용시에 무엇을 주의해야 할지 많은 생각을 하는 세미나 였다.

실제 토비님의 강연을 오프라인으로 처음 들었지만 약 2시간30분이라는 시간이 어떻게 지나갔을지 모를 정도로 유익한 시간이었다.

토비님의 이야기 중에 아래 이야기를 다시 생각하며 열심히 공부하고 리액티브 프로그래밍을 현업에 도입할 수 있도록 노력해 봐야겠다 마음먹으며 스프링 리액티브 프로그래밍 세미나 참석 후기를 마무리 하겠다.

스펙을 명확하게 이해하고 하나하나 마스터해야 리액티브 프로그래밍을 제대로 하실 수 있습니다.