JDBI: @FetchSize 옵션과 가상 스레드(Virtual Thread) 최적화¶
JDBI 사용 시 @FetchSize 어노테이션을 통해 데이터 로딩 성능을 튜닝할 수 있습니다. 특히 Java 21의 가상 스레드 환경에서 이 옵션이 왜 중요한지 정리합니다.
1. @FetchSize란?¶
- 정의: 데이터베이스에서 결과를 가져올 때, 한 번의 네트워크 왕복(Round-trip)으로 가져올 행(Row)의 개수를 지정하는 설정입니다.
- 작동 원리: 기본적으로 JDBC 드라이버는 매우 작은 단위(보통 10개)로 데이터를 가져옵니다. 1만 건의 데이터를 조회할 때
FetchSize가 10이라면 1,000번의 네트워크 요청이 발생하지만, 1,000으로 설정하면 10번의 요청으로 끝납니다.
2. 가상 스레드(Virtual Thread)와의 관계 및 이점¶
가상 스레드 환경에서 @FetchSize(1000)과 같은 설정은 단순한 속도 향상 이상의 의미를 갖습니다.
2.1 I/O 효율성 및 Unmounting 극대화¶
- 가상 스레드는 I/O 작업 시 캐리어 스레드를 양보(Unmount)합니다.
FetchSize가 너무 작으면 빈번한 네트워크 I/O 발생으로 인해 가상 스레드의 컨텍스트 스위칭(Mount/Unmount) 오버헤드가 증가할 수 있습니다.- 적절한
FetchSize는 I/O 대기 시간을 집약시켜 가상 스레드가 더 효율적으로 CPU 자원을 반납하고 재점유할 수 있게 돕습니다.
2.2 메모리 점유 및 스택 관리¶
- 가상 스레드는 수만 개가 동시에 실행될 수 있습니다.
- 만약 모든 가상 스레드가
FetchSize를 너무 크게(예: 10만) 설정하면, JVM 힙 메모리에 데이터가 급격히 쌓여OutOfMemoryError가 발생할 수 있습니다. - 따라서 가상 스레드 환경에서는 "속도(네트워크 횟수 감소)"와 "메모리(동시 실행 수 고려)" 사이의 균형을 맞춘 적절한
FetchSize(예: 500~1000) 설정이 필수적입니다.
3. 유사한 튜닝 옵션들¶
JDBI 및 JDBC에서 함께 고려할 수 있는 옵션들입니다.
@MaxRows(n): 결과 셋의 전체 최대 행 수를 제한합니다. (메모리 보호 용도)@QueryTimeout(n): 쿼리 실행 시간이 너무 길어질 경우 차단합니다. 가상 스레드가 특정 쿼리에 영원히 묶여 있는 것을 방지합니다.Stream<T>반환: JDBI에서ResultIterable.stream()을 사용하면 전체 데이터를 메모리에 올리지 않고FetchSize단위로 읽으며 처리할 수 있어 가상 스레드와 궁합이 매우 좋습니다.
4. 실무 권장 설정 예시¶
public interface UserDao {
@SqlQuery("SELECT * FROM users WHERE status = :status")
@FetchSize(1000) // 한 번에 1000개씩 읽어옴
@QueryTimeout(10) // 10초 타임아웃
List<User> findByStatus(@Bind("status") String status);
}
5. 결론¶
가상 스레드를 사용한다면 "동시성(Concurrency)"이 비약적으로 높아지므로, 각 스레드가 사용하는 자원을 정교하게 제어해야 합니다. @FetchSize는 네트워크 비용을 줄이면서도 힙 메모리 폭발을 막는 중요한 스로틀링(Throttling) 장치 역할을 합니다.