Spring Boot 성능 최적화 — JVM 튜닝부터 쿼리 개선까지
Spring Boot 애플리케이션이 느리다면 JVM, 커넥션 풀, 쿼리, 캐시 순서로 확인하세요.
JVM 튜닝
섹션 제목: “JVM 튜닝”힙 메모리 설정
섹션 제목: “힙 메모리 설정”# 컨테이너 환경 (권장)JAVA_OPTS="-XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=50.0"
# 직접 지정JAVA_OPTS="-Xms512m -Xmx1g"# Xms: 초기 힙 크기 / Xmx: 최대 힙 크기# 컨테이너에서는 Xms = Xmx로 설정 권장 (GC 예측 가능성)GC 선택
섹션 제목: “GC 선택”# G1GC — Java 9+ 기본값, 대부분의 경우 적합-XX:+UseG1GC-XX:MaxGCPauseMillis=200 # 최대 GC 멈춤 목표 200ms
# ZGC — 낮은 레이턴시 필요 시 (Java 15+)-XX:+UseZGC# GC 멈춤 < 1msSpring Boot 설정 최적화
섹션 제목: “Spring Boot 설정 최적화”# HikariCP 커넥션 풀spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 3000 idle-timeout: 600000 max-lifetime: 1800000
# Tomcat 스레드 풀server: tomcat: threads: max: 200 min-spare: 20 accept-count: 100
# 불필요한 자동 설정 비활성화spring: autoconfigure: exclude: - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfigurationN+1 쿼리 해결 (JPA)
섹션 제목: “N+1 쿼리 해결 (JPA)”// N+1 발생 코드@Entitypublic class Order { @ManyToOne private User user; // 지연 로딩 — 접근 시마다 쿼리}
// 100개 주문 조회 시 → 101개 쿼리 발생List<Order> orders = orderRepository.findAll();orders.forEach(o -> log.info(o.getUser().getName()));
// 해결 1: JPQL fetch join@Query("SELECT o FROM Order o JOIN FETCH o.user WHERE o.status = :status")List<Order> findByStatusWithUser(@Param("status") String status);
// 해결 2: EntityGraph@EntityGraph(attributePaths = {"user"})List<Order> findByStatus(String status);
// 해결 3: @BatchSize (컬렉션)@BatchSize(size = 100)@OneToMany(mappedBy = "order")private List<OrderItem> items;Spring Cache 적용
섹션 제목: “Spring Cache 적용”implementation 'org.springframework.boot:spring-boot-starter-cache'implementation 'org.springframework.boot:spring-boot-starter-data-redis'@Servicepublic class ProductService {
// 캐시 적용 — Redis에 저장, 1시간 TTL @Cacheable(value = "products", key = "#id") public Product getProduct(Long id) { return productRepository.findById(id).orElseThrow(); }
// 캐시 갱신 @CachePut(value = "products", key = "#product.id") public Product updateProduct(Product product) { return productRepository.save(product); }
// 캐시 삭제 @CacheEvict(value = "products", key = "#id") public void deleteProduct(Long id) { productRepository.deleteById(id); }}# Redis 캐시 TTL 설정spring: cache: redis: time-to-live: 3600000 # 1시간 (ms)비동기 처리
섹션 제목: “비동기 처리”// @EnableAsync를 메인 클래스에 추가@SpringBootApplication@EnableAsyncpublic class Application { }
// 비동기 메서드@Servicepublic class NotificationService {
@Async public CompletableFuture<Void> sendEmail(String to, String content) { // 이메일 발송 (API 응답에 영향 안 줌) emailClient.send(to, content); return CompletableFuture.completedFuture(null); }}
// 스레드 풀 설정@Configuration@EnableAsyncpublic class AsyncConfig implements AsyncConfigurer {
@Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(10); executor.setMaxPoolSize(50); executor.setQueueCapacity(100); executor.setThreadNamePrefix("async-"); executor.initialize(); return executor; }}Actuator로 성능 모니터링
섹션 제목: “Actuator로 성능 모니터링”management: endpoints: web: exposure: include: health, metrics, prometheus metrics: export: prometheus: enabled: true# 주요 메트릭 확인curl localhost:8080/actuator/metrics/jvm.memory.usedcurl localhost:8080/actuator/metrics/hikaricp.connections.activecurl localhost:8080/actuator/metrics/http.server.requests
# Prometheus로 수집curl localhost:8080/actuator/prometheus다음 단계
섹션 제목: “다음 단계”이 가이드를 내 서비스에 직접 적용해 보세요.
TestForge 무료 스캔 시작 →