💻 Backend
스프링 웹플럭스와 가상 쓰레드(spring webflux vs virtual thread)
date
Dec 10, 2023
slug
GDG-Songdo-Backend4
author
status
Public
tags
Conference
summary
[GDG-Songdo-Backend] spring webflux vs virtual thread
type
Post
thumbnail
category
💻 Backend
updatedAt
May 14, 2024 01:48 AM
이상준 / spring webflux vs virtual thread (Backend)기존 자바 Thread웹플럭스의 문제점Virtual Thread 등장사용테스트테스트 결과연관 자바 라이브러리참고링크CommentJava 21 - Virtual Thread
이상준 / spring webflux vs virtual thread (Backend)
⏰15:50
- Spring Webflux 쓸 때가 되었나?
- Kotlin Coroutine 으로 할까?
- Virtual Thread 정식 출시!
아직 정리중인 문서입니다! 발표내용 들으면서 메모장에 빠르게 메모한 내용일 뿐이니 지나가셔도 됩니다 🦋내용 수정 예정 - 24.05.15 00시
기존 자바 Thread
기존 자바의 쓰레드는 실제 OS의 Thread와 1:1구조
갯수가 제한적이고 유지하는 비용이 비쌈 → 생성과 유지비가 비싸니 pool링을 하게됨 (DB Connection Pool)
Thread Per Request 모델
Apache Httpd, Apache Tomcat
GCP 4코어 16gb, 10g 장비의 thread-max size
- 127790
C10K Problem
논블락킹 I/O, 동기 I/O
먹는시간 > 대기시간
- 요청을 처리하는 과정을 뜯어보면 Blocking I/O에서 wait에 대부분 시간을 소요
해결 방법
- 장비 업그레이드
- 너무 비싸고 가성비 안나옴
- OS Thread 는 동시에 Core 수 만큼만 처리 가능하기 때문
- 너무 늘리면 오히려 Context Switching 비용만 비싸짐
- Event Driven
- 커널레벨 프로그램
- select(), poll(), kqueue(), epoll()
- Python - fasAPI
- Javascript - NodeJs
- JAVA - NIO, Netty
- Redis
- 리액티브 프로그래밍, 웹플럭스
Event-Driven, Event loop
Spring 기반 웹 프레임워크도 C10K Problem 이슈 해결!
리액티브 프로그래밍 패러다임을 따르는 리액티브 스트림 등장.
ex) flatMap
리액티브 프로그래밍 - RxJava, Reactor ⇒ Reactor 기반 스프링 웹 프로젝트: Spring Webflux
- 퀘이사 만든 사람이 버츄얼 스레드 작업 하고있더라.
- 리액티브 프로그래밍
반응형 프로그램의 대표 - 스프레드 시트
선언적으로 함수를 선언
A1, B1의 값이 변하면 C1의 값이 함께 변함
- 리액티브 선언문
- 선언형 프로그래밍
원하는 결과만 프로그래밍, 언제 어떤 방식으로 결과가 나올지는 추상화하는 방법
변경사항 전파 (이벤트, 메시징)
Pull → Push
이벤트 발행 → 이벤트 구독
동기적인 요청과 응답대기에서 벗어남
- Flutter Provider / React Context API
앞에서 본 I/O Multiplexing
아키 레이어로 가면 MQ
웹플럭스의 문제점
C10K 해결, 리액티브 프로그래밍 스타일도 마음에 들지만..
높은 프로그래밍 난이도
자바는 스레드를 기반으로 동작하게 되어있는데, 처리는 스레드가 계속 변경되기 때문에 디버깅이 어려워짐
reactive streams가 등장하고 Webflux 같은 프레임워크가 생긴건 사실 자바가 비동기 논블럭킹 지원을 잘 안해줘서임.
kotlin 코루틴은 컴파일로 알아서 때려서 .. (async/await 같은걸 컴파일 단계에서) 해결한 거임
Virtual Thread 등장
JDK 21 2023.09.19 LTS
- JDK 21에 정식 출시된 경량(lightweight) 쓰레드
Project Loom 의 결과물(thread 보다 작은 단위의 task 지원)
JVM이 동작하는 OS Thread를 사용하지 않음
Virtual Machine 에서 자체 지원하는 Thread 를 사용함
- 아까 12만개였는데 수백만개까지 생성이 가능하게 되었음
가상 쓰레드는 새로운 개념이 아님.
User Thread
- Lightweight Thread, Green thread, Virtual Thread, Fiber Thread
Kernel Thread
- OS Thread, Platform Thread, Carrier Thread
Go ⇒ 고루틴 자체가 경량 스레드임. 자바에서 똑같은 걸 만들어낸거임
Haskell GHC, Erlang, .. (Rust는 아님)
JVM 1.3 까지는 자체 스레드를 사용했었음
- OS 스레드와 1:1 매핑되는 스레드를 쓰고있지 않았었음.. (M:1)
- 싱글 코어 시절에 설계였기 때문에 그 이후 플랫폼 스레드를 만들어서 쓰고있었음 (1:1)
- (M:N)
사용
spring.threads.virtual.enabled=true
Thread.currentThread().toString() // Spring MVC // Thread(#43,http-nio-8080-exec-1,5,main) // → // Virtual-Thread(..)
- Virtual-Thread(Fiber Thread)
- Application
- Virtual Thread N
- ForkJoinPool
- Carrier Thread - OS Thread
- 스케쥴러
- CPU Core
- Carrier Thread
테스트
테스트 환경
- 그라파나 K6 + 마이크로미터, 4코어, maxHeap 4GB, Mysql, Connection Pool 150 으로 테스트 했음
- virtual 2000명 부하 테스트, R2DBC
- Platform Thread Count Monitor
- 기존의 플랫폼 스레드를 사용해서 스레드 카운트 확인 시
htop
- Virtual Thread Count Monitor (옵션 켰을 때)
- 33개로 시작되어 ForkJoinPool 이 4개 떠있음(해당 서버에선 4 Core)
- Thread 개수 수치 변화 없음
- JDK 8 + Spring Boot 2.7.13 (Jetbrain 조사결과 아직도 1등임)
- JDK21 + Spring Boot 3.2.0 + Virtual Thread
- ..
테스트 API
- 단순 문자열 반환
- 1초 블라킹 API
- 1초 블라킹 API + DB 조회 1초 실행 쿼리
- 1초 블라킹 API + DB 조회 슬로우 쿼리
테스트 결과
..
Jdbc Connection 경합
- virtual thread 는 지속적으로 계속 자원을 가져오려고 요청하기에 경합이 일어남
Syncronized - Carrier Thread Blocking
Syncronized 사용 시 Virtual Thread에 연결 된 Carrier Thread가 Blocking.
- Thread Pinned 되었다고 표현함
연관 자바 라이브러리
- JDK 21 대응을 위해 Syncronized 을 제거하는 작업을 하고있음
Mysql-mysql-connector-j
Postgres - pgjdbcd
Continuation 구현임
Structured Concurrency 는 전혀 다른 얘기
- OS 스레드가 블락킹이 걸리지 않는 게 관점
- API를 동시호출해서 처리하고 싶으면 기존처럼 CompletableFuture 을 사용하면 됨
- 기존의 Platform Thread 방식이 대체되는 것 X
- 되려 CPU 부하됨. 각자 환경에 맞게 쓰기
DB 커넥션 관련 이슈만 해결되면 Spring MVC + Virtual Thread 사용
Virtual Thread와 WebFlux 의 처리량 차이로 인해 이슈가 생길 정도라면?
WebFlux 부터 도입 X → 스케일 아웃, 샤딩, 캐싱, CQRS, 업무 프로세스 재설계 등..
그래도 부족하면 차라리 Rust? Elixir? ㄷㄷ
코틀린 코루틴이 갖고있던 장점이 많이 퇴색됨
코루틴 - Continuation 구현
- Structured Concurrency 만 남았음
신기술 정보에 대해 알 수 있는 추천
- 크롬 익스텐션
참고링크
Comment
Java 21 - Virtual Thread
하드웨어를 최대한 활용하고자 하는 일부 개발자들은 스레드를 공유하는 방식을 사용하기도 한다. 대표적으로 리액티브 스택(Reactor) 기반의 스프링 웹플럭스(webflux)가 있다. 한 스레드가 I/O 작업이 끝나기를 기다리는(블로킹) 대신, 해당 스레드를 반납하여 다른 요청을 처리할 수 있도록 하는 것이다. I/O 작업은 제외하고 연산을 수행하는 동안에만 스레드를 보유하기 때문에 적은 수의 스레드로도 많은 동시 요청을 처리할 수 있다.
이를 통해 OS 스레드 부족으로 인한 처리량 제한을 없앴지만, 비용이 상당히 크다. 순차적인 것처럼 보이는 요청 처리 단계가 다른 스레드에서 실행될 수 있으며, 이로 인해 Stack Trace를 위한 컨텍스트를 제공할 수 없고, 요청 처리 로직을 순차적으로 살펴볼 수도 없다. 또한 러닝 커브가 상당히 높으며, 자바라는 프로그래밍 언어의 스타일과도 맞지 않는다.