Virtual Thread란 무엇인가?

Jayson·2025년 3월 16일
post-thumbnail

Virtual Thread와 비동기 처리: 푸시 알림 기능을 중심으로

최근 진행 중인 대규모 시스템 설계 스터디에서 가상 스레드(Virtual Thread)와 코루틴(Coroutine)의 유사성에 대한 이야기가 오갔다. 처음 가상 스레드를 접했을 때는 우아한형제들의 세미나를 통해 개념을 이해했지만, 코루틴에 대한 개념은 익숙하지 않았다. 이를 계기로 가상 스레드를 다시 정리하고, 차후에 코루틴과 비교해볼 계획을 세웠다.

또한, 4차 스프린트에서 푸시 알림 기능을 구현하면서 비동기 처리에 대한 고민이 깊어졌다. 많은 사용자에게 알림을 효율적으로 전송해야 하는데, 기존 스레드 모델을 사용할 경우 높은 부하가 발생할 가능성이 있었다. 이에 따라 Virtual Thread를 활용한 비동기 처리가 적절한 해결책이 될 수 있을지 고민하게 되었다.

가상 스레드 등장 배경

푸시 알림 기능에선 다량의 요청을 처리하면서 비동기적으로 빠르게 알림을 전송해야 한다. 기존 스레드 모델에서는 스레드 풀을 사용하여 제한된 수의 스레드를 관리해야 하지만, Virtual Thread를 활용하면 수백만 개의 스레드를 생성하면서도 성능 저하 없이 동작할 수 있다.

가상 스레드의 등장

  • 2018년 Project Loom에서 시작된 경량 스레드 모델
  • 2023년 JDK 21에서 정식 기능으로 추가

가상 스레드의 장점

  1. 스레드 생성 및 스케줄링 비용이 저렴
  2. Nonblocking I/O 지원을 통한 높은 동시성 처리 가능
  3. 기존 스레드를 상속하여 코드 호환성 유지

기존 스레드와의 차이점

기존 플랫폼 스레드의 문제점

  • 생성 비용이 크며, 최대 2MB의 메모리 사용
  • 운영체제(OS)가 스케줄링을 담당하여 시스템 콜이 발생 (커널 영역 접근 필요)
  • 다량의 스레드 생성 시 컨텍스트 스위칭 비용 증가

가상 스레드의 특징

  • 스레드 풀 개념이 없음 (필요할 때마다 생성 후 삭제)
  • 메모리 사용량이 적음
  • OS가 아닌 JVM이 직접 스케줄링
  • 생성 비용이 저렴하여 대량의 동시 실행 처리 가능

푸시 알림을 처리할 때, 알림을 전송하는 과정에서 많은 네트워크 요청이 발생한다. 기존 스레드를 사용할 경우 각 요청이 Blocking 상태가 되어 대기 시간이 길어질 수 있지만, 가상 스레드는 비동기적으로 실행되며 대량의 요청을 병렬로 처리할 수 있다.

Nonblocking I/O 지원

현대 서버 아키텍처에서는 I/O Blocking이 성능 병목의 주요 원인이 된다. 특히 Thread-per-Request 모델에서는 요청이 많을수록 대기 시간이 길어진다.

Nonblocking I/O의 필요성

  • MSA(Microservices Architecture) 환경에서 I/O 처리 시간이 증가
  • Thread-per-Request 모델에서는 Blocking Time이 병목

기존 Blocking I/O 방식

  • 요청마다 스레드를 할당 → 스레드가 I/O 작업 완료까지 대기
  • 스레드 자원 소모 증가 → 스케일링 어려움

Nonblocking I/O 방식

  • 스레드가 I/O 작업이 끝날 때까지 대기하지 않음 → 다른 작업 수행 가능
  • 대표적인 예시: Spring WebFlux + Netty

가상 스레드에서의 Nonblocking I/O

  • JVM이 스레드를 직접 스케줄링하여 효율적인 실행
  • Continuation을 활용한 컨텍스트 전환

푸시 알림 기능을 구현할 때, 알림 서버로 다량의 요청을 전송하는 과정에서 Nonblocking I/O를 활용하면 요청 대기 시간이 줄어들고 전체 시스템의 응답성이 향상된다. 기존에는 Spring WebFlux나 Kotlin Coroutine을 사용해야 했던 부분을 Virtual Thread로 해결할 수 있는지 실험해보는 것도 흥미로운 과제가 될 것이다.

Virtual Thread의 동작 원리

기존의 플랫폼 스레드 (User Thread)

  • OS가 직접 스케줄링
  • OS의 커널 스레드와 1:1 매핑
  • Runnable 단위로 작업 실행

Virtual Thread

  • JVM이 직접 스케줄링
  • Carrier Thread(캐리어 스레드)와 1:N 매핑
  • Runnable 대신 Continuation 단위로 작업 수행

푸시 알림을 처리하는 서비스에서 가상 스레드가 어떻게 활용될 수 있을지 실험해보면서, 실제로 성능 차이가 얼마나 나는지도 확인해볼 계획이다.

Virtual Thread 적용 시 주의사항

1. Blocking Carrier Thread 문제 (Pinning)

  • Carrier Thread가 Block되면 Virtual Thread의 장점이 사라짐
  • 주의해야 할 동작:
    • synchronized 블록 사용
    • parallelStream 사용
    • 지원되지 않는 DB 드라이버 (MySQL 일부 드라이버 등)

푸시 알림을 보낼 때 DB에서 수신자 정보를 가져오는 과정에서 Blocking이 발생할 가능성이 있으므로, Pinning 문제가 없는지 주의해야 한다.

2. Thread Pool 불필요

  • Virtual Thread는 스레드 풀을 사용할 필요 없음
  • 생성 비용이 저렴하므로 매번 새로 생성 후 GC로 정리

푸시 알림 서비스는 요청이 몰리는 경우가 많기 때문에, 기존에는 스레드 풀을 활용해 최대 동시 실행 수를 제한했다. 하지만 가상 스레드는 이러한 제한 없이 동작할 수 있다.

결론

Virtual Thread는 가볍고, 빠르고, Nonblocking 처리 가능한 경량 스레드이다.

푸시 알림 기능을 구현하면서 비동기 처리 방식에 대한 고민을 더욱 깊이 하게 되었고, Virtual Thread가 기존의 스레드 모델을 대체할 가능성이 높다는 점을 실감했다. 아직 실험 단계지만, 실제 서비스 적용 시 어떤 장점과 한계가 있을지 검토해보는 것도 좋은 경험이 될 것이다.

앞으로의 방향

향후, 푸시 알림 기능에서 Virtual Thread와 기존 비동기 처리 방식(WebFlux, Coroutine 등)의 성능 비교를 진행해볼 계획이다. 이를 통해 실제 서비스 환경에서 가장 적절한 비동기 처리 방법을 찾아나갈 것이다.

참고 자료

profile
Small Big Cycle

0개의 댓글