ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Java의 미래, Virtual Thread - 4월 우아한테크세미나
    Spring/Framework 2024. 4. 23. 18:29

    4월 우아한 테크

    Java의 미래, Virtual Thread

    김태헌님

    https://www.youtube.com/@woowatech

     

    우아한테크

    우아한형제들의 기술조직 이야기를 전하는 우아한테크입니다. 우아한형제들 https://www.woowahan.com/ 우아한테크 Facebook https://www.facebook.com/woowahanTech 우아한형제들 기술블로그 https://techblog.woowahan.com

    www.youtube.com

    https://www.youtube.com/watch?v=BZMZIM-n4C0

     

    발표자료 : https://drive.google.com/file/d/1GWMlgtKM8S4XcymK8fwCZLaQZ-K6P7Vq/view

     

    [우아한테크세미나] 2024-04 Java의 미래, Virtual Thread.pdf

     

    drive.google.com

     

    blog : https://techblog.woowahan.com/17163/

     

    [신청 시작] 4월 우아한테크세미나: Java의 미래, Virtual Thread | 우아한형제들 기술블로그

    {{item.name}} 4월 우아한테크세미나 : Java의 미래, Virtual Thread 📅 일정 👉 신청 방법 신청 링크 : (링크) 신청 기간 : 2024. 4. 23.(화) 자정까지 신청하신 분들께 세미나가 끝난 뒤 발표 자료(.pdf)를 보내

    techblog.woowahan.com

     

    Index

     

    전사 게이트웨이 시스템, 안정성과 처리량 고민

    •  Project Loom 은 아직 개발 중이었기 때문에 Kotlin Coroutine 선택
    •  Virtual Thread 가 정식 출시되어, deep dive

     

    I. Virtual Thead 소개

    LOOM : thread(실) 을 다루는 베틀(직기)

     

    장점 List

    1. 스레드 생성 및 스케줄링 비용이 기존 스레드보다 저렴
    2. 스레드 스케줄링을 통해 Nonblocking I/O 지원
    3. 기존 스레드를 상속하여 코드 호환

     

    장점1 - 생성/스위칭 비용

    기존 자바 스레드는 생성 비용이 크다

    • 스레드 풀의 존재 이유
    • 사용 메모리 크기가 크다 ( max 2mb )
    • os 에 의해 스케줄링

    vs

    Virtual Thread 는 생성 비용이 작다

    • 스레드 풀 개념 X
    • 사용 메모리 크기가 작다
    • OS가 아닌 JVM내 스케줄링

     

     

    Thread 1백만개 생성하기 실험

    기존 스레드 32초 vs Virtual Thread 0.4 초

     

    장점2 - NonBlocking I/O

     

    동기,순차호출

    vs

    비동기, 병렬호출

     

    CF, Webflux & Netty, Event Loop

     

    VT의 2가지 핵심 개념

    • JVM 스레드 스케줄링
    • Continuation 활용

     

    비동기 실험

    • Tomcat 스레드 10개
    • 10초 소요 API (sleep)
    • 100회 동시 호출

    동기방식 : 100(호출) / 10(스레드) * 10(소요시간) = 100초(예상시간)

    동기방식 실제 호출 : 130초

    vs

    Virtual Thread 호출 : 10.2초 

     

     

    장점3 - 기존 스레드 상속

    기존 Java 와 완벽 호환

     

    ExcutorService -> newVirtualThreadPerTaskExecutor

     

    VT 의 장점 3개 요약

     

     

    기존스레드 vs Virtual Thread

    기존 스레드

     

     

    Thread::start → synchronized(){ start0() // 커널 스레드 생성요청} → native void start0

     

    Virtual Thread

     

     

     

     

    II. Virtual Thead 동작 원리

    virtualThread::start → submitRunContinuation() //작업 스케줄링

     

    submitRunContinuation(){

      scheduler.execute(runContinuation); // JVM내 가상스레드 스케줄링 담당

    }

     

     

    ForkJoinPool 을 사용하는 DEFAULT_SCHEDULER = createDefaultScheduler();

    static final ForkJoinPool // 모든 Virtual Thread 는 동일한 스케줄러를 공유

    ForkJoinPool 메커니즘으로 스케줄링

     

     

     

    <FoorkJoinWorkerThread> pa = () -> new CarrierThread(pool); // worker thread(일반 Thread)

    parallelism = ..availableProcessors();  // worker thread 수

     

     

     

    Work Stealing 방식 : 본인의 workQueue 가 비어 있으면 남의 work queue 에서 가져오기 때문에 stealing 방식

     

     

     

     

    Continuation 작업 단위

    cf. kotlin coroutine 

     

    coroutine 의 suspend funcation 은

    • 중단가능 
    • 중단지점부터 재실행 가능한 구조

    vs

    Continuation

    • 실행 가능한 작업 흐름
    • 중단 가능
    • 중단 지점으로부터 재실행 가능

     

     

    #1 cont1 실행

     

    #2 cont1 block, 

     

    #3 cont1 을 Stack -> Heap 으로 옮기고, 

     

    #4 cont2 실행

    cont2 는 실행하기 위해 Heap -> Stack

     

    #5 실행중이던 cont2 가 bloack 발생하면, stack -> heap 으로 이동

     

    #6 cont1 을 Heap -> Stack 으로 꺼내고, 다시 실행

     

     

    continuation1 

    "Continuation1 : 실행 중 1"

    yield

    "Continuation1 : 실행 중 2"

     

    continuation2

    "Continuation2 : 실행 중 1"

    yield

    "Continuation2 : 실행 중 2"

    continuation 1 과 2 를 하나씩 실행 시키면, 

    → 1개 찍고, yield, 1개 찍고 yield 과정이 반복 됨

    continuation1.run()
    continuation2.run()
    continuation1.run()
    continuation2.run()

     

     

    Virtual Thread 에서 Continuation 을 어떻게 사용하나?

     

    Continuation cont; // vt 의 field 로 cont, task continuation 임을 알 수 있음

    Runnable runContinuation; //  Continuation 실행 람다

     

    생성자

    cont = new VThreadContinuation(this,task) // 받을 task 를 VThreadContinuation 으로 만들어서 가지고 있음

    runContinuation 사용

     

    runContinuation 은 아래처럼 cont.run() 하여 실행시킴

     

    Virtual Thread 를 시작할 때 submitRunContinaution 을 호출

    runContinaution 은 continuation 실행하는 작업이다.


    yield

    park 메소드가 yield

    근데 private 이라 LockSupprot.park 을 호출해야 함

    LockSupport.part() 는 아래처럼 isVirtual() 검사 후 park 호출하는 메소드

     

    참고 

    else (일반스레드)에서도 U.park 로 yield 가능하나

    Unsafe.park() 은  맵핑된 os thread 를 park 시키므로 native thread 가 놀게 됨

    Thread 를 Blocking 할 때 쓰이는 메소드 3개

    Thread.sleep()

    Mono.block()

    CompletableFuture.get()

     

     

    기존에는 LockSupport.park 호출 시  OS thread 가 park 했었으나,

    Virtual Thread 에서는 VirtualThread.park() 호출 시 cotinuation.yield 가 호출되는 것을 알 수 있음

     

     

     

    Continuation 이 어떻게 작업하는지 그림으로 설명

    #1 vt.start() 는  WorkQueue 에 작업을 집어 넣는다

     

     

    #2 Blocking 되는 부분이 있으면, Continuation 의 yield 를 호출

    회색의 Cont1 이 WorkQueue 에서 제거됨

     

    #3 Cont1 제거 후 Cont2 가 이어서 작업하게 된다

     

     

    Continuation 사용 이유

    * Thread 는 작업 중단을 위해 커널 스레드를 중단

    * Virtual Thread 는 작업 중단을 위해 continuatio yield

    * 작업이 blocking 되어도 실제 스레드는 중단되지 않고 다른 작업 처리 ->  NonBlocking I/O 처럼 동작

    * 커널 스레드 중단이 없으므로 시스템 콜이 없고 -> 컨텍스트 스위칭 비용이 낮음

     

     

    -> ForkJoinPool

     

    ->  yield(park)


    III.기존 스레드 모델 서버와 비교

     

    기존 스레드 모델에서는 Request2 를 처리하는 Platform2 스레드가 Blocking 되어 park() 를 호출하더라도, Request3 를 처리할 수 없습니다. Pool 에서 대기해야 함

     

    아래처럼  TomcatProtocolHandlerCustomizer  를 newVirtualThreadPerTaskExecutor() 로 변경하여, VT 를 사용하면

    Tomcat 에 virtual thread 적용방법

     

     

     

    #1 Request2 의 Virtual Thread2 가 blocking 발생하게 되면,

    continuation.park(yield) 하면, 

     

    #2 virtual2 yeild 후 request3(virtual3) 를 Carrier2 스레드가 처리하게 됨

     

     

     


    IV.성능 테스트

    환경

    vt 성능 극대화 위해, 비교적 열악한 환경에서 테스트 진행

     

     

    I/O Bound 작업은 NonBlockng 작업을 하여 50% 성능 향상

    CPU Bound 작업을 굳이 VT로 돌리면 성능 7% 하락

     

    WebFlux vs Virtual Thread

    WebFlux 는 특정 vuser 수 이상이면 WebFlux 처리량 급감

    컨텍스트 스위칭 비용으로 인한 처리량 저하

     

     

    주의사항

    Blocking carrier Thread

     

    synchronized, parallelStream 는 캐리어 스레드를  block 하므로 조심

    VM Option 으로 감지 가능

    -Djdk.tracePinnedThreads ..

     

     

    그래서 spring 이나 Mongodb 에서는 synchronized, parallelStream 을  ReentrantLock 으로 변경 중입니다.

    아쉽게도 mysql 에서는 아직 작업이 진행 중

     

    주의사항2

    그래서 Virtual Thread 적용하기전, 점검해야

    Blocking carrier thread(Pin)

    • 병목 가능성이 존재하는지?
    • 사용 라이브러리가 Vritual Thread 를 지원하는지 release 점검
    • 변경 가능하다면 java.util 의 reentrantLock 을 사용하도록 변경해야 한다

     

    주의사항3

    No Pooling

    • 생성비용이 저럼하기 때문
    • 사용할때마다 생성
    • 사용완료 후 GC

    pooling 해서 갯수에 제한을 두지 말고 사용

    아래처럼 virtual thread 의 excutor is unbounded

     

     

    주의사항4

    CPU bound task

    • 결국 Carrier Thread 위에서 동작하므로 성능 낭비
    • nonblocking 의 점점을 활용하지 못함

     

    경량 스레드

     

    수백만개 만들 수 있게 가볍게 사용하자

    JDK21 preview ScopedValue (  Thread Local 을 대체 )

     

    Virtual thread 는 배압조절(BackPressure) 기능 없다

     

    결론

     

    V. Q&A

     

    Virtual Thread vs Kotlin Corotine

    Virtual Thread Coroutine
    Thread 단위

    아직 구조화된 동시성 지원하지 않음 (수동으로 중단/재개 X) 
     - 만들고 있음
    routine 단위 : 메소드 or Function
    - 단위가 작기 때문에 더 높은 동시성 가능

    특정 corotine 을 종료하거나 캔슬이 가능

     

     

    Virtual Thread vs WebFlux

    Webflux 장점

    - 함수형 프로그래밍을 지원

    - 배압조절 가능(BackPressure)

     

     

    아직 운영에는 적용 안했음 → 검증 중

     

    db connection pool  등에서, BackPressure 가 없으니, 세마포어등을 사용해야 하는데,

    Max 이상으로 들어오는 트래픽은 버리거나 바로 에러처리 되는데, 어떻게 하는게 좋을까요?

    → 질문에 있는 것처럼 현재 거론되는 해답은 세마포어

    → Application code 로 배압을 처리해야 될거 같은데, 나도 고민중이다

     

    ThreadLocal 처럼 

    - preview feature -> scopedValue

     

     

    Virutal thread 를 MySql Jdbc driver 사용은?

    - 아직  PR 진행중, 지켜보자

    댓글

Designed by Tistory.