웹플럭스를 알기 전에
Spring 생태계를 사용하고 있다면, 웹플럭스 이전에는 너무도 유명한 Spring MVC를 사용해왔을 것이다. 보통 Embedded Tomcat을 이용한 MVC를 사용할 것이다.
그럼 우선 Spring MVC에 대해 알아보자
Spring MVC는 Servlet API 스펙 위에 정의된 스프링 프레임워크이다. Servlet API라고 정의된 Java Spec에 따라 만들어진 것이다.
위와 같은 모델에서는 Request 1개당 1개의 쓰레드를 할당 받게 된다. Tomcat 쓰레드가 전체 500여개의 쓰레드 풀을 관리하고 있다고 한다면, 동시에 처리될 수 있는 처리량은 최대 500개인 것이다.
동시처리량은 응답속도와 직접적인 영향이 있으므로 응답속도가 1초라고 가정하고 최대 쓰레드 갯수가 500개라고 가정하게 되면 초당 최대 동시처리량은 500개가 되는 것이고, 응답속도가 500ms라고 가정한다면 초당 최대 동시처리량은 1000개가 되는 것이다. (이때의 동시처리량은 최대 커넥션 갯수와 동일하다고 할 수 있다.)
Tomcat 쓰레드를 늘리는 것에는 한계가 있다. 보통 우리가 이야기하는 자바의 Thread는 OS레벨의 쓰레드이다.(물론 경량쓰레드가 나오고 있기는 하지만) 다시 말하면, Tomcat에서 생성된 쓰레드 1개는 실제 OS레벨의 쓰레드 하나를 의미한다는 것이다.
쓰레드가 많다고 해서, 반드시 성능이 잘 나오는 것은 아니다. 오히려 성능을 저해하는 경우가 많다. CPU 코어 간에 Context 스위칭이 많이 일어나면서 오히려 더 많은 비용이 발생하는 경우가 많다.
이벤트 기반 아키텍처에서는 코어수가 매우 중요하다. 대표적인 예가 nginx인데, nginx의 worker 갯수는 코어수만큼 두는 경우가 대부분이다. 왜냐하면, nginx의 모든 처리는 이벤트로 처리가 되고 worker 쓰레드는 이벤트들을 처리하도록 되어 있기 때문인데, 이 경우에 코어 수만큼의 워커는 길게 줄지어진(queue) 이벤트들을 순차적으로 나눠서 처리하게 되고 Context switching이 아예 일어나지 않는다고 가정하면 CPU는 최상의 성능을 낼 수 있다. (넌 다른 코어랑 바꿔가면서 일하지말고 그냥 죽어라 일만하라는 뜻이다.)
여기까지를 보고, 저렇게 설정하면 톰캣은 동시에 500개를 처리할 수 있다는 뜻인가? 라고 생각한다면 맞다.
Webflux는 뭐가 다른건데?
Webflux는 Project Reactor와 Netty하고 관련이 있다. 결론부터 이야기 하면 두가지 매우 중요한 장점이 있다.
- 이벤트기반 아키텍처를 사용해서 자원을 매우 효율적으로 사용할 수 있게 된다. (Context 스위칭을 최소화 해서 CPU를 매우 효율적으로 사용할 수 있다.)
- 이벤트 기반 아키텍처이기 때문에 기존에 동기(sync) 처리되던 것이 비동기(async)처리 될 수 있는데 그 방법이 매우 간단하다.
Project Reactor는 Reactive Stream을 자바에 구현해낸 구현체로 볼 수 있다. 본래는 RxJava라던지 여러가지 형태로 존재해 왔고, 지금은 Reactor가 나타나면서 가장 많이 사용하고 있는 것 같다. (VMware것이고 VMware는 현재 스프링을 만드는 회사다.)
Reactive Stream은 일종의 비동기 프로그래밍의 표준으로 하나(Mono) 혹은 여러개(Flux)의 Element들을 처리할 때, 비동기처리를 매우 간단하게 해주는 것으로 보면 된다. 다만 프로그래밍 모델 자체가 조금 다르긴 하다.
Netty도 비동기 기반 웹서버로 Webflux 프로젝트를 만들게 되면 기본으로 탑재된다.
네티의 주요 개념은 EventLoop라고 하는 쓰레드풀을 가지고 있는 것이고, 이 쓰레드풀은 보통 코어 수만큼 만들어진다. (설정에서 변경은 가능하다)
모든 요청들은 이벤트로 처리되기 시작하고 이벤트 핸들러에 등록이 된다. 요청 후 응답이 없는 경우에는 이벤트 핸들러에 등록만 되고 어떤 쓰레드도 점유하지 않을 수 있다. (물론 이벤트를 확인하는 쓰레드는 몇개 정도 있다)
다만, 이벤트 루프가 실행되는 동안 매우 느린 작업이 실행되면 전체 이벤트루프가 느려지는 Reactor Melt Down이 발생할 수 있으니 주의가 필요하다.
응답이 느린 요청이 많다고 해서 반드시 모든 이벤트 루프를 점유할 필요가 없으며, 이런 경우에는 이벤트 루프의 갯수가 몇개인지에 따라 최대 연결 갯수를 계산하는 것은 거의 불가능에 가깝다고 볼 수 있다. 수십만개의 커넥션을 연결해놓고 그 커넥션들이 실제 Idle 상태라고 한다면 어떤 스레드도 사용되고 있지 않은 상태도 가능하다는 뜻이다. (Thread Per Request 모델을 사용하고 있는 MVC에서는 불가능한 내용이다. 500개가 최대 한도라고요..)
그럼 어떻게 사용할 수 있는 건데?
Spring Boot을 사용한다면 webflux 디펜던시를 추가하고, 메소드의 리턴 타입만 Mono, Flux, CompletableFuture 등으로 바꿔주면 알아서 적용해준다.
아 그럼 Project Reactor에 나오는 Mono, Flux에 대해 알아야 하는 거군? 이것들은 다음 포스팅에 다루고자 한다.
참고문헌
https://www.baeldung.com/spring-webflux-concurrency
https://gocoding.org/java-servlet-api/
https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc