Reactive Programming ‐ Blocking IO & Non‐Blocking IO - thought-corner/Backend-PlayGround GitHub Wiki

Blocking I/O

  • 파일 I/O 외에 데이터베이스에서 데이터를 조회하거나 추가하는 작업 역시 I/O이며, 이를 DB I/O라고 한다.
  • 그리고 웹 애플리케이션에서 다른 웹 애플리케이션으로 네트워크 통신을 한다면 네트워크 I/O가 발생한다.
  • Apache Tomcat 같은 서블릿 컨테이너 등에서 실행되는 웹 애플리케이션은 동적인 데이터를 처리하기 때문에 어떤 식으로는 I/O가 발생하게 된다.
  • 이렇게 하나의 쓰레드가 I/O에 의해서 차단되어 대기하는 것을 Blocking I/O라고 한다.
  • Blocking I/O 방식의 문제점을 보완하기 위해서 멀티쓰레딩 기법으로 추가 쓰레드를 할당하여 차단된 그 시간을 효율적으로 사용할 수는 있으나 이렇게 CPU 대비 많은 수의 쓰레드를 할당하는 멀티쓰레딩 기법은 몇 가지 문제점이 발생한다.

1. 컨텍스트 스위칭으로 인한 쓰레드 전환 비용이 발생한다.

  • 컴퓨터 상에서 뮤직 플레이어로 음악을 들으면서 동시에 웹 브라우저를 열어서 웹 서핑을 하고 있다면 우리의 눈에는 2개의 프로그램이 동시에 실행되는 것처럼 보이지만 실제로 컴퓨터상에는 2개의 프로그램을 번갈아 가면서 실행시켜주는 것인데 그 속도가 너무 빠르기 때문에 우리 눈에는 2개의 프로그램이 동시에 실행되는 것처럼 보이는 것이다.
  • 이렇게 2개의 프로그램이 번갈아 가며 실행되는 과정에서 기존에 실행되고 있는 프로세스의 정보를 PCB라는 공간에 저장하고 다시 실행시켜야 할 프로세스 정보를 PCB로부터 불러오는 과정을 컨텍스트 스위칭(Context Switching)이라고 부른다.
  • P1의 실행 시간이 끝나는 시점에 P2가 실행되는 것이 아니고 어느 정도의 대기 시간이 존재하는데 이런 대기 시간이 발생하는 이유는 바로 프로세스의 정보를 PCB에 저장하고 PCB에서 프로세스 정보를 reload하는데 일정 시간이 소요되기 때문이다.
  • 이렇게 프로세스 정보를 PCB에 저장, reload하는 시간 동안에는 CPU가 다른 작업을 하지 못하고 대기하게 된다. 당연히 컨텍스트 스위칭이 많으면 많을수록 CPU의 전체 대기 시간은 길어지기 때문에 성능 저하가 발생하는 것이다.

쓰레드 컨텍스트 스위칭

  • 멀티쓰레딩 기법에서 쓰레드 역시 여러 개의 쓰레드가 동시에 실행되는 것이 아니라 번갈아가면서 실행한다. 따라서 쓰레드 간에도 컨텍스트 스위칭이 발생한다.
  • 다만 쓰레드는 특정 프로세스에 종속된 하위 개념이기 때문에 쓰레드 간에 컨텍스트 스위칭이 발생하더라도 프로세스의 정보를 공유하기 때문에 교환할 필요가 없고 비교적 적은 정보로 구성된 쓰레드 정보만 TCB(Thread Control Block)라는 공간에 저장하고 reload하는 과정을 반복하므로 프로세스 간의 컨텍스트 스위칭보다 상대적으로 오버헤드가 적다.
  • 그렇다하더라도 애플리케이션 내에서 대량의 멀티쓰레드가 지속적으로 생성된다면 시스템에 문제가 발생할 가능성이 그만큼 높아진다.

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

  • 새로운 쓰레드가 실행되면 JVM에서는 해당 쓰레드를 위한 스택 영역의 일부를 할당하며, 새로운 쓰레드의 정보는 스택 영역에 개별 프레임의 형태로 저장된다.
  • JVM의 디폴트 스택 사이즈는 64비트의 경우 1024KB이다.
  • 일반적으로 서블릿 컨테이너 기반의 Java 애플리케이션은 요청당 하나의 쓰레드를 할당한다. 만약 각각의 쓰레드 내부에서 또 다른 작업을 처리하기위해 쓰레드를 추가로 할당하게 된다면 시스템이 감당하기 힘들 정도로 메모리 사용량이 늘어날 가능성이 있다.

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

  • SpringBoot는 자체적으로 Tomcat이라는 서블릿 컨테이너를 내장한다.
  • Tomcat은 사용자의 요청을 효과적으로 처리하기 위해 쓰레드 풀을 사용한다.
  • 쓰레드 풀이란 일정 개수의 쓰레드를 미리 생성해서 풀에 저장해두고 사용자의 요청이 들어올 경우 아직 사용되지 않고 있는 쓰레드가 있다면 풀에서 꺼내 사용할 수 있도록 하는 일종의 쓰레드 저장소이다.
  • 쓰레드 풀을 사용하지 않는다면 요청이 들어올 때마다 쓰레드를 처음부터 새로 생성해야 하기 때문에 쓰레드 생성과 수거에 비용이 많이 든다. 하지만 쓰레드 풀을 사용한다고 하더라도 또 다른 문제가 발생할 수 있다.
  • 대량의 요청이 발생하게 되어 쓰레드 풀에 사용 가능한 유휴 쓰레드가 없을 경우 사용 가능한 쓰레드가 확보되기 전까지 응답 지연이 발생한다. 이런 응답 지연에는 반납된 쓰레드가 사용 가능하도록 전환되는 지연 시간도 포함된다.

Non-Blocking I/O

  • Non-Blocking I/O는 Blocking I/O와 반대로 쓰레드가 차단되지 않는다.
  • Non-Blocking I/O는 작업 쓰레드 종료 여부와 관계없이 요청한 쓰레드는 차단되지 않는다. Non-Blocking I/O 방식의 경우 쓰레드가 차단되지 않기 때문에 하나의 쓰레드로 많은 수의 요청을 처리할 수 있다.
  • 즉, Blocking I/O 방식보다 더 적은 수의 쓰레드를 사용하기 때문에 Blocking I/O에서 멀티쓰레딩 기법을 사용할 때 발생한 문제점들이 생기지 않는다. 따라서 CPU 대기 시간 및 사용량에 있어서도 대단히 효율적이다.
  • 만약에 쓰레드 내부에 CPU를 많이 사용하는 작업이 포함된 경우에는 성능에 악영향을 준다.
  • 사용자 요청에서 응답까지 전체 과정에 Blocking I/O 요소가 포함된 경우에는 Non-Blocking 이점을 발휘하기 힘들다.