0. 전통적 웹 방식

전통적 웹 방식은 1개 요청 당 1개의 스레드를 사용하는 Thread per Request 입니다. 하나의 요청에 스레드가 블락킹됩니다. JVM 에서 플랫폼 스레드와 커널 스레드가 1:1 매핑되는 구조로, 무한정 스레드를 늘릴 수 없습니다.

1. 리액티브 프로그래밍

콜벡헬을 함수형 프로그램밍 관점으로 해결한 것으로, 대표적 표준은 리액티브 스트림즈입니다. 스프링에서는 Spring Webflux 를 사용해 개발합니다. 다만 매우 높은 학습곡선이 있고, 연산자에 대한 마블 다이어그램 이해가 필요합니다.

실무 중 발생하는 문제 중 하나로, 내부에서 블로킹 API 를 사용하는 것입니다. 블로킹 API 를 사용하는 경우라면 별도의 스레드 풀 위에서 실행시켜야 합니다.

2. 코틀린 코루틴

코루틴은 코틀린의 확장 라이브러리입니다. 확장 라이브러리이기 때문에 별도로 gradle 에 디펜던시를 추가해야 합니다. 코루틴은 비동기-논블로킹 환경에서 사용되어야 합니다.

2.1 코루틴이 가벼운 이유

다수의 코루틴이 소수의 플랫폼 스레드 위에서 동작합니다. 코루틴 간 전환은 os 가 아닌 언어레벨에서 이루어지게 됩니다.

Thread.sleep 은 스레드가 블로킹되지만 delay 함수는 논블로킹 형태로 코루틴이 일시중단됩니다.

2.2 코루틴 사용

코루틴 빌더

  • runBlocking : 감싸진 코드는 모든 수행이 끝날 때까지 스레드 블로킹
  • launch : 스레드 차단 없이 새로운 코루틴을 시작하며, 결과를 만들어내지 않는 비동기 작업에 적합함
  • async : 비동기 작업을 통해 결과를 만들어내는 경우 적합. await 함수를 통해 async 로 수행한 비동기 작업의 결과를 받아올 수 있음

2.3 구조적 동시성

코루틴 생명주기와 예측 가능성을 향상시킵니다.

  • coroutineScope : 스레드 블로킹 없이 각 동시 작업의 수행이 완료된 후 함수가 종료됨
  • 구조적 동시성에서 예외를 다루는 방법 : coroutineScope 내부의 자식 코루틴에서 예외가 발생하면 모든 코루틴이 취소됩니다.
  • SupervisorScope : 내부의 예외를 부모 코루틴으로 전파하지 않습니다.
  • NonCancellable : 다른 코루틴에서 예외가 발생햇도 작업이 수행됩니다.

2.4 코루틴 컨텍스트

  • Dispatchers : 코루틴이 동작하는 스레드를 결정하는 방법

ThreadLocal 의 데이터를 코루틴에 전파하는 방법

MDC Context 를 사용해서 ThradLocal 내의 값을 coroutine 내에서 사용할 수 있습니다.

3. 버츄얼 스레드

Java 플랫폼 위에서 경량으로 동작하는 스레드입니다. 코루틴과 매우 비슷하게 다수의 버츄얼 스레드가 소수의 캐리어 스레드에서 동작합니다. 캐리어 스레드는 버츄얼 스레드 전용의 플랫폼 스레드를 말합니다. 코루틴과 마찬가지로 버츄얼 스레드는 os 레벨이 아닌 jvm 플랫폼 레벨에서 전환이 이루어집니다.

버츄얼 스레드는 코륕ㄴ과 달리 내부에서 Tread.sleep() 과 같이 스레드를 멈춰도 계속 동작합니다.

4. 테스트

chatGPT 로 요청을 보내고 응답받는 코드를 테스트합니다. 요청 당 20~30초 걸립니다.

  • 일반적인 요청 (스레드 200개) : 200/1000 성공
  • 코루틴 + IO 디스패처 : 64/1000 성공 -> IO 스레드가 블로킹되기 때문
  • 버추얼 스레드 : 1000/1000 성공

5. 코루틴과 버추얼 스레드의 통합

코루틴이 버추얼 스레드 위에서 동작한다고 생각하면 됩니다.

  • 코루틴의 단점인 블로킹 상황의 성능 하락 이슈를 버추얼 스레드가 해결
  • 쿠루틴과 버추얼 스레드의 상호 운용을 통해 코루틴의 잘 만들어진 구조적 동시성과 코루틴 컨텍스트를 활용할 수 있음
  • 버추얼 스레드에서 지원하지 않는 기능(스트리밍, 백프레셔 등)은 코루틴의 고급 라이브러리를 활용해 구현 가능

댓글남기기