!원문 캡처 · pvrlabs.xyz

Java Performance / Experiments

GitHub 스타를 추적하는 소규모 Spring Boot 앱에서 JdbcTemplate을 사용한 버전은 Spring Data JPA와 Hibernate를 사용한 버전보다 상주 메모리(RSS)를 약 90MiB 적게 썼고, 대시보드 응답에도 약 절반의 시간이 걸렸다.

이번 비교는 Spring Boot 애플리케이션에서 기존 Spring Data JPA와 Hibernate 대신 Spring JDBC를 선택하면 상주 메모리와 시작 시간을 얼마나 줄일 수 있는지 살펴봤다. 두 버전은 Spring Boot 4.1.1과 컴팩트 객체 헤더를 적용한 JDK 25에서 실행했다. H2, Thymeleaf, 같은 로컬 GitHub 테스트 데이터, 대시보드, 모니터링 요청 부하를 사용했고, 실행 가능한 팻 JAR로 패키징했다. 패키징 방식은 별도 팻 JAR과 압축 해제 레이아웃 비교에서 다뤘다.

두 버전 모두 Spring Boot Actuator의 Prometheus 엔드포인트로 메트릭을 제공했다. 이 메트릭은 StatLite 같은 도구로 모니터링할 수 있다. JPA 버전은 엔티티, Spring Data 리포지토리, Hibernate가 관리하는 관계, Spring 트랜잭션을 사용했다. JDBC 버전은 JdbcTemplate을 직접 사용하고 작은 서비스에 영속성 로직을 두었다.

비교 대상은 각 방식으로 만든 애플리케이션 전체다. 영속성 방식을 바꾸면서 애플리케이션 구조와 데이터 모델도 달라졌다. JPA 기준 버전에는 리포지토리 필드가 더 들어가고 리포지토리 및 서비스 코드도 더 많다.

측정 결과

실행 순서를 바꿔 두 차례 측정한 결과, JDBC 버전은 JPA 기준 버전보다 RSS를 약 90~91MiB 적게 사용했다. JPA 버전보다 약 38% 낮은 수치다. 첫 대시보드 응답까지 걸린 시간도 JDBC는 프로세스 시작 후 약 3.9~4.2초였고, JPA는 8.1~8.8초였다.

두 차례 모두 실행 순서와 관계없이 비슷한 차이가 나타났다. JDBC는 RSS 약 146MiB와 응답 시간 약 4초를 기록했고, JPA는 RSS 약 237MiB와 응답 시간 약 8~9초를 기록했다. RSS는 12개 표본의 중앙값이며, 시작 시간은 프로세스 실행부터 첫 대시보드 성공 응답까지 측정한 단일 값이다.

  • JPA를 먼저 실행: JPA RSS 237.0MiB, JDBC RSS 145.8MiB, 차이 91.2MiB. 시작 시간은 각각 8.134초와 4.162초였다.
  • JDBC를 먼저 실행: JPA RSS 236.9MiB, JDBC RSS 146.6MiB, 차이 90.4MiB. 시작 시간은 각각 8.804초와 3.939초였다.

시작 시간에는 애플리케이션 초기화와 첫 요청 처리가 모두 포함된다.

측정 방법과 한계

두 버전은 Temurin JDK 25.0.4를 사용하는 한 대의 macOS 컴퓨터에서 같은 JVM 설정으로 실행했다.

-Xms16m

-Xmx80m

-Xss256k

-XX:+UseSerialGC

-XX:TieredStopAtLevel=1

-XX:ReservedCodeCacheSize=32m

-XX:+UseCompactObjectHeaders

비교 스크립트는 고정된 로컬 GitHub 테스트 서버를 시작한 뒤 애플리케이션을 실행하고, 대시보드 응답을 기다려 한 차례 새로 고침을 수행했다. 이후 30초 동안 예열하고 1분간 5초 간격으로 프로세스 RSS를 측정했다. 예열과 측정 중에는 대시보드와 Actuator의 상태 확인, 메트릭, Prometheus 엔드포인트에 요청을 보냈다. 대시보드에 테스트 데이터의 세 가지 스타 수가 모두 표시되는지, 두 앱이 JVM 메트릭을 제공하는지도 확인했다.

테스트 서버는 고정된 스타 수를 제공했고, 백그라운드 폴링은 측정 시간에 겹치지 않도록 지연했다. 한 버전의 측정을 마친 뒤 다른 버전에서도 같은 과정을 반복했으며, 먼저 실행하는 순서와 나중에 실행하는 순서 모두 측정했다. RSS는 ps -o rss=로 읽은 프로세스 전체의 상주 메모리다. 두 프로세스 모두 1분 측정 동안 약 1~2MiB 증가했으므로, 이 값은 예열 후 런타임 측정치다.

이번 결과는 한 대의 macOS 컴퓨터에서 실행 순서를 바꿔 두 차례 측정한 값이며, 버전별 예열 시간은 30초, RSS 표본은 12개다. 두 애플리케이션 전체를 비교했기 때문에 Hibernate만의 영향을 분리한 결과는 아니다. 이를 분리하려면 두 빌드에서 데이터 모델과 UI, 주변 코드를 동일하게 유지해야 한다.

이 애플리케이션처럼 데이터 모델이 단순하고 쿼리가 복잡하지 않다면 Hibernate가 필요한 것보다 많은 기능을 제공할 수 있다. 반면 애플리케이션과 도메인 모델이 커지고 영속성 동작이 복잡해지면 Hibernate가 해결하는 문제가 생긴다. 이 실험의 JDBC 버전은 여전히 작고 읽기 쉬우면서 측정된 런타임 메모리 사용량은 크게 낮았다.

약 90MiB의 절감은 대형 서버에서는 감당할 수 있지만, 256MiB 또는 512MiB VPS에서는 수용 용량에 영향을 줄 수 있다. 다만 측정은 VPS 메모리 제한이 없는 Mac에서 진행했으므로, 실제 용량에 미치는 영향은 대상 머신에서 확인해야 한다. JDBC 버전은 예상대로 메모리를 덜 썼을 뿐 아니라, 이번 측정에서는 첫 대시보드 응답에도 약 절반의 시간이 걸렸다.

테이블이 몇 개뿐이고 영속성 요구가 단순한 Spring Boot 서비스라면 JPA와 Hibernate를 기본 선택으로 두기 전에 필요성을 살펴볼 만하다. Spring JDBC는 추상화 수준을 일부 포기하지만, 이번 실험에서는 RSS를 약 90MiB 줄이고 첫 대시보드 성공 응답까지 4~5초 단축했다. 메모리가 제한된 소규모 서비스라면 Hibernate를 자동으로 선택하기 전에 직접 측정할 이유가 있다.