콘텐츠로 이동

Spring Boot 성능 최적화 — JVM 튜닝부터 쿼리 개선까지

Spring Boot 애플리케이션이 느리다면 JVM, 커넥션 풀, 쿼리, 캐시 순서로 확인하세요.

Terminal window
# 컨테이너 환경 (권장)
JAVA_OPTS="-XX:MaxRAMPercentage=75.0 -XX:InitialRAMPercentage=50.0"
# 직접 지정
JAVA_OPTS="-Xms512m -Xmx1g"
# Xms: 초기 힙 크기 / Xmx: 최대 힙 크기
# 컨테이너에서는 Xms = Xmx로 설정 권장 (GC 예측 가능성)
Terminal window
# G1GC — Java 9+ 기본값, 대부분의 경우 적합
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200 # 최대 GC 멈춤 목표 200ms
# ZGC — 낮은 레이턴시 필요 시 (Java 15+)
-XX:+UseZGC
# GC 멈춤 < 1ms
application.yml
# 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.SecurityAutoConfiguration
// N+1 발생 코드
@Entity
public 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;
build.gradle
implementation 'org.springframework.boot:spring-boot-starter-cache'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
@Service
public 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
@EnableAsync
public class Application { }
// 비동기 메서드
@Service
public class NotificationService {
@Async
public CompletableFuture<Void> sendEmail(String to, String content) {
// 이메일 발송 (API 응답에 영향 안 줌)
emailClient.send(to, content);
return CompletableFuture.completedFuture(null);
}
}
// 스레드 풀 설정
@Configuration
@EnableAsync
public 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;
}
}
application.yml
management:
endpoints:
web:
exposure:
include: health, metrics, prometheus
metrics:
export:
prometheus:
enabled: true
Terminal window
# 주요 메트릭 확인
curl localhost:8080/actuator/metrics/jvm.memory.used
curl localhost:8080/actuator/metrics/hikaricp.connections.active
curl localhost:8080/actuator/metrics/http.server.requests
# Prometheus로 수집
curl localhost:8080/actuator/prometheus
visitor count

이 가이드를 내 서비스에 직접 적용해 보세요.

TestForge 무료 스캔 시작 →