Blocking I/O와 Non-Blocking I/O

...·2024년 9월 9일

Reactive Programming

목록 보기
3/5

Blocking I/O

웹 애플리케이션 측면에서의 I/O 작업에는 먼저 파일에서 데이터를 읽어 들이거나 파일에 데이터를 기록하는 작업을 들 수 있다.

파일 I/O 외에 데이터 베이스에서 데이터를 조회하거나 추가하는 작업 역시 I/O이며, 이를 DB I/O라고 한다.

그리고 웹 애플리케이션에서 다른 웹 애플리케이션으로 네트워크 통신을 한다면 네트워크 I/O가 발생하는 것이다.

  1. 클라이언트 PC에서 브라우저 등을 이용해 본사 API 서버에 도서 정보 조회를 위한 요청.
  2. 본사 API 서버에서는 클라이언트 PC의 요청에 맞는 도서 정보를 제공하기 위해 지점 API 서버에 요청
    (본사 API 서버에서 지점 API 서버로 요청을 보내는 그 시점에 본사 API 서버에서 실행된 스레드(요청 스레드)는 차단되어 지점 API 서버의 스레드(작업 스레드)가 처리를 끝내고 응답을 반환하기 전까지 대기하게 됨)

이렇게 하나의 스레드가 I/O에 의해서 차단되어 대기하는 것을 Blocking I/O라고 한다.

Blocking I/O 방식의 문제점을 보완하기 위해서 멀티스레딩 기법으로 추가 스레드를 할당하여 차단된 그 시간을 효율적으로 사용할 수는 있다. 하지만 CPU 대비 많은 수의 스레드를 할당하는 멀티스레딩 기법은 몇 가지 문제점이 존재한다.

  • 컨텍스트 스위칭(Context Switching)으로 인한 스레드 전환 비용이 발생한다.

두 개의 프로그램이 번갈아 가며 실행되는 과정에서 기존에 실행되고 있는 프로세스의 정보를 PCB(Process Control Block)라는 공간에 저장하고, 다시 실행시켜야 할 프로세스 정보를 PCB로부터 불러오는 그 과정을 바로 컨텍스트 스위칭(Context Switching)이라고 한다.

프로세스의 정보를 PCB에 저장, reload하는 시간 동안에는 CPU가 다른 작업을 하지 못하고 대기하게 된다. 당연히 컨텍스트 스위칭이 많으면 많을수록 CPU의 전체 대기 시간은 길어지기 때문에 성능이 저하되는 것이다.

  • 과다한 메모리 사용으로 오버헤드가 발생할 수 있다.

일반적으로 새로운 스레드가 실행되면 JVM에서는 해당 스레드를 위한 스택 영역의 일부를 할당하며, 새로운 스레드의 정보는 스택 영역에 개별 프레임의 형태로 저장된다.

일반적으로 서블릿 컨테이너 기반의 Java 웹 애플리케이션은 요청 당 하나의 스레드를 할당한다. 만약 각각의 스레드 내부에서 또 다른 작업을 처리하기 위해 스레드를 추가로 할당하게 된다면, 시스템이 감당하기 힘들 정도로 메모리 사용량이 늘어날 가능성이 있다.

  • 스레드 풀(Thread Pool)에서 응답 지연이 발생할 수 있다.

Spring Boot은 자체적으로 톰캣이라는 서블릿 컨테이너를 내장한다. 그리고 톰캣은 사용자의 요청을 효과적으로 처리하기 위해 스레드 풀을 사용한다. 스레드 풀이란 일정 개수의 스레드를 미리 생성해서 풀에 저장해 두고 사용자의 요청이 들어올 경우, 아직 사용되지 않고 있는 스레드가 있다면 풀에서 꺼내어 사용할 수 있도록 하는 일종의 스레드 저장소이다.

스레드 풀을 사용하지 않는다면 요청이 들어올 때마다 스레드를 처음부터 새로 생성해야 하기 때문에 스레드 생성과 수거에 비용이 많이 든다. 하지만 스레드 풀을 사용한다고 하더라도 또 다른 문제가 발생할 수 있다.

대량의 요청이 발생하게 되어 스레드 풀에 사용 가능한 유휴 스레드가 없을 경우, 사용 가능한 스레드가 확보되기 전까지 응답 지연이 발생한다.

이러한 응답 지연에는 반납된 스레드가 사용 가능하도록 전환되는 지연 시간이 포함된다.

Non-Blocking I/O

Non-Blocking I/O는 Blocking I/O와 반대로 스레드가 차단되지 않는다.

  1. 클라이언트 PC에서 본사 API 서버로 도서 정보 조회를 위한 요청을 보냄
  2. 본사 API 서버에서는 클라이언트 PC의 요청에 맞는 도서 데이터를 제공하기 위해 지점 API 서버에 추가 요청을 보냄. 이때 A 지점 API 서버로의 요청을 처리하는 동안 스레드가 차단되지 않기 때문에 대기 없이 B 지점 API 서버로 요청을 즉시 보낼 수 있음

이처럼 Non-Blocking I/O 방식의 경우, 작업 스레드의 종료 여부와 관계없이 요청한 스레드는 차단되지 않는다. Non-Blocking I/O 방식의 경우 스레드가 차단되지 않기 때문에 하나의 스레드로 많은 수의 요청을 처리할 수 있다.

즉, Blocking I/O 방식보다 더 적은 수의 스레드를 사용하기 때문에 Blocking I/O에서 멀티스레딩 기법을 사용할 때 발생한 문제점들이 생기지 않는다. 따라서 CPU 대기 시간 및 사용량에 있어서도 대단히 효율적이다.

하지만 Blocking I/O 방식보다 뛰어난 성능을 보이는 Non-Blocking I/O 방식에도 단점은 존재한다.

  • 만약에 스레드 내부에 CPU를 많이 사용하는 작업이 포함되는 경우에는 성능에 악영향을 준다.
  • 사용자의 요청에서 응답까지의 전체 과정에 Blocking I/O 요소가 포함된 경우에는 Non-Blocking의 이점을 발휘하기 힘들다.

Spring Framework에서의 Blocking I/O와 Non-Blocking I/O

Blocking I/O 방식의 애플리케이션이 감당하기 힘들 만큼의 클라이언트 요청 트래픽이 발생하는 상황이 많아졌다. 이런 상황은 Spring MVC 기반의 애플리케이션이라고 예외가 될 수 없었다.

이러한 문제점을 극복하기 위해서 Spring MVC의 대안으로 나온 것이 바로 Spring WebFlux이다.

Spring MVC와 Spring WebFlux의 가장 큰 차이점은 바로 Spring MVC는 Blocking I/O 방식이고 Spring WebFlux는 Non-Blocking I/O 방식이라는 점이다. 또한 서블릿 컨테이너 기반의 Spring MVC는 요청당 하나의 스레드를 사용하기 때문에 대량의 요청을 처리하기 위해서 과도한 스레드를 사용함으로써 CPU 대기 시간이 늘어나고 메모리 사용 시 오버헤드가 발생한다.

반면에 Spring WebFlux는 Netty 같은 비동기 Non-Blocking I/O 기반의 서버 엔진을 사용함으로써 적은 수의 스레드로 많은 수의 요청을 처리하기 때문에 CPU와 메모리를 효율적으로 사용할 수 있어 결과적으로 적은 컴퓨팅 파워로 고성능의 애플리케이션을 운영할 수 있게 해 준다.

Non-Blocking I/O 방식의 통신이 적합한 시스템

Spring WebFlux를 도입하기 위해서는 고려해야 할 사항이 몇 가지 있다.

1. 학습 난이도
2. 리액티브 프로그래밍 경험이 있는 개발 인력 확보의 어려움

대량의 요청 트래픽이 발생하는 시스템

일반적으로 애플리케이션에 발생하는 요청 트래픽이 충분히 감당할 수준이라면 서블릿 기반의 Blocking I/O 방식의 애플리케이션으로 충분할 수 있다.

하지만 대량의 요청 트래픽으로 자주 애를 먹는 시스템이라면 Spring WebFlux 기반 애플리케이션으로의 전환을 고려해 볼 만하다. 물론 서버 증설이나 VM 확장 등을 통해 트래픽을 분산할 수 있겠지만 그만큼의 높은 비용을 지불해야 할 가능성이 높다.

Spring WebFlux 기반 애플리케이션은 상대적으로 적은 컴퓨팅 파워를 사용함으로써 저비용으로 고수준의 성능을 이끌어 내는 괜찮은 선택이 될 수 있을 것이다.

마이크로 서비스 기반 시스템

마이크로 서비스 기반의 시스템은 시스템의 특성상 서비스들 간에 많은 수의 I/O가 지속적으로 발생한다. 따라서 특정 서비스들 간의 통신에서 Blocking으로 인한 응답 지연이 발생하게 된다면 해당 서비스뿐만 아니라 다른 서비스들에도 영향을 미칠 가능성이 상당히 높다. 심지어 응답 지연의 연쇄 작용으로 시스템 자체가 마비될 수도 있다. 그렇기 때문에 마이크로 서비스 기반 시스템에서는 Spring WebFlux 같은 Non-Blocking 방식의 기술이 반드시 필요하다고 볼 수 있다.

스트리밍 또는 실시간 시스템

리액티브 프로그래밍은 HTTP 통신이나 데이터베이스 조회와 같은 일회성 연결뿐만 아니라 끊임없이 들어오는 무한한 데이터 스트림을 전달받아서 효율적으로 처리할 수 있다. Spring WebFlux를 이용하면 이러한 무한 데이터 스트림을 처리하기 위한 스트리밍 또는 실시간 시스템을 쉽게 구축할 수 있다.

참고 서적: 스프링으로 시작하는 리액티브 프로그래밍

profile
주니어 백엔드 개발자

0개의 댓글