앞서 스마일게이트 부트캠프를 진행하며 MSA 환경에서 Passport 기반 인증 시스템을 설계하며, Spring Cloud Gateway를 중심으로 비동기 구조로 구현했다.
이 과정에서 가장 중요한 전환점은 바로 동기(Synchronous)에서 비동기(Asynchronous)로의 패러다임 전환이었다.
하지만 서비스 규모가 커지고 인증 요청이 많아지면서, 기존 Spring MVC 기반 인증 서버에서 병목 문제가 발생했다. 이는 시스템 전체의 응답 속도 저하로 이어졌고, 나는 자연스럽게 인증서버를 WebFlux 기반의 구조로 전환을 고민하게 되었다.
이번 글에서는 이러한 전환 과정을 바탕으로
를 중심으로 구조적 이해를 정리하고자 한다.
현 시점에서 주톡피아의 인증 서버를 WebFlux 기반으로 리팩토링하게 된 기술적 배경과 판단 근거를 담았다.
Java에서 스레드는 프로세스 내에서 실행되는 하나의 흐름이며, Spring Boot는 기본적으로 이를 효율적으로 처리하기 위해 Tomcat의 스레드풀(Thread Pool) 을 활용한다.
스레드를 매 요청마다 생성하는 방식은 비용이 크기 때문에, Tomcat은 일정 수의 스레드를 미리 만들어두고 요청마다 할당하는 방식으로 동작한다.
이로 인해 다수의 요청도 안정적으로 처리할 수 있으며, 이는 Spring Boot 애플리케이션에서 성능을 좌우하는 중요한 요소가 된다.
기본 설정은 다음과 같다. 공식문서에서도 확인이 가능하다.
server: tomcat: threads: max: 200 # 최대 스레드 수 min-spare: 10 # 항상 유지되는 idle 스레드 수 max-connections: 8192 accept-count: 100
Tomcat의 이러한 스레드풀 구조 덕분에 우리는 별도의 멀티스레딩 처리를 하지 않아도,
Spring MVC 구조에서 다중 요청 처리를 손쉽게 구현할 수 있었던 것이다.
하지만 이 구조는 한계도 명확하다.
요청이 많아질수록 스레드 수가 급격히 증가하게 되고, 결국 스레드풀의 한계에 도달하면서 서버 성능이 저하되는 문제가 발생한다.
특히 데이터베이스 조회, 외부 API 호출과 같은 블로킹 작업이 포함된 요청이 많아질수록 이 문제는 더 두드러진다.
결국 우리는 아래와같이 고민하게 된다.
"매 요청마다 스레드를 점유하지 않으면서도, 많은 요청을 동시에 처리할 수는 없을까?"
이 질문의 답이 바로 비동기(Asynchronous) 처리 방식이다.
앞서 살펴본 것처럼 Spring Boot(MVC 구조)는 요청이 들어오면 Tomcat의 스레드풀에서 하나의 스레드를 할당받아 요청을 처리한다.
이 방식은 구조가 단순하고 익숙하다는 장점이 있지만, 모든 요청이 응답이 끝날 때까지 스레드를 계속 점유한다는 근본적인 한계가 있다.
특히 다음과 같은 상황에서는 심각한 성능 병목으로 이어질 수 있다
동기 방식은 요청-응답을 하나의 흐름으로 처리한다.
즉, 한 작업이 완료되어야 다음 작업이 진행되며, 요청-응답 사이의 블로킹이 발생할 경우 스레드가 낭비된다.
RestTemplate
, 기존 Spring MVC
방식@GetMapping("/user")
public String getUser() {
String result = restTemplate.getForObject("http://auth-service/user", String.class);
return result;
}
위 코드에서 restTemplate
은 블로킹 방식으로 동작하며, 응답이 돌아올 때까지 현재 스레드는 아무 작업도 못하고 대기하게 된다.
비동기 방식은 요청과 처리를 분리하여, 처리가 완료될 때까지 스레드를 점유하지 않는다.
대신 콜백 또는 리액티브 스트림을 통해 결과를 처리한다.
이 방식은 동시 요청 처리에 훨씬 유리하며, 고성능 API 서버, Gateway, 인증 서버 등에 특히 적합하다.
WebClient
, Mono
, Flux
, Spring WebFlux
@GetMapping("/user")
public Mono<String> getUser() {
return webClient.get()
.uri("http://auth-service/user")
.retrieve()
.bodyToMono(String.class);
}
WebClient는 논블로킹 방식으로 동작하기 때문에, 외부 요청을 보내고 응답을 기다리는 동안 스레드를 반환하여 다른 요청을 처리할 수 있게 해준다.
즉, 리소스를 효율적으로 사용하며 확장성 높은 구조를 제공한다.
구분 | 동기 (MVC) | 비동기 (WebFlux) |
---|---|---|
처리 모델 | 요청당 스레드 1개 | 이벤트 루프 기반 |
I/O 대기 처리 | 블로킹 | 논블로킹 |
구조 | 단순 | 복잡 (초기 학습 필요) |
적합한 환경 | 적은 요청, 내부 서비스 | 고트래픽, MSA, API Gateway |
성능 | 요청 증가 시 병목 | 높은 확장성과 처리량 |
스레드풀 기반의 동기 방식은 구조가 단순하고 구현이 쉬우며, 소규모 단일 서비스에서는 효율적일 수 있다.
하지만 MSA 환경, 고트래픽 API 서버, 인증 서버 등 초당 수천 건 이상의 요청을 처리해야 하는 상황에서는 명확한 한계가 존재한다.
이 한계를 극복하기 위한 구조가 바로 비동기 방식, 그리고 그것을 구현하는 Spring WebFlux다.
이제부터는 WebFlux 기반 구조가 실제로 어떻게 동작하는지, Spring Cloud Gateway와 어떻게 연결되는지를 살펴보자.
앞서 우리는 스레드풀 기반의 동기 처리 방식이 많은 요청을 감당하기 어렵다는 한계를 확인했다.
이제는 구조적으로 그 차이를 명확히 이해해볼 필요가 있다. 아래는 Spring MVC와 WebFlux의 주요 차이점을 정리한 표다.
항목 | Spring MVC | Spring WebFlux |
---|---|---|
실행 모델 | 동기, 블로킹 | 비동기, 논블로킹 |
스레드 처리 | 요청당 스레드 1개 | 이벤트 루프 기반 |
서버 | Tomcat, Jetty | Netty, Undertow |
요청 처리 방식 | @RestController + RestTemplate | @RestController + WebClient , Mono , Flux |
성능 | 요청 수 증가 시 병목 발생 | 고성능, 높은 확장성 |
사용 난이도 | 상대적으로 쉬움 | 리액티브 개념 학습 필요 |
기존 MVC 방식은 익숙하고 구현이 단순하여 작은 서비스나 내부 API 처리에는 적합하다.
하지만 MSA 구조처럼 서비스 간 연동이 잦고, 인증/권한 검증처럼 블로킹 포인트가 많은 환경에서는
WebFlux 기반의 비동기 구조가 훨씬 더 유리하다.
특히 WebFlux는 Netty 기반 이벤트 루프를 활용하여, 소수의 스레드로 수천 건의 요청을 동시에 처리할 수 있는 구조이기 때문에
높은 확장성과 안정성을 보장할 수 있다.
Spring Cloud Gateway는 API Gateway 역할을 하며,
모든 요청이 이 관문을 통과하므로 시스템의 병목 지점을 만들지 않는 구조가 필수다.
이러한 이유로 Gateway는 Spring WebFlux 기반으로 설계되었으며,
내부적으로 Netty를 사용하는 논블로킹 이벤트 루프 방식을 채택하고 있다.
이는 다음과 같은 구조적 특성에서 확인할 수 있다.
WebClient
를 활용한 외부 API 호출 시 완전한 논블로킹 방식 유지webClient.get()
.uri("http://auth-service/validate")
.header(HttpHeaders.AUTHORIZATION, token)
.retrieve()
.bodyToMono(UserInfo.class);
위 코드는 인증 요청을 WebClient로 비동기 전송하고,
Mono<UserInfo>
로 반환받은 값을 필터 체인에서 후속 처리에 활용한다.
즉, Gateway는 하나의 요청 처리 중에도 다른 요청을 처리할 수 있는 구조이기 때문에,
고트래픽 상황에서도 병목 없이 안정적인 인증 흐름을 제공할 수 있다.
우리는 아래와 같은 구조적 차이를 통해,
왜 Spring MVC 기반 시스템에서 WebFlux 기반 시스템으로의 전환이 필요한지를 확인할 수 있었다.
항목 | 동기 (Spring MVC) | 비동기 (WebFlux + Gateway) |
---|---|---|
스레드 효율성 | 요청당 스레드 1개 소모 | 이벤트 루프 기반 |
확장성 | 낮음 | 매우 높음 |
적합한 환경 | 단순 API, 소규모 서비스 | 고트래픽 MSA, 인증 시스템 |
전환 난이도 | 쉬움 | 리액티브 개념 학습 필요 |
비동기 구조는 단순한 성능 개선 수준을 넘어,
서비스의 확장성과 장애 대응 능력을 구조적으로 보장하는 중요한 선택지라는 것을 깨달았다.
주톡피아의 인증 서버는 처음엔 Spring MVC 기반으로 구축되어 있었고,
Gateway → Auth 서버 간 통신은 전형적인 블로킹 구조였다.
이로 인해 전체 요청 처리 속도가 느려지고 병목 현상이 발생하는 문제를 마주하게 되었다.
이를 해결하기 위해, 나는 WebFlux 기반으로 인증 서버를 리팩토링했고,
이 과정에서 어떤 선택과 고민이 있었는지를 다음 글에서 자세히 풀어보려 한다.