ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • WebFlux에서의 이벤트 루프와 컨텍스트 스위칭, 그리고 코루틴??
    카테고리 없음 2023. 9. 5. 00:00

    WebFlux와 이벤트 루프 스레드

    WebFlux는 비동기적이고 논블로킹 I/O를 지원하는 리액티브 프로그래밍 모델이다. 이 모델은 이벤트 루프를 사용하여 많은 수의 I/O 작업을 효율적으로 처리한다. 이벤트 루프 내의 스레드는 일반적으로 블로킹되지 않으며, 작업이 완료될 때까지 기다리지 않고 다른 작업을 계속 수행한다. 그러나 이는 스레드가 절대 블로킹되지 않는다는 것을 의미하지는 않는다. 특정 상황에서는 블로킹이 발생할 수 있다.

    논블로킹 I/O의 한계와 이벤트 루프의 역할

    논블로킹 I/O를 사용하는 WebFlux는 스레드가 블로킹 없이 작업을 처리할 수 있게 한다. 그러나 요청 수가 급증하면 이벤트 루프 스레드가 처리해야 할 작업의 수가 증가하면서 처리 지연이 발생할 수 있다. 이는 일종의 블로킹 상황을 유발할 수 있으며, 이벤트 루프의 큐에 많은 작업이 쌓이게 되어 응답 속도가 느려질 수 있다.

     

    park와 unpark의 역할

     

    Java의 LockSupport 클래스에서 제공하는 park와 unpark 메서드는 스레드를 효율적으로 블로킹하고 깨우는 데 사용된다. park는 현재 스레드를 블로킹 상태로 만들고, unpark는 블로킹된 스레드를 깨운다. 이는 WebFlux에서도 특정 상황에서 블로킹이 필요할 때 사용할 수 있다. 그러나 WebFlux의 기본 철학은 논블로킹 I/O를 사용하는 것이므로, 이러한 메서드는 주로 블로킹 작업을 별도의 스레드 풀에서 처리할 때 사용된다.

    블로킹 작업을 별도의 스레드 풀에서 처리하기

    WebFlux에서 블로킹 작업을 처리할 때 Schedulers.boundedElastic()을 사용하여 별도의 스레드 풀에서 작업을 처리할 수 있다. 이는 이벤트 루프 스레드가 블로킹되지 않도록 하여 전체 시스템의 응답성을 유지한다. 아래는 블로킹 작업을 별도의 스케줄러에서 처리하는 예제이다.

     

     

     

    스케줄러 전환과 컨텍스트 스위칭

    WebFlux에서 publishOn과 subscribeOn을 사용하여 스케줄러를 전환할 수 있다. 이는 작업의 실행 컨텍스트를 변경하는 것을 의미하며, 넓은 의미에서 컨텍스트 스위칭으로 볼 수 있다. 그러나 이는 운영체제 레벨의 컨텍스트 스위칭과는 다르다. 운영체제 레벨의 컨텍스트 스위칭은 스레드의 상태를 저장하고 복원하는 무거운 작업인 반면, 리액티브 프로그래밍에서의 스케줄러 전환은 상대적으로 가볍다.

     

    리액티브 프로그래밍에서의 스케줄러 전환

    • 이벤트 루프: 이벤트 루프는 논블로킹 I/O 작업을 처리하며, 한 번에 하나의 작업을 처리한다. 하지만 동시에 여러 작업을 처리하기 위해 여러 이벤트 루프 스레드를 사용할 수 있다. 이때 각 이벤트 루프 스레드 간의 작업 전환 시 컨텍스트 스위칭이 발생할 수 있다.
    • 스레드 풀: 리액티브 시스템에서는 비동기 작업을 처리하기 위해 종종 스레드 풀을 사용한다. 스레드 풀 내에서 비동기 작업이 큐에 쌓이고, 각 스레드가 큐에서 작업을 가져와 처리한다. 이 과정에서도 스레드 간의 전환이 필요할 수 있다.

    데이터베이스 호출

    리액티브 프로그래밍에서는 데이터베이스 호출 같은 블로킹 작업을 논블로킹 방식으로 처리하기 위해 전용 스레드 풀을 사용할 수 있다. 예를 들어, 리액티브 애플리케이션이 데이터베이스에 쿼리를 보내고 결과를 기다리는 동안, 해당 작업을 논블로킹 방식으로 처리하기 위해 다른 스레드 풀에서 실행한다. 이 과정에서 스레드 간의 전환이 일어날 수 있습니다.

    이벤트 루프 내의 작업 전환

    비동기 작업들이 이벤트 루프 내에서 순차적으로 처리되더라도, 특정 작업이 완료되면 다음 작업으로 전환되는 과정에서 컨텍스트 스위칭이 발생할 수 있다. 이는 이벤트 루프가 다양한 작업을 효율적으로 처리하기 위한 필수적인 부분이다.

     

    리액티브 프로그래밍은 기본적으로 비동기적이고 논블로킹 방식으로 동작하지만, 스레드 컨텍스트 스위칭이 완전히 없는 것은 아니다. 이벤트 루프와 스레드 풀 간의 작업 전환, 비동기 작업의 분배 및 처리 과정에서 컨텍스트 스위칭이 발생할 수 있다. 이는 주로 여러 스레드가 동시에 작업을 처리하고, 블로킹 작업을 논블로킹 방식으로 처리하는 과정에서 발생한다.

     

     

    스케줄러 전환의 오버헤드

    스케줄러 전환에는 약간의 오버헤드가 발생할 수 있다. 작업이 다른 스케줄러로 이동하면서 작업 큐에 추가되고, 다른 스레드가 이 큐에서 작업을 가져와 실행하기 때문이다. 따라서 불필요한 publishOn 호출을 피하고, 필요할 때만 스케줄러를 전환하는 것이 좋다.

     

    최적화 전략

    블로킹 작업을 처리한 후, 다시 기본 이벤트 루프 스레드로 돌아가는 것은 성능과 응답성을 유지하는 데 중요하다. publishOn(Schedulers.immediate())를 사용하여 블로킹 작업 이후의 처리가 기본 이벤트 루프 스레드에서 계속되도록 할 수 있다. 이는 불필요한 스레드 전환을 줄이고, 스케줄러 자원을 효율적으로 사용할 수 있으나 테스트는 필요하다.

     

     

     

     

    블록킹 작업이 있을때는 boundedElastic 보단 coroutine을 써보는 것도 괜찮을까??

     

    WebFlux의 boundedElastic 스케줄러

    WebFlux는 기본적으로 이벤트 루프 스레드를 사용하여 비동기 작업을 처리한다. 하지만 블록킹 작업이 필요한 경우, boundedElastic 스케줄러를 사용하여 이러한 작업을 별도의 스레드 풀에서 처리한다. 이 접근 방식은 블록킹 작업이 이벤트 루프를 방해하지 않도록 하는 데 유용하다. 그러나 몇 가지 단점이 있다:

     

    1. 높은 컨텍스트 스위칭 비용: boundedElastic 스케줄러는 운영체제 수준의 스레드를 사용하기 때문에 스레드 간의 컨텍스트 스위칭 비용이 크다.
    2. 추가 스레드 리소스: 블록킹 작업을 처리하기 위해 추가적인 스레드를 사용하게 되어 시스템 리소스 소비가 증가한다.

     

    Kotlin 코루틴의 장점

     

    Kotlin 코루틴은 경량 스레드로 작동하며, 비동기 작업을 처리하는 데 매우 효율적이다. 특히, Dispatchers.IO를 사용하면 블록킹 IO 작업을 최적화할 수 있다.

    1. 낮은 컨텍스트 스위칭 비용: 코루틴의 컨텍스트 스위칭은 스택을 저장하고 복원하는 방식이 아니라 상태를 저장하고 복원하는 방식으로 이루어져 운영체제 수준의 스레드 컨텍스트 스위칭보다 훨씬 가볍다.
    2. 효율적인 스레드 관리: Dispatchers.IO는 최소한의 스레드로 많은 양의 IO 작업을 효율적으로 처리할 수 있다.
    3. 간결한 코드: 코루틴을 사용하면 코드가 간결하고 가독성이 높아진다. 비동기 작업을 일관되게 처리할 수 있다.

     

     

    코루틴을 사용하는 것이 자원 효율성 면에서 유리한 이유는 크게 두 가지로 요약이 가능할 듯 하다.

     

    1. 컨텍스트 스위칭 비용 감소

    • 코루틴의 경량 스레드: 코루틴은 경량 스레드로 작동하며, 실제 스레드보다 훨씬 적은 자원을 사용한다. 코루틴 간의 컨텍스트 스위칭은 매우 저렴하게 이루어진다..
    • 리액티브 프로그래밍의 스레드 전환: 리액티브 스트림을 사용하는 WebFlux에서는 이벤트 루프와 스레드 풀을 통해 비동기 작업을 처리하지만, 여전히 스레드 간의 전환이 발생할 수 있다. 이러한 스레드 컨텍스트 스위칭은 자원 소모를 유발할 수 있다.
    • 결론: 코루틴을 사용하면 경량 스레드를 통해 컨텍스트 스위칭 비용을 줄일 수 있어 자원 효율성을 높일 수 있다.

    2. 블로킹 작업의 효율적 처리

    • 리액티브 프로그래밍의 블로킹 작업 처리: 리액티브 프로그래밍에서는 블로킹 작업을 처리하기 위해 별도의 BoundedElasticScheduler와 같은 스레드 풀을 사용한다. 이 스레드 풀은 블로킹 작업을 처리하지만, 스레드 풀의 관리와 스레드 간의 전환 비용이 발생할 수 있다.
    • 코루틴의 경량 스레드 활용: 코루틴은 블로킹 작업을 경량 스레드에서 논블로킹 방식으로 처리할 수 있다. 이는 별도의 스레드 풀을 사용하는 것보다 자원 효율적이다.

     

    비동기 작업을 처리할 때, 특히 블록킹 IO 작업이 포함된 경우나 IO 작업이 많을 경우, WebFlux의 boundedElastic 스케줄러를 사용하거나 Webflux 만 사용하는 구조 대신 Kotlin 코루틴을 사용하는 것도 고려해볼 수 있을 것 같다. 코루틴은 컨텍스트 스위칭 비용이 낮고, 최소한의 스레드로 많은 작업을 처리할 수 있는 장점이 있다.

     

Designed by Tistory.