Querydsl 충돌 해결과 Virtual Threads 최적화 전략

궁금하면 500원·2025년 12월 23일

미생의 스프링

목록 보기
48/50

개발 현장에서 가장 땀나는 순간은 라이브러리 버전을 올린 뒤, 전혀 상관없어 보이던 DB 레이어나 타임아웃 설정에서 사고가 터질 때죠.
Spring Boot 4 / Resilience4j 3.0 환경에서 실제로 겪을 법한 Querydsl/Hibernate 충돌가상 스레드(Virtual Threads) 최적화 전략을 정리했습니다.


1. Jakarta EE 네임스페이스의 늪

Spring Boot 4는 Hibernate 6.x 이상을 사용하며, Java의 표준 패키지명이 javax.*에서 jakarta.*로 완전히 전환되었습니다.
이 과정에서 Querydsl이 가장 큰 고비가 됩니다.

충돌 시나리오

Spring Boot 4로 올린 후 QClass가 생성되지 않거나, 실행 시 NoClassDefFoundError: javax/persistence/Entity 에러가 발생합니다.
이는 Querydsl 설정이 여전히 구버전기준이기 때문입니다.

Jakarta 분류기 명시로 해결하기

build.gradle.kts에서 Querydsl 의존성에 jakarta 분류기를 명시적으로 붙여줘야 합니다.

dependencies {
    // Querydsl Jakarta 지원 버전 (6.x 이상 권장)
    implementation("com.querydsl:querydsl-jpa:5.1.0:jakarta") 
    kapt("com.querydsl:querydsl-apt:5.1.0:jakarta")
    
    // Spring Boot 4의 Hibernate와 네임스페이스 일치
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
}
  • Tip: Hibernate 6부터는 Criteria API 성능이 대폭 개선되었습니다.
    만약 Querydsl 설정이 계속 꼬인다면, 복잡하지 않은 쿼리는 Hibernate Native 기능을 검토해 보는 것도 운영 유지보수 측면에서 대안이 될 수 있습니다.

2. 가상 스레드 환경에서의 Resilience4j 튜닝

가상 스레드는 스레드 생성 비용이 거의 없고 수천 개를 동시에 띄울 수 있지만, "차단이 발생해도 스레드 자체가 부족해지지는 않는다"는 특징이 있습니다.
이 때문에 Resilience4j의 튜닝 방향도 달라져야 합니다.

타임아웃 & 수치 튜닝 전략

slowCallDurationThreshold: 더 민감하게 설정

플랫폼 스레드 환경에서는 스레드 고갈을 막기 위해 타임아웃을 넉넉히 줬다면, 가상 스레드 환경에서는 시스템 전체의 응답성을 위해 느린 호출을 더 빠르게 감지하고 차단하는 것이 유리합니다.

  • 변경 제안: 기존 2s → 1s (가상 스레드는 스레드 개수가 넉넉하므로, 특정 API가 느려지는 것을 더 일찍 끊어내도 시스템 전체가 멈추지 않습니다.)

maxWaitDurationInHalfOpenState 가상 스레드 핀닝 주의

가상 스레드가 synchronized 블록 내에서 I/O를 수행하면 플랫폼 스레드에 고정되는 'Pinning' 현상이 발생합니다.
Resilience4j 내부 코드도 가상 스레드 친화적으로 바뀌었지만, 사용자가 정의한 Fallback 로직 등에서 synchronized를 쓰지 않도록 주의해야 합니다.

waitDurationInOpenState 회복 탄력성 강화

가상 스레드 환경은 요청 밀도가 훨씬 높아질 수 있습니다.
서킷이 열렸을 때 재시도하는 간격을 너무 짧게 잡으면, 서킷이 닫히자마자 가상 스레드들이 몰려들어 대상 서버를 다시 터뜨릴 수 있습니다.

  • 변경 제안: 기존 5s → 10s~15s
    (안정적인 복구를 위해 대기 시간을 조금 더 여유 있게 가져가는 것이 안전합니다.)

가상 스레드 최적화 설정 예시

resilience4j:
  circuitbreaker:
    configs:
      default:
        # 가상 스레드 환경 최적화
        slidingWindowType: TIME_BASED
        slidingWindowSize: 60
        minimumNumberOfCalls: 20
        # 느린 응답 기준을 낮춰서 전체 시스템의 '지연 시간'을 관리함
        slowCallDurationThreshold: 800ms 
        slowCallRateThreshold: 50
        # 대상 서버 보호를 위해 복구 대기 시간은 충분히
        waitDurationInOpenState: 15s
        # 핀닝 이슈 방지를 위해 가상 스레드 스케줄러 활용 여부 확인 (v3.0 기준 자동 최적화)
        writableStackTraceEnabled: false 

요약하면...

  1. Querydsl: :jakarta 분류기 누락은 없는지 반드시 확인하세요. 이거 하나로 반나절을 날릴 수 있습니다.

  2. Hibernate: 6버전부터는 Type 매핑 방식이 달라졌으니, 커스텀 타입을 쓰고 있다면 이 부분도 체크 대상입니다.

  3. Virtual Threads: 가상 스레드는 '양'으로 밀어붙이는 기술입니다. Resilience4j는 이제 '스레드 고갈 방지' 목적보다는 '느린 시스템으로부터 우리 서비스를 격리'하는 본연의 목적에 더 집중해서 수치를 튜닝해야 합니다.

profile
에러가 나도 괜찮아 — 그건 내가 배우고 있다는 증거야.

0개의 댓글