안녕하세요, IT 기술 블로거입니다. 웹 애플리케이션 개발에서 성능과 확장성은 늘 중요한 화두입니다. 특히 대규모 트래픽과 실시간 상호작용이 요구되는 현대 웹 서비스에서는 더욱 그렇죠. 스프링 개발자라면 대부분 전통적인 서블릿 기반의 스프링 MVC를 사용해 오셨을 겁니다. 이는 요청당 하나의 스레드가 할당되어 작업을 처리하는 블로킹(Blocking) 방식입니다.
데이터베이스 조회, 외부 API 호출 등 I/O 작업이 발생하는 순간, 해당 스레드는 작업이 완료될 때까지 기다리게 됩니다. 이로 인해 많은 요청이 동시에 발생하면, 대기 중인 스레드가 늘어나고 이는 곧 서버 자원 고갈과 성능 저하로 이어질 수 있습니다. 이러한 한계를 극복하기 위해 등장한 개념이 바로 논블로킹(Non-Blocking)이며, 스프링 진영에서는 이를 구현한 혁신적인 프레임워크, 바로 스프링 웹플럭스(Spring WebFlux)를 선보였습니다.
논블로킹의 핵심은 “기다리지 않음”입니다. 전통적인 블로킹 방식에서는 I/O 작업이 시작되면 스레드가 멈춰서 결과가 올 때까지 기다립니다. 반면 논블로킹 방식에서는 I/O 작업 요청 후, 스레드는 기다리지 않고 즉시 다른 작업을 처리합니다. I/O 작업이 완료되면 등록된 콜백(Callback) 메커니즘을 통해 결과를 받아 처리하게 됩니다.
이러한 방식은 스레드의 효율적인 활용을 가능하게 합니다. 적은 수의 스레드로 훨씬 더 많은 동시 요청을 처리할 수 있으며, 이는 곧 서버 자원(메모리, CPU)의 사용 효율을 높이고 더 큰 확장성을 제공합니다. 웹플럭스는 주로 넷티(Netty)와 같은 논블로킹 웹 서버 위에서 동작하며, 이러한 효율성을 극대화합니다.
스프링 웹플럭스는 리액티브 프로그래밍 패러다임을 기반으로 구축된 웹 프레임워크입니다. 리액티브 프로그래밍은 데이터 스트림과 변경 사항의 전파에 중점을 두며, 비동기적이고 논블로킹 방식으로 동작하는 애플리케이션을 쉽게 구축할 수 있도록 돕습니다.
웹플럭스의 핵심 컴포넌트는 리액터(Project Reactor) 라이브러리입니다. 리액터는 두 가지 핵심 타입을 제공합니다.
Mono<T>: 0개 또는 1개의 데이터를 비동기적으로 전달하는 퍼블리셔(Publisher). 단일 결과나 void 응답에 적합합니다.Flux<T>: 0개에서 N개의 데이터를 비동기적으로 전달하는 퍼블리셔. 여러 개의 데이터를 스트리밍하거나 컬렉션을 처리할 때 사용됩니다.이 Mono와 Flux를 통해 개발자는 데이터의 흐름을 선언적으로 정의하고, 다양한 연산자(map, filter, flatMap 등)를 적용하여 데이터 변환 및 처리를 효율적으로 수행할 수 있습니다. 스레드는 이러한 데이터 흐름을 기다리는 대신, 다른 요청을 처리하는 데 자유롭게 활용됩니다.
아래는 스프링 웹플럭스에서 논블로킹 방식으로 동작하는 간단한 컨트롤러의 예시입니다. Mono를 사용하여 단일 문자열을 반환하고, delayElement를 통해 비동기 I/O 작업이 발생하는 것처럼 시뮬레이션합니다.
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Mono;
import java.time.Duration;
@RestController
public class NonBlockingExampleController {
@GetMapping("/hello-reactive")
public Mono<String> getReactiveHello() {
// 이 코드는 즉시 실행되고, 실제 문자열 'Hello, Reactive World!'는 2초 후에 Subscriber에게 전달됩니다.
// 이 2초 동안 요청을 처리하는 스레드는 다른 작업을 수행할 수 있습니다.
return Mono.just("Hello, Reactive World!")
.delayElement(Duration.ofSeconds(2));
}
@GetMapping("/time")
public Mono<String> getCurrentTime() {
return Mono.just("현재 시간은 " + java.time.LocalTime.now() + "입니다.");
}
}
위 /hello-reactive 엔드포인트에 요청이 들어오면, Mono.just()는 즉시 Mono 객체를 생성하여 반환합니다. delayElement(Duration.ofSeconds(2))는 이 Mono가 데이터를 실제로 발행하는 것을 2초 지연시킵니다. 중요한 점은, 이 2초 동안 요청을 처리하던 스레드는 결과를 기다리며 블로킹되지 않고, 다른 요청을 처리할 수 있다는 것입니다.
같은 애플리케이션에서 /time 엔드포인트에 동시에 요청을 보내보면, /hello-reactive가 아직 응답하지 않았음에도 불구하고 /time은 즉시 응답하는 것을 확인할 수 있습니다. 이것이 바로 논블로킹의 위력입니다.
스프링 웹플럭스와 논블로킹 아키텍처는 고성능, 고확장성 애플리케이션을 구축하는 데 매우 강력한 도구입니다. 특히 다음과 같은 시나리오에서 큰 이점을 발휘합니다.
물론, 리액티브 프로그래밍은 기존의 명령형 프로그래밍 방식과는 다른 사고방식을 요구하며, 학습 곡선이 존재합니다. 하지만 한 번 익숙해지면 복잡한 비동기 로직을 더 간결하고 강력하게 표현할 수 있게 됩니다.
스프링 웹플럭스는 현대 웹 환경의 복잡한 요구사항에 대응하는 스프링의 해답입니다. 논블로킹의 힘을 이해하고 적절히 활용한다면, 여러분의 애플리케이션은 한 차원 높은 성능과 확장성을 경험하게 될 것입니다.
Text by Chaelin & Gemini. Photographs by Chaelin, Unsplash.